oppor11 搜索不到蓝牙android_android oppor11 文件夹权限问题 - CSDN
  • 前面已写了蓝牙打开和蓝牙搜索,这次一起来看下蓝牙文件分享的流程,也就是蓝牙应用opp目录下的代码,作为蓝牙最基本的一个功能,这部分的代码在之前的版本中就已经有了,新旧版本代码对比很多类名都是一样的,这一...
    前面已写了蓝牙打开和蓝牙搜索,这次一起来看下蓝牙文件分享的流程,也就是蓝牙应用opp目录下的代码,作为蓝牙最基本的一个功能,这部分的代码在之前的版本中就已经有了,新旧版本代码对比很多类名都是一样的,这一部分新东西不多,写在这里帮助大家梳理下流程吧。
    

            有没有这种感觉,智能手机的普及让我们提高了一点对蓝牙的关注,手机间使用蓝牙互传文件应该是最常用的应用之一,手机与电脑也可以通过蓝牙做同样的事情,大部分笔记本都支持蓝牙功能,本本上蓝牙芯片多数是broadcom的,也有其它厂商(比如东芝)不过数量不多,毕竟broadcom在BT这方面是老大。不过本本上蓝牙一般只支持蓝牙耳机听歌,并没实现对opp的支持,如果体验下手机与电脑的蓝牙文件传输怎么办呢,安装一个叫bluesoleil(中文名好像是千月)软件就可以了,这个软件对蓝牙功能的支持还是比较全的。可能需要卸载本本自带蓝牙驱动。扯淡结束,本文还是要关注手机间蓝牙opp的代码流程,这段的废话也许能帮助你提高下对蓝牙的体验。

            蓝牙发送文件时发送端先来到这里packages/apps/Bluetooth/src/com/android/bluetooth/opp/BluetoothOppLauncherActivity.java,一个没有界面只是提取下文件信息的中转站,源码的注释写的很清楚了,两个分支action.equals(Intent.ACTION_SEND)和action.equals(Intent.ACTION_SEND_MULTIPLE)

    1.  if (action.equals(Intent.ACTION_SEND) || action.equals(Intent.ACTION_SEND_MULTIPLE)) {  
    2.             //Check if Bluetooth is available in the beginning instead of at the end  
    3.             if (!isBluetoothAllowed()) {  
    4.                 Intent in = new Intent(this, BluetoothOppBtErrorActivity.class);  
    5.                 in.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);  
    6.                 in.putExtra("title"this.getString(R.string.airplane_error_title));  
    7.                 in.putExtra("content"this.getString(R.string.airplane_error_msg));  
    8.                 startActivity(in);  
    9.                 finish();  
    10.                 return;  
    11.             }  
    12.             if (action.equals(Intent.ACTION_SEND)) {  
    13.                .......   
    14.                Thread t = new Thread(new Runnable() {  
    15.                             public void run() {  
    16.                                 BluetoothOppManager.getInstance(BluetoothOppLauncherActivity.this)  
    17.                                     .saveSendingFileInfo(type,fileUri.toString(), false);  
    18.                                 //Done getting file info..Launch device picker  
    19.                                 //and finish this activity  
    20.                                 launchDevicePicker();  
    21.                                 finish();  
    22.                             }  
    23.                         });  ......           
    24.             } else if (action.equals(Intent.ACTION_SEND_MULTIPLE)) {  
    25.               .......                     
    26.             }  

               最前面那个isBluetoothAllowed()会判断是否处于飞行模式,如果是会禁止发送的。在launchDevicePicker()里还会判断蓝牙是否已经打开,就是下面这个条件语句(!BluetoothOppManager.getInstance(this).isEnabled())。如果已经打开了蓝牙,如果蓝牙打开了就进入设备选择界面DeviceListPreferenceFragment(DevicePickerFragment)选择设备,这个跳转过程简单说明下,注意这个new Intent(BluetoothDevicePicker.ACTION_LAUNCH)里字符串,完整定义public static final String ACTION_LAUNCH = "android.bluetooth.devicepicker.action.LAUNCH";路径frameworks/base/core/java/android/bluetooth/BluetoothDevicePicker.java,你会在setting应用的manifest.xml里发现

    1. <activity android:name=".bluetooth.DevicePickerActivity"  
    2.                 android:theme="@android:style/Theme.Holo.DialogWhenLarge"  
    3.                 android:label="@string/device_picker"  
    4.                 android:clearTaskOnLaunch="true">  
    5.             <intent-filter>  
    6.                 <action android:name="android.bluetooth.devicepicker.action.LAUNCH" />  
    7.                 <category android:name="android.intent.category.DEFAULT" />  
    8.             </intent-filter>  
    9.         </activity>  
              这样目标就指向了DevicePickerActivity,注意此时它的代码路径是packages/apps/Settings/src/com/android/settings/bluetooth/DevicePickerActivity.java,这个类代码很简单,只有一个onCreate并只在里加载了一个布局文件bluetooth_device_picker.xml,就是这个布局文件指明下一站在哪,看下面就知道怎么来到DevicePickerFragment了
    1. <fragment  
    2.         android:id="@+id/bluetooth_device_picker_fragment"  
    3.         android:name="com.android.settings.bluetooth.DevicePickerFragment"  
    4.         android:layout_width="match_parent"  
    5.         android:layout_height="0dip"  
    6.         android:layout_weight="1" />  
              到了这里,已经可看到配对过的蓝牙列表了,选择其中一个点击会来到这里,里面那个sendDevicePickedIntent是我们关心的,又发了一个广播,去找谁收了广播就好了
    1.     void onDevicePreferenceClick(BluetoothDevicePreference btPreference) {  
    2.         mLocalAdapter.stopScanning();  
    3.         LocalBluetoothPreferences.persistSelectedDeviceInPicker(  
    4.                 getActivity(), mSelectedDevice.getAddress());  
    5.         if ((btPreference.getCachedDevice().getBondState() ==  
    6.                 BluetoothDevice.BOND_BONDED) || !mNeedAuth) {  
    7.             sendDevicePickedIntent(mSelectedDevice);  
    8.             finish();  
    9.         } else {  
    10.             super.onDevicePreferenceClick(btPreference);  
    11.         }  
    12.     }<div>    public static final String ACTION_LAUNCH = "android.bluetooth.devicepicker.action.LAUNCH";  
    13.          private void sendDevicePickedIntent(BluetoothDevice device) {  
    14.          Intent intent = new Intent(BluetoothDevicePicker.ACTION_DEVICE_SELECTED);  
    15.          intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);  
    16.          if (mLaunchPackage != null && mLaunchClass != null) {  
    17.              intent.setClassName(mLaunchPackage, mLaunchClass);  
    18.          }  
    19.         getActivity().sendBroadcast(intent);}  
    20. </div>  
            通过BluetoothDevicePicker.ACTION_DEVICE_SELECTED查找,会在/packages/apps/Bluetooth/src/com/android/bluetooth/opp/BluetoothOppReceiver.java这个找到对该广播的处理,也就是下面的代码:
    1. else if (action.equals(BluetoothDevicePicker.ACTION_DEVICE_SELECTED)) {  
    2.           BluetoothOppManager mOppManager = BluetoothOppManager.getInstance(context);           
    3.           BluetoothDevice remoteDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);   
    4.            
    5.           // Insert transfer session record to database  
    6.           mOppManager.startTransfer(remoteDevice);  
    7.            
    8.           // Display toast message  
    9.           String deviceName = mOppManager.getDeviceName(remoteDevice);  
    10.           .......  
    11. }  
            看来关键代码是mOppManager.startTransfer(remoteDevice),在packages/apps/Bluetooth/src/com/android/bluetooth/opp/BluetoothOppManager.java,里面开启线程执行发送动作,既然是开启线程,直接去看run方法就是了,方法里面依旧区分单个和多个文件的发送,看一个就可以。
    1.    public void startTransfer(BluetoothDevice device) {  
    2.         if (V) Log.v(TAG, "Active InsertShareThread number is : " + mInsertShareThreadNum);  
    3.         InsertShareInfoThread insertThread;  
    4.         synchronized (BluetoothOppManager.this) {  
    5.             if (mInsertShareThreadNum > ALLOWED_INSERT_SHARE_THREAD_NUMBER) {  
    6.                 ...........  
    7.                 return;  
    8.             }  
    9.             insertThread = new InsertShareInfoThread(device, mMultipleFlag, mMimeTypeOfSendingFile,  
    10.                     mUriOfSendingFile, mMimeTypeOfSendingFiles, mUrisOfSendingFiles,  
    11.                     mIsHandoverInitiated);  
    12.             if (mMultipleFlag) {  
    13.                 mfileNumInBatch = mUrisOfSendingFiles.size();  
    14.             }  
    15.         }  
    16.         insertThread.start();  
    17.     }             
    18.     public void run() {  
    19.             Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);  
    20.             ..........  
    21.             if (mIsMultiple) {  
    22.                 insertMultipleShare();  
    23.             } else {  
    24.                 insertSingleShare();  
    25.             }  
    26.             .......... }  

           以insertSingleShare() 为例,在它的实现会看到mContext.getContentResolver().insert,不多想了,要去provider里找到insert()函数了,

    对应的代码在BluetoothOppProvider.java (bluetooth\src\com\android\bluetooth\opp),insert的函数实现如下,里面又拉起BluetoothOppService,开始还以为只是针对数据库的操作,差点错过了风景。路径/packages/apps/Bluetooth/src/com/android/bluetooth/opp/BluetoothOppService.java

    1. public Uri insert(Uri uri, ContentValues values) {  
    2.  if (rowID != -1) {  
    3.      context.startService(new Intent(context, BluetoothOppService.class));  
    4.       ret = Uri.parse(BluetoothShare.CONTENT_URI + "/" + rowID);  
    5.      context.getContentResolver().notifyChange(uri, null);  
    6.  } else {  
    7.       if (D) Log.d(TAG, "couldn't insert into btopp database");  
    8.  }  

           在BluetoothOppService的onStartCommand方法中会看到updateFromProvider(),这里又开启了一个线程UpdateThread,后续代码当然是看它的run方法了,这里面内容不少,好在这部分代码注释比较多,理解起来不难。先暂时只关心发送的动作insertShare方法,代码也不少,只贴出了告诉我们接下来去哪里的代码和有关的逻辑注释,在下面的代码我们可以看到 BluetoothOppTransfer.java的对象,下一站就是它了。

    1. private void insertShare(Cursor cursor, int arrayPos) {  
    2.     .........  
    3.     /* 
    4.      * Add info into a batch. The logic is 
    5.      * 1) Only add valid and readyToStart info 
    6.      * 2) If there is no batch, create a batch and insert this transfer into batch, 
    7.      * then run the batch 
    8.      * 3) If there is existing batch and timestamp match, insert transfer into batch 
    9.      * 4) If there is existing batch and timestamp does not match, create a new batch and 
    10.      * put in queue 
    11.      */  
    12.     if (info.isReadyToStart()) {  
    13.         .............  
    14.         if (mBatchs.size() == 0) {  
    15.            ........  
    16.             mBatchs.add(newBatch);  
    17.             if (info.mDirection == BluetoothShare.DIRECTION_OUTBOUND) {  
    18.                  mTransfer = new BluetoothOppTransfer(this, mPowerManager, newBatch);  
    19.             } else if (info.mDirection == BluetoothShare.DIRECTION_INBOUND) {  
    20.                 mServerTransfer = new BluetoothOppTransfer(this, mPowerManager, newBatch,  
    21.                         mServerSession);  
    22.             }  
    23.   
    24.             if (info.mDirection == BluetoothShare.DIRECTION_OUTBOUND && mTransfer != null) {  
    25.                 mTransfer.start();  
    26.             } else if (info.mDirection == BluetoothShare.DIRECTION_INBOUND  
    27.                     && mServerTransfer != null) {  
    28.                 mServerTransfer.start();  
    29.             }  
    30.   
    31.         } else {  
    32.             .........  
    33.     }}  
            虽然名字是start(),可实际并不是什么线程的,就是一普通方法的,路径是/packages/apps/Bluetooth/src/com/android/bluetooth/opp/BluetoothOppTransfer.java
    1. public void start() {  
    2.       ....这里省略未贴的代码是检查蓝牙是否打开,一个很谨慎的判断。看似无用,不过还是安全第一。  
    3.   
    4.       if (mHandlerThread == null) {  
    5.           ........  
    6.           if (mBatch.mDirection == BluetoothShare.DIRECTION_OUTBOUND) {  
    7.               /* for outbound transfer, we do connect first */  
    8.               startConnectSession();  
    9.           } else if (mBatch.mDirection == BluetoothShare.DIRECTION_INBOUND) {  
    10.               /* 
    11.                * for inbound transfer, it's already connected, so we start 
    12.                * OBEX session directly 
    13.                */  
    14.               startObexSession();  
    15.           }  
    16.       }  
    17.   }  

            上面的代码是分发送文件和接收文件的,看下这两行代码就很清楚了,如果分享给别人是OUTBOUND,先执行startConnectSession(),这个函数最后还是要跑到startObexSession()这里的,如果收文件直接startObexSession,所以后面就只看startObexSession方法了

    1. // This transfer is outbound, e.g. share file to other device.  
    2.  public static final int DIRECTION_OUTBOUND = 0;  
    3.  // This transfer is inbound, e.g. receive file from other device.  
    4.  public static final int DIRECTION_INBOUND = 1;  
           还是在同一个类里,发送流程快结束了,同样区分是传入还是传出,发文件看OUTBOUND,去BluetoothOppObexClientSession.java
    1. private void startObexSession() {       
    2.       if (mBatch.mDirection == BluetoothShare.DIRECTION_OUTBOUND) {  
    3.           if (V) Log.v(TAG, "Create Client session with transport " + mTransport.toString());  
    4.           mSession = new BluetoothOppObexClientSession(mContext, mTransport);  
    5.       } else if (mBatch.mDirection == BluetoothShare.DIRECTION_INBOUND) {           
    6.           if (mSession == null) {  
    7.                markBatchFailed();  
    8.               mBatch.mStatus = Constants.BATCH_STATUS_FAILED;  
    9.               return;  
    10.           }  
    11.           if (V) Log.v(TAG, "Transfer has Server session" + mSession.toString());  
    12.       }  
    13.       mSession.start(mSessionHandler);  
    14.       processCurrentShare();  
    15.   }  
           同样名字是start,实际只是一个普通方法而已,会看又是一个线程 mThread = new ClientThread(mContext,mTransport),这时的start才是线程的start(),还是看run方法,一些线程状态的判断,看到doSend() 就是了,直正的发送在这里packages/apps/Bluetooth/src/com/android/bluetooth/opp/BluetoothOppObexClientSession.java,
    1. private void doSend() {  
    2.   
    3.      int status = BluetoothShare.STATUS_SUCCESS;  
    4.      ........关于status值的判断  
    5.      if (status == BluetoothShare.STATUS_SUCCESS) {  
    6.          /* do real send */ //看到这个注释了没,它才是真家伙sendFile  
    7.          if (mFileInfo.mFileName != null) {  
    8.              status = sendFile(mFileInfo);  
    9.          } else {  
    10.              /* this is invalid request */  
    11.              status = mFileInfo.mStatus;  
    12.          }  
    13.          waitingForShare = true;  
    14.      } else {  
    15.          Constants.updateShareStatus(mContext1, mInfo.mId, status);  
    16.      }  
    17.   
    18.      if (status == BluetoothShare.STATUS_SUCCESS) {  
    19.          Message msg = Message.obtain(mCallback);  
    20.          msg.what = BluetoothOppObexSession.MSG_SHARE_COMPLETE;  
    21.          msg.obj = mInfo;  
    22.          msg.sendToTarget();  
    23.      } else {  
    24.          Message msg = Message.obtain(mCallback);  
    25.          msg.what = BluetoothOppObexSession.MSG_SESSION_ERROR;  
    26.          mInfo.mStatus = status;  
    27.          msg.obj = mInfo;  
    28.          msg.sendToTarget();  
    29.      }  
    30.  }  
            sendFile是真正干活的,执行完sendFile会把分享成功或失败的消息传回去,sendFile里会执行打包的过程,对于字段的含义要看Headset.java,

    代码路径在frameworks/base/obex/javax/obex/HeaderSet.java。这个sendFile方法行数虽然多,不过逻辑还是比较清晰的,在这里就不贴了。到这蓝牙发送文件流程也就此结束。由于发送文件时长肯定是不确定,所以在这个流程我们看到了很多开启线程代码也是很正常的,对于这线程,直接看对应的run方法就是了。

            对于蓝牙接收文件时会收到MSG_INCOMING_BTOPP_CONNECTION消息,收到这个消息是由于在蓝牙打开,即蓝牙状态是 BluetoothAdapter.STATE_ON时会执行

    startSocketListener(),在这个函数开启了监听程序,看下面贴在一起的代码就明白了,

    1. if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {  
    2.     switch (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR)) {  
    3.         case BluetoothAdapter.STATE_ON:  
    4.             if (V) Log.v(TAG,"Receiver BLUETOOTH_STATE_CHANGED_ACTION, BLUETOOTH_STATE_ON");  
    5.             startSocketListener();  
    6.             break;  
    7.                           
    8. private void startSocketListener() {  
    9.         if (V) Log.v(TAG, "start RfcommListener");  
    10.         mSocketListener.start(mHandler);  
    11.         if (V) Log.v(TAG, "RfcommListener started");  
    12. }  
    13. mSocketListener.start(mHandler);这个的实现在这里,比较长,没有贴上来     
    14. /packages/apps/Bluetooth/src/com/android/bluetooth/opp/BluetoothOppRfcommListener.java  
            回到上面处理消息,在BluetoothOppService.java的handlemessage中这个分支 case BluetoothOppRfcommListener.MSG_INCOMING_BTOPP_CONNECTION, 创建一个 createServerSession(transport); 最后走/frameworks/base/obex/javax/obex/ServerSession.java的run方法中接收数据
    1. private void createServerSession(ObexTransport transport) {  
    2.      mServerSession = new BluetoothOppObexServerSession(this, transport);  
    3.      mServerSession.preStart();       
    4.  }  
            对于蓝牙接收文件部分的流程还没有细致的跟踪,暂时只看到这里,对于了解基本流程这此应该够用了,同时如果想更好理解蓝牙OPP文件传输,了解是OBEX基础协议也是有必要的,网上资料还是有不少的,多数是论文形式的。对于蓝牙OPP部分,本文只是描述android代码中的流程,旨在帮你快速的理清流程,本文对OPP本身并没有深入,相关的知识需要进一步学习才行,有同道先行的童鞋还望赐教一二,谢谢。
    展开全文
  • Android蓝牙Socket通信

    2017-09-28 11:35:05
    Android蓝牙部分暂且分为2.0,4.0,虽然苹果已经出了5.0版本了,咱先说这个。 简单讲解一下,4.0是兼容2.0的功能的,今天讲的bluetoothSocket通信是在2.0上面就已经有了。 Android手机蓝牙跟硬件蓝牙交互一般都是...
    Android的蓝牙部分暂且分为2.0,4.0,虽然苹果已经出了5.0版本了,咱先不说这个。
    简单讲解一下,4.0是兼容2.0的功能的,今天讲的bluetoothSocket通信是在2.0上面就已经有了。
    

    Android手机蓝牙跟硬件蓝牙交互一般都是用的bluetoothGatt.
    Android手机跟手机蓝牙通信一般用的是bluetoothSocket.

    这里我暂且把要连接的设备叫客户端,扫描以及请求配对都由客户端操作,被连接的设备叫做服务端。

    流程:(双方蓝牙都在打开并且服务端在可检测的情况下)

    客户端–>扫描到服务端–>通过扫描到bluetoothDevice获取bluetoothSocket(UUID);再用
    bluetoothSocket.connect();连接服务端,这里的UUID是服务端指定的UUID

    服务端–>通过蓝牙适配器获取BluetoothServerSocket(Name,UUID);–Name跟UUID都有自己指定;客户端通过这个UUID连接服务端,建立双方通信。

    服务端:

    
        OutputStream outputStream;
    
        BluetoothServerSocket serverSocket;
    
        private void startServer(){
        //新建线程,并且使服务端做好接收准备
                        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    //name以及UUID由服务端指定
                    serverSocket= bluetoothAdapter.listenUsingRfcommWithServiceRecord("Server" ,UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"));
                    Log.i("bluetooth" , "蓝牙服务器开始等待...");
                    BluetoothSocket socket = serverSocket.accept();
                    Log.i("bluetooth" , "蓝牙客户端连接上来了!");
                    outputStream = socket.getOutputStream();
                    InputStream inputStream = socket.getInputStream();
                    byte[] bytes = new byte[1024];
                    int n ;
                    while((n = inputStream.read(bytes)) != -1) {
    
                        String b = new String(bytes , 0 , n , "UTF-8");
                        Log.i("bluetooth" , "蓝牙服务器接收到数据" + b);
                    }
                    outputStream.close();
                    inputStream.close();
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        }
    
        /**
         * 发送数据给客户端
         */
        private void sendSocketData(){
            if (outputStream !=null) {
                Log.i("bluetooth" , "蓝牙服务器发送数据!");
                try {
                    //指定发送的数据已经数据编码,编码统一,不然会乱码
                    outputStream.write("数据".getBytes("UTF-8"));
                    outputStream.flush();
                } catch (IOException e) {
                    e.printStackTrace();
                }
        }
    

    客户端:

    
        BluetoothAdapter adapter;
        BluetoothDevice device;
    
        private BluetoothSocket socket;
        private OutputStream outputStream;
        private InputStream inputStream;
    
     @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_operator_login);
            sendBroadcast();
            adapter = BluetoothAdapter.getDefaultAdapter();
        }
       /**
         * 发送数据给服务端
         */
        private void sendData(){
            if (outputStream != null) {
                try {
                    outputStream.write("数据".getBytes("UTF-8"));
                    outputStream.flush();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    
       //发出蓝牙扫描的接受广播
        private void sendBroadcast(){
            BluetoothReceiver receiver = new BluetoothReceiver();
            IntentFilter filter = new IntentFilter();
            filter.addAction(BluetoothDevice.ACTION_FOUND);
            filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);//状态改变
            filter.addAction(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED);//行动扫描模式改变了
            filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);//动作状态发生了变化
            registerReceiver(receiver , filter);
        }
    
     class BluetoothReceiver extends BroadcastReceiver{
    
            @RequiresApi(api = Build.VERSION_CODES.KITKAT)
            @Override
            public void onReceive(Context context, Intent intent) {
    
                if (intent.getAction().equals(BluetoothDevice.ACTION_FOUND)){
                    //获取蓝牙设备
                    device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
    
                    if (device.getName().equals("OPPO A31")){
                        adapter.cancelDiscovery();
                        LogUtils.i("bluetoothInfo"  , "扫描到的蓝牙设备名称" + device.getName());
                        try {
                            //这里的UUID必须跟服务端的UUID一样
                            socket = device.createRfcommSocketToServiceRecord(UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"));
                            socket.connect();
                            LogUtils.i("bluetoothInfo"  , "连接服务器成功");
                            outputStream = socket.getOutputStream();
                            inputStream = socket.getInputStream();
                            new Thread(new Runnable() {
                                @Override
                                public void run() {
                                    try {
                                        byte[] b = new byte[1024];
                                        int n;
                                        while ((n = inputStream.read(b)) != -1) {
    
                                            String s = new String(b,0 , n ,"UTF-8");
                                            LogUtils.i("bluetoothInfo" , "客户端收到服务器的数据了" + s);
    
                                        }
                                    } catch (IOException e) {
                                        e.printStackTrace();
                                    }
                                }
                            }).start();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
    
                    }
                } //状态改变时
                else if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(intent.getAction())) {
                    BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                    switch (device.getBondState()) {
                        case BluetoothDevice.BOND_BONDING://正在配对
                            LogUtils.i("bluetoothInfo", "正在配对......");
                            break;
                        case BluetoothDevice.BOND_BONDED://配对结束
                            LogUtils.i("bluetoothInfo", "完成配对");
                            break;
                        case BluetoothDevice.BOND_NONE://取消配对/未配对
                            LogUtils.i("bluetoothInfo", "取消配对");
                        default:
                            break;
                    }
                }
            }
        }

    最后:

    别忘了加权限

    <uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />

    最后大家回想,你这个都没有配对啊,只是扫描了一下啊;其实在客户端扫描到服务端的时候;
    bluetoothSocket.connect();的时候这步操作就已经包含了配对操作,会提前提示双方手机配对,当然已经配对过的就不会有。

    操作基本上跟平时的tcp的Socket的用法都差不多。

    展开全文
  • Android 11 面向开发者引入了...此外,如需了解平台变更可能会在哪些方面影响您的应用,请务必查看会影响以 Android R 为目标平台的应用和所有应用的 Android 11 行为变更,以及隐私权变更。 新体验 设备控件 ...

    Android 11 面向开发者引入了一些出色的新功能和 API。以下几部分内容可帮助您了解适用于您的应用的功能并开始使用相关 API。

    有关新增、修改和移除的 API 的详细列表,请参阅 API 差异报告。如需详细了解新的 API,请访问 Android API 参考文档 — 新 API 会突出显示以方便查看。此外,如需了解平台变更可能会在哪些方面影响您的应用,请务必查看会影响以 Android R 为目标平台的应用所有应用的 Android 11 行为变更,以及隐私权变更

    新体验

    设备控件

    Android 11 包含一个新的 ControlsProviderService API,可用于向连接的外部设备提供控件。这些控件显示于 Android 电源菜单中的设备控件下。如需了解详情,请参阅控制外部设备

    媒体控件

    Android 11 更新了媒体控件的显示方式。媒体控件显示于快捷设置旁。来自多个应用的会话排列在一个可滑动的轮播界面中,其中包括在手机本地播放的会话流、远程会话流(例如在外部设备上检测到的会话或投射会话)以及可继续播放的以前的会话(按上次播放的顺序排列)。

    用户无需启动相关应用即可在轮播界面中重新开始播放以前的会话。当播放开始后,用户可按常规方式与媒体控件互动。

    如需了解详情,请参阅媒体控件

    屏幕

    更好地支持瀑布屏

    Android 11 提供了一些 API 以支持瀑布屏,这是一种无边框的全面屏。这种显示屏被视为刘海屏的变体。现有的 DisplayCutout.getSafeInset…() 方法现在会返回能够避开瀑布区域以及刘海的安全边衬区。如需在瀑布区域中呈现您的应用内容,请执行以下操作:

    注意:如果您未将上述窗口布局属性设为 LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS,Android 会在黑边模式下显示窗口,从而避开缺口和瀑布区域。

    合页角度传感器和可折叠设备

    使用 Android 11,可以通过以下方法使运行在采用合页式屏幕配置的设备上的应用能够确定合页角度:提供具有 TYPE_HINGE_ANGLE 的新传感器,以及新的 SensorEvent,后者可以监控合页角度,并提供设备的两部分之间的角度测量值。您可以使用这些原始测量值在用户操作设备时执行精细的动画显示。

    尽管对于某些类型的应用(例如启动器和壁纸)而言,知道确切的合页角度会很有用,但大多数应用都应该使用 Jetpack 窗口管理器库,通过调用 DeviceState.getPosture() 检索设备状态

    或者,您的应用也可以调用 registerDeviceStateChangeCallback(),以在 DeviceState 更改时收到通知,并在状态发生变化时做出响应。

    由于目前市场上已经有且未来还会出现更多不同的窗口和设备配置,因此对设备状态做出响应更加安全可靠。

    会话

    改进了会话

    Android 11 对会话的处理方式进行了多项改进。会话是两人或更多人之间的实时双向通信。这些会话具有特殊的重要性,并且用户在如何与其进行交互方面有多个新的选项可以选择。

    如需详细了解会话以及您的应用如何支持会话,请参阅会话

    聊天气泡

    现已面向开发者推出气泡功能,该功能有助于在系统中显示会话。气泡是 Android 10 中的一项实验性功能,通过开发者选项启用;在 Android 11 中,这项功能不再是必选功能。

    注意:在以后的预览版中,应用需要请求权限才能发送气泡。不过,在 Beta 版 1 中不需要请求权限。

    气泡功能有多项改进,现在用户可以更灵活地在每个应用中启用和停用气泡功能。对于实现了实验性支持的开发者,Android 11 中的 API 有一些变更:

    5G 图标显示

    在 Android 11(API 级别 30)及更高版本中,具有 android.Manifest.permission.READ_PHONE_STATE 权限的应用可以通过 PhoneStateListener.onDisplayInfoChanged() 请求更新电话显示信息,其中包括用于营销和品牌塑造的无线接入技术信息。

    这款新 API 提供了适用于不同运营商的各种 5G 图标显示解决方案。支持的技术包括:

    • LTE
    • 采用载波聚合技术的 LTE (LTE+)
    • 高级专业版 LTE (5Ge)
    • NR (5G)
    • 毫米波移动网络频段上的 NR (5G+)

    隐私权

    Android 11 引入了大量变更和限制,目的是加强用户隐私保护。如需了解详情,请参阅隐私权页面。

    安全

    生物识别身份验证机制更新

    为了帮助您控制应用数据的安全级别,Android 11 对生物识别身份验证机制进行了多项改进。

    身份验证类型

    Android 11 引入了 BiometricManager.Authenticators 接口,该接口定义了 BiometricManager 类支持的身份验证类型:

    BIOMETRIC_STRONG

    使用满足兼容性定义页面上定义的强度级别要求的硬件元素进行身份验证。

    BIOMETRIC_WEAK

    使用满足兼容性定义页面上定义的强度级别要求的硬件元素进行身份验证。

    DEVICE_CREDENTIAL

    使用屏幕锁定凭据(即用户的 PIN 码、解锁图案或密码)进行身份验证。

    如需定义您的应用接受的生物识别身份验证类型,请向 setAllowedAuthenticators() 方法传递一个身份验证类型或按位类型组合。例如,如果您的应用接受“强”硬件元素或屏幕锁定凭据,请传入 BIOMETRIC_STRONG | DEVICE_CREDENTIAL

    如需检查是否有必要的身份验证元素,请将同一按位类型组合传入 canAuthenticate() 方法。如有必要,请调用 ACTION_BIOMETRIC_ENROLL intent 操作。在 intent extra 中,提供您的应用可接受的一组身份验证器。该 intent 会提示用户为您的应用接受的身份验证器注册凭据。

    注意:为了注册身份验证器,用户需要创建 PIN 码、解锁图案或密码。如果用户还没有 PIN 码、解锁图案或密码,生物识别注册流程会提示他们创建一个。

    在用户进行身份验证后,您可以通过调用 getAuthenticationType() 检查用户是使用设备凭据还是生物识别凭据进行的身份验证。

    对“每次使用时进行身份验证”密钥的额外支持

    Android 11 在 BiometricPrompt 类中提供了对“每次使用时进行身份验证”密钥的更多支持。此类密钥要求每次您的应用需要访问受该密钥保护的数据时,用户都必须提供生物识别凭据、设备凭据或上述任一凭据。“每次使用时进行身份验证”密钥对高价值的事务(如支付大笔款项或更新个人的健康档案)很有用。

    如需将 BiometricPrompt 对象与“每次使用时进行身份验证”密钥相关联,请添加类似以下的代码:

    KeyGenParameterSpec authPerOpKeyGenParameterSpec =
            new KeyGenParameterSpec.Builder("myKeystoreAlias", key-purpose)
        // Accept either a biometric credential or a device credential.
        // To accept only one type of credential, include only that type as the
        // 2nd argument.
        .setUserAuthenticationParameters(0 /* duration */,
                KeyProperties.AUTH_BIOMETRIC_STRONG |
                KeyProperties.AUTH_DEVICE_CREDENTIAL)
        .build();

     

    Android 11 弃用了以下方法:

    • setDeviceCredentialAllowed() 方法。
    • setUserAuthenticationValidityDurationSeconds() 方法。
    • 不带任何参数的 canAuthenticate() 过载版本。

     

    在某些情况下,例如涉及机器学习或媒体播放时,您的应用可能需要与其他应用使用同一个大型数据集。在较早的 Android 版本中,您的应用与其他应用需要各自单独下载该数据集。

    为帮助减少网络中和磁盘上的数据冗余,Android 11 允许使用共享数据 blob 在设备上缓存这些大型数据集。如需详细了解如何共享数据集,请参阅有关共享大型数据集的深度指南

    性能和质量

    无线调试

    Android 11 支持通过 Android 调试桥 (adb) 从工作站以无线方式部署和调试应用。例如,您可以将可调试的应用部署到多台远程设备,而无需通过 USB 实际连接您的设备,从而避免常见的 USB 连接问题(例如驱动程序安装方面的问题)。

    如需使用无线调试,您需要使用配对码将您的设备与工作站配对。您的工作站和设备必须连接到同一无线网络。如需连接到您的设备,请按以下步骤操作:

    1. 在您的工作站上,更新到最新版本的 SDK 平台工具
    2. 在设备上启用开发者选项
    3. 启用无线调试选项。
    4. 在询问要在此网络上允许无线调试吗?的对话框中,点击允许
    5. 选择使用配对码配对设备。记下设备上显示的配对码、IP 地址和端口号(参见图片)。
    6. 在工作站上,打开一个终端并导航到 android_sdk/platform-tools
    7. 运行 adb pair ipaddr:port。使用第 5 步中的 IP 地址和端口号。
    8. 当系统提示时,输入您在第 5 步中获得的配对码。系统会显示一条消息,表明您的设备已成功配对。
    9. Enter pairing code: 482924
      Successfully paired to 192.168.1.130:37099 [guid=adb-235XY]
    10. 仅适用于 Linux 或 Microsoft Windows)运行 adb connect ipaddr:port。使用无线调试下的 IP 地址和端口(参见下图)。

    ADB 增量 APK 安装

    在设备上安装大型(2GB 以上)APK 可能需要很长的时间,即使应用只是稍作更改也是如此。ADB(Android 调试桥)增量 APK 安装可以安装足够的 APK 以启动应用,同时在后台流式传输剩余数据,从而加速这一过程。如果设备支持该功能,并且您安装了最新的 SDK 平台工具adb install 将自动使用此功能。如果不支持,系统会自动使用默认安装方法。

    运行以下 adb 命令以使用该功能。如果设备不支持增量安装,该命令将会失败并输出详细的解释。

    adb install --incremental

    在运行 ADB 增量 APK 安装之前,您必须先为 APK 签名并创建一个 APK 签名方案 v4 文件。必须将 v4 签名文件放在 APK 旁边,才能使此功能正常运行。

    使用原生内存分配器进行错误检测

    GWP-ASan 是一种原生内存分配器功能,可帮助查找释放后使用和堆缓冲区溢出错误。您可以全局启用此功能,也可以为应用的特定子进程启用此功能。如需了解详情,请参阅 GWP-Asan 指南

    Neural Networks API 1.3

    Android 11 扩展并改进了 Neural Networks API (NNAPI)

    新运算方式

    NNAPI 1.3 引入了新的运算数类型 TENSOR_QUANT8_ASYMM_SIGNED,以支持 TensorFlow Lite 的新量化方案

    此外,NNAPI 1.3 还引入了以下新运算:

    • QUANTIZED_LSTM
    • IF
    • WHILE
    • ELU
    • HARD_SWISH
    • FILL
    • RANK

    新的机器学习控件

    NNAPI 1.3 引入了新控件以帮助机器学习流畅运行:

    NDK Thermal API

    当设备过热时,它们可能会限制 CPU 和/或 GPU,而这可能会以意想不到的方式影响应用。如果应用或游戏包含复杂图形,大量计算或持续网络活动,它们就更容易遇到问题。

    在 Android 11 中使用 NDK Thermal API 监控设备上的温度变化,然后采取相应措施以降低耗电量和设备温度。该 API 类似于Java Thermal API;您可以使用它接收任何热状态更改的通知或直接轮询当前状态。

    文本和输入

    改进了 IME 转换

    Android 11 引入了新的 API 以改进输入法 (IME) 的转换,例如屏幕键盘。这些 API 可让您更轻松地调整应用内容,与 IME 的出现和消失以及状态和导航栏等其他元素保持同步。

    如需在聚焦至任何 EditText 时显示 IME,请调用 view.getInsetsController().show(Type.ime())(您可以在与聚焦的 EditText 相同层次结构中的任何视图上调用此方法,无需专门在 EditText 上调用它)。如需隐藏 IME,请调用 view.getInsetsController().hide(Type.ime())。您可以通过调用 view.getRootWindowInsets().isVisible(Type.ime()) 检查 IME 当前是否可见。

    如需同步应用的视图与 IME 的显示和消失,请通过提供 WindowInsetsAnimation.Callback 到 View.setWindowInsetsAnimationCallback() 在视图上设置监听器(您可以在任何视图上设置该监听器,它不一定必须为 EditText)。IME 会调用监听器的 onPrepare() 方法,之后会在转换开始时调用 onStart()。然后,它会在每次转换的过程中调用 onProgress()。转换完成后,IME 会调用 onEnd()。在转换过程中,您随时可以调用 WindowInsetsAnimation.getFraction() 以了解转换的进度。

    有关如何使用这些 API 的示例,请参阅新的 WindowInsetsAnimation 代码示例。

    控制 IME 动画

    您还可以控制 IME 动画或其他系统栏(如导航栏)的动画。如需执行此操作,请先调用 setOnApplyWindowInsetsListener(),为窗口边衬区更改设置新的监听器:

    mRoot.setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() {
       @Override
       public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
    
           Insets barsIME = insets.getInsets(Type.systemBars() | Type.ime());
           mRootView.setPadding(barsIme.left, barsIme.top, barsIme.right,
                                 barsIme.bottom);
    
          // We return the new WindowInsets.CONSUMED to stop the insets being
          // dispatched any further into the view hierarchy. This replaces the
          // deprecated WindowInsets.consumeSystemWindowInsets() and related
          // functions.
           return WindowInsets.CONSUMED;
       }
    });

     如需移动 IME 或其他系统栏,请调用控制器的 controlWindowInsetsAnimation() 方法:

    mRoot.getWindowInsetsController().controlWindowInsetsAnimation(
           Type.ime(), 1000, new LinearInterpolator(), cancellationSignal,
           new WindowInsetsAnimationControlListener() {
               @Override
               public void onReady(
                       @NonNull WindowInsetsAnimationController controller,
                       int types
                       ) {
                       // update IME inset
                       controller.setInsetsAndAlpha(Insets.of(0, 0, 0, inset),
                               1f /* alpha */, 0.1 /* fraction progress */);
               }
    
               @Override
               public void onCancelled() {}
           });

    ICU 库更新

    Android 11 更新了 android.icu 软件包,以使用 ICU 库版本 66,而 Android 10 中使用的是版本 63。新版库包含更新的 CLDR 语言区域数据以及众多对于 Android 中的国际化支持的增强功能。

    新版库包含以下主要变更:

    • 许多格式化 API 现在都支持可扩展 FormattedValue 的新返回对象类型。
    • LocaleMatcher API 在以下方面得到增强:提供了构建器类,支持 java.util.Locale 类型,并且结果类可提供有关匹配的额外数据。
    • 现在支持 Unicode 13。

    媒体

    分配 MediaCodec 缓冲区

    Android 11 包含一个新的 MediaCodec API,可让应用在分配输入和输出缓冲区时获得更多控制。这样可以让您的应用更高效地管理内存。

    新类:

    新方法:

    此外,MediaCodec.Callback() 中两种方法的行为也发生了变化:

    onInputBufferAvailable()

    如果配置为使用 Block Model API,应用应通过索引使用 MediaCodec.getQueueRequest,并将 LinearBlock/HardwareBuffer 附加到插槽,而不是通过索引调用 MediaCodec.getInputBuffer() 和 MediaCodec.queueInputBuffer()

    onOutputBufferAvailable()

    应用可以通过索引使用 MediaCodec.getOutputFrame() 获取包含更多信息的 OutputFrame 对象和 LinearBlock/HardwareBuffer 缓冲区,而不是通过索引调用 MediaCodec.getOutputBuffer()

    MediaCodec 低延时解码

    Android 11 增强了 MediaCodec,针对游戏和其他实时应用支持低延时解码。您可以将 FEATURE_LowLatency 传递到 MediaCodecInfo.CodecCapabilities.isFeatureSupported(),检查编解码器是否支持低延时解码。

    如需启用或停用低延时解码,请执行以下任一操作:

    注意:支持低延时解码可能需要额外的资源,例如更高的功耗。仅在必要时使用低延时解码。

    已弃用 OpenSL ES

    从 NDK r21b Beta 版 2 开始,已弃用 OpenSL ES API。您应改用 Oboe

    平台仍支持现有应用的 OpenSL ES。然而,使用 minSdkVersion 为 30 或更高版本的 OpenSL ES 时,系统会显示构建警告。

    新的 AAudio 函数 AAudioStream_release()

    函数 AAudioStream_close() 会同时释放和关闭音频流。这可能很危险。如果其他进程在音频流关闭后尝试对其进行访问,该进程将会崩溃。

    新函数 AAudioStream_release() 会释放音频流,但不会将其关闭。这样会释放其资源并使音频流处于已知状态。该对象将一直存在,直到您调用 AAudioStream_close()

    MediaParser API

    MediaParser 是用于媒体提取的新型低级别 API。它比 MediaExtractor 更灵活,并提供对媒体提取功能的额外控制。

    输出切换器

    Android 11 针对使用 Cast 和 MediaRouter API 的应用实现了新行为。

    除了可从应用内访问投射选项外,切换选项也显示于系统媒体播放器中。当用户改变视听环境时(例如在厨房中观看视频与在手机上观看相比,或者在家中收听音频与在车中收听相比),这有助于为用户提供无缝切换设备的流畅体验。

    默认情况下,按媒体通知中的路由选择按钮后,系统会打开输出切换器,其中包含以下选项:

    • 当前设备上的扬声器
    • 所有已连接的蓝牙音频设备

    应用还可以根据其功能提供更多选项,例如“投射”。

    应用可以使用新的 MediaRouter2 API 自定义路由选择。您可以排除不支持的设备(例如,如果您在观看 Netflix 智能电视,就可以滤除纯音频的 Chromecast),也可以添加应用可识别的其他特殊设备。

    网络连接

    Wi-Fi Passpoint 增强功能

    通过 Passpoint,应用可以自动静默地执行身份验证并连接到安全的 Wi-Fi 热点。以 API 级别 30 及更高级别为目标平台的应用可以使用 Passpoint 的以下其他功能。

    失效日期强制执行和通知

    对个人资料强制执行失效日期可让框架避免使用过期凭据自动连接到接入点,该操作必定会失败。这样可以阻止无线连接,并节省电量和后端带宽。当用户的个人资料位于范围内但已过期时,该功能会向用户显示通知。

    FQDN 匹配

    允许使用 PerProviderSubscription (PPS) 管理对象 (MO) 中的 Extension/Android 节点,配置独立于接入网络查询协议 (ANQP) 完全限定域名 (FQDN) 的命名 AAA 域。

    自签名的私人 CA

    对于 Passpoint R1 个人资料,Android 接受采用私人自签名 CA 进行连接身份验证。

    允许使用具有相同 FQDN 的多个个人资料

    允许安装具有相同 FQDN 的多个 Passpoint 个人资料。FQDN 不用作个人资料的键。需要 FQDN 的现有 Passpoint API(如 remove)会将请求应用于具有相同 FQDN 的所有匹配的个人资料。

    允许安装没有根 CA 证书的个人资料

    允许使用没有根 CA 证书的个人资料。在这种情况下,系统会根据安装在信任库中的公共根 CA 证书验证 AAA 服务器证书。

    改进了家庭网络服务提供商和漫游服务提供商的匹配

    系统会匹配家庭网络或漫游网络,而不考虑所通告的身份验证方法。此外,还增加了对 OtherHomePartners 和 HomeOIList 列表的家庭网络匹配功能的支持。

    Wi-Fi Suggestion API 扩展

    Android 11 扩展了 Wi-Fi Suggestion API,以提高应用的网络管理能力,包括:

    • 连接管理应用可以通过允许断开连接请求管理自己的网络。
    • Passpoint 网络集成到 Suggestion API 中,可以推荐给用户。
    • 通过 Analytics API,您可以获取有关网络质量的信息。

    CallScreeningService 更新

    从 Android 11 开始,CallScreeningService 可以针对来电请求有关 STIR/SHAKEN 验证状态 (verstat) 的信息。此信息将包含在来电的通话详情中。

    如果 CallScreeningService 持有 READ_CONTACTS 权限,当收到用户联系人号码的来电或拨打用户联系人号码时,应用会收到通知。

    GNSS 天线支持

    Android 11 引入了 GnssAntennaInfo 类,让您的应用能够更多地利用全球导航卫星系统 (GNSS) 提供的厘米精度定位。用户向您的应用授予 ACCESS_FINE_LOCATION 权限之后,您的应用可以访问与 GNSS 天线相关的以下详细信息:

    • 相位中心偏移 (PCO) 坐标
    • 相位中心变化 (PCV) 校正
    • 信号增益校正

    如需确定设备是否可以向您的应用提供 GNSS 天线信息,请调用 hasGnssAntennaInfo()

    隐私注意事项

    • GNSS 天线只能识别设备型号,而不能识别具体设备。
    • 如需使用 GnssAntennaInfo 类,必须具备 ACCESS_FINE_LOCATION 权限。

    图形

    NDK 图像解码器

    NDK ImageDecoder API 提供了一种标准 API,供 Android C/C++ 应用直接解码图像。应用开发者不再需要使用框架 API(通过 JNI)或捆绑第三方图像解码库。有关详情,请参阅图像解码器开发者指南

    Frame rate API

    Android 11 提供了一个 API,可让应用告知系统其预期帧速率,从而减少支持多个刷新率的设备上的抖动。有关如何使用此 API 的信息,请参阅帧速率指南

    请求并检查低延时支持

    特定的显示屏可以执行图形后期处理,例如某些外部显示屏和电视。此类后期处理改善了图形质量,但可能会增加延时。支持 HDMI 2.1 的新款显示屏具有自动低延时模式(ALLM,也称为游戏模式),该模式可以通过关闭后期处理以最大限度地缩短延时。如需详细了解 ALLM,请参阅 HDMI 2.1 规范

    窗口可以请求使用自动低延时模式(如果可用)。ALLM 对于游戏和视频会议等应用特别有用,因为对于这些应用而言,低延时的重要性要高于拥有最佳的图形质量。

    如需开启或关闭最低限度的后期处理,请调用 Window.setPreferMinimalPostProcessing(),或将窗口的 preferMinimalPostProcessing 属性设置为 true。并非所有的显示屏都支持最低限度的后期处理;如需了解某个显示屏是否支持该功能,可调用新方法 Display.isMinimalPostProcessingSupported()

    注意:如果用户停用最低限度的后期处理,或者显示屏不支持低延时模式,那么调用 Window.setPreferMinimalPostProcessing() 不会有任何作用。

    高性能图形调试层注入

    应用现在可以将外部图形层(GLESVulkan)加载到原生应用代码中,可以在不产生性能开销的前提下,提供与可调试应用相同的功能。在使用 GAPID 等工具对应用进行性能剖析时,此功能尤为重要。如需对应用进行性能剖析,只需要在应用清单文件中添加以下元数据元素,而无需让应用变成可调试应用:

    <application ... >
        <meta-data android:name="com.android.graphics.injectLayers.enable"
                      android:value="true" />
    </application>

    适用于 OpenGL ES 的 ANGLE

    您可以使用 ANGLE 运行非核心应用以评估性能,并确定特定应用是否应使用 ANGLE,而不是原生 OpenGL ES 驱动程序。有关说明,请参阅使用适用于 OpenGL ES 的 ANGLE

    图片和相机

    在主动拍摄期间关闭通知提示音和振动

    从 Android 11 开始,在主动使用相机时,您的应用可以使用 setCameraAudioRestriction() 以仅关闭振动、同时关闭声音和振动或都不关闭。

    Android 模拟器中的相机支持扩展

    Android 11 改进了Android 模拟器相机功能。添加的功能包括:

    • RAW 捕获
    • YUV 重新处理
    • 3 级设备
    • 逻辑相机支持
    • 使用传感器管理器中的数据模拟传感器方向
    • 采用视频防抖技术(通过降低握手频率)
    • 采用边缘增强技术(通过移除通常在 YUV 流水线中完成的升频)
    • 并发相机

    更好地支持包含多个帧的 HEIF 图片

    从 Android 11 开始,如果您调用 ImageDecoder.decodeDrawable() 并传递包含帧序列的 HEIF 图片(如动画或连拍照片),则该方法会返回包含整个图片序列的 AnimatedImageDrawable。在较低版本的 Android 系统中,该方法会返回仅包含单个帧的 BitmapDrawable

    如果 HEIF 图片包含的多个帧不在一个序列中,您可以通过调用 MediaMetadataRetriever.getImageAtIndex()检索各个帧。

    无障碍功能

    面向无障碍服务开发者的更新

    如果您创建自定义无障碍服务,可以在 Android 11 中使用以下功能:

    • 在无障碍服务的面向用户的解释中,除了纯文本之外,现在还允许使用 HTML 和图片。这种灵活性可让您更轻松地向最终用户解释您的服务有何功能以及对他们有何帮助。
    • 如需使用比 contentDescription 在语义上更有意义的界面元素的状态说明,请调用 getStateDescription() 方法。
    • 如需请求触摸事件绕过系统的触摸浏览器,请调用 setTouchExplorationPassthroughRegion()。同样,如需请求手势绕过系统的手势检测器,请调用 setGestureDetectionPassthroughRegion()
    • 您可以请求 IME 操作(如“输入”和“下一个”),以及不启用 FLAG_SECURE 标记的窗口的屏幕截图。

    其他功能

    应用进程退出原因

    Android 11 引入了 ActivityManager.getHistoricalProcessExitReasons() 方法,用于报告近期任何进程终止的原因。应用可以使用此方法收集崩溃诊断信息,例如进程终止是由于 ANR、内存问题还是其他原因所致。此外,您还可以使用新的 setProcessStateSummary() 方法存储自定义状态信息,以便日后进行分析。

    getHistoricalProcessExitReasons() 方法会返回 ApplicationExitInfo 类的实例,该类包含与应用进程终止相关的信息。通过对此类的实例调用 getReason(),您可以确定应用进程终止的原因。例如,REASON_CRASH的返回值表示应用中发生了未处理的异常。如果应用需要确保退出事件的唯一性,可以保留特定于应用的标识符,例如基于 getTimestamp() 方法的时间戳的哈希值。

    资源加载器

    Android 11 引入了一个新 API,允许应用动态扩展资源的搜索和加载方式。新的 API 类 ResourcesLoader 和 ResourcesProvider 主要负责提供新功能。两者协同作用,可以提供额外的资源,或修改现有资源的值。

    ResourcesLoader 对象是向应用的 Resources 实例提供 ResourcesProvider 对象的容器,而 ResourcesProvider 对象提供从 APK 和资源表加载资源数据的方法。

    此 API 的一个主要用例是自定义资源加载。您可以使用 loadFromDirectory() 创建一个 ResourcesProvider,用于重定向基于文件的资源的解析,从而让其搜索特定目录,而不是应用 APK。您可以通过 AssetManager API 类中的 open() 系列方法访问这些资源,就像访问 APK 中绑定的资源一样。

    APK 签名方案 v4

    Android 11 添加了对 APK 签名方案 v4 的支持。此方案会在单独的文件 (apk-name.apk.idsig) 中生成一种新的签名,但在其他方面与 v2 和 v3 类似。没有对 APK 进行任何更改。此方案支持 ADB 增量 APK 安装,这样会加快 APK 安装速度。

    动态 intent 过滤器

    如需接收 intent,应用必须通过在其清单中定义 intent 过滤器,在编译时声明它能够接收哪些类型的数据。在 Android 10 及更低版本中,应用无法在运行时更改其 intent 过滤器。这对于虚拟化应用(如虚拟机和远程桌面)而言是一个问题,因为这些应用无法确切得知用户将在它们内部安装什么软件。

    Android 11 引入了 MIME 组,这是一个新的清单元素,可让应用在 intent 过滤器中声明一组动态的 MIME 类型,并在运行时以编程方式对其进行修改。如需使用 MIME 组,请使用新的 android:mimeGroup 属性在应用清单中添加一个数据元素:

    <intent-filter>
      <action android:name="android.intent.action.SEND"/>
      <category android:name="android.intent.category.DEFAULT"/>
      <data android:mimeGroup="myMimeGroup"/>
    </intent-filter>

    android:mimeGroup 属性的值是任意字符串 ID,用于在运行时标识 MIME 组。您可以通过将某个 MIME 组的 ID 传递给 PackageManager API 类中的以下新方法,访问和更新该 MIME 组的内容:

    如果您以编程方式将 MIME 类型添加到 MIME 组,其运作方式与清单中明确声明的静态 MIME 类型完全相同。

    注意mimeGroup 字符串是基于每个软件包定义的。在同一软件包中,您可以在多个 intent 过滤器或组件中使用相同的 mimeGroup 字符串以声明它们之间共享的 MIME 组。不同的软件包不能共享 MIME 组,但它们可以使用相同的 mimeGroup 字符串,而不相互干扰。

    自动填充增强功能

    Android 11 改进了自动填充服务。

    AssistStructure.ViewNode 中的提示标识符

    对自动填充服务来说,根据视图的属性计算视图的签名哈希值通常很有用。在计算签名哈希值时,视图提示是一个非常值得参考的属性,但提示字符串可能会随着手机的语言区域而发生变化。为了解决此问题,Android 11 使用新的 getHintIdEntry() 方法扩展了 AssistStructure.ViewNode,该方法会返回视图提示文本的资源标识符。此方法提供一个与语言区域无关的值,可用于计算签名哈希值。

    提供了数据集的事件

    为了帮助自动填充服务提高建议内容的质量,Android 11 提供了一种方法以识别自动填充服务提供了数据集但用户未选择任何数据集的情况。在 Android 11 中,FillEventHistory 会报告一种新的 TYPE_DATASETS_SHOWN 事件类型。每当自动填充服务向用户提供一个或多个数据集时,FillEventHistory 就会记录此类型的事件。自动填充服务可以将这些事件与现有的 TYPE_DATASET_SELECTED 事件结合使用来确定用户是否选择了任何提供的自动填充选项。

    与内容捕获服务共享数据

    从 Android 11 开始,应用可以与设备的内容捕获服务共享数据。借助此功能,设备可以更轻松地提供情境智能,例如显示用户环境中正在播放的歌曲的名称。

    如需将应用中的数据共享给内容捕获服务,请对 ContentCaptureManager 的实例调用 shareData() 方法。如果系统接受数据共享请求,应用会收到将与内容捕获服务共享的只写文件描述符。

    展开全文
  • 版权声明:本文为博主原创文章,未经博主允许不得转载。 一.什么是蓝牙(Bluetooth)?...1.3常用于连接耳机,鼠标和移动通讯设备等. 二.与蓝牙相关的API 2.1BluetoothAdapter: 代表了本地的蓝...

    一. 什么是蓝牙(Bluetooth)?

     

    1.1  BuleTooth是目前使用最广泛的无线通信协议

     

    1.2  主要针对短距离设备通讯(10m)

     

    1.3  常用于连接耳机,鼠标和移动通讯设备等.

     

    二. 与蓝牙相关的API

     

    2.1 BluetoothAdapter:

    代表了本地的蓝牙适配器

     

    2.2 BluetoothDevice

    代表了一个远程的Bluetooth设备

     

    三. 扫描已经配对的蓝牙设备(1)

     

    注:必须部署在真实手机上,模拟器无法实现

     

    首先需要在AndroidManifest.xml 声明蓝牙权限

     

    <user-permission Android:name="android.permission.BLUETOOTH" />

     

    配对蓝牙需要手动操作:

     

    1. 打开设置--> 无线网络 --> 蓝牙 勾选开启

     

    2. 打开蓝牙设置  扫描周围已经开启的蓝牙设备(可以与自己的笔记本电脑进行配对),点击进行配对

     

     

     电脑上会弹出提示窗口: 添加设备

     

     显示计算与设备之间的配对码,要求确认是否配对

     

     手机上也会显示类似的提示. 

     

    四. 扫描已经配对的蓝牙设备(2)

     

    4.1 获得BluetoothAdapter对象

    4.2 判断当前移动设备中是否拥有蓝牙

    4.3 判断当前移动设备中蓝牙是否已经打开

    4.4 得到所有已经配对的蓝牙设备对象

     

     

    实现代码如下:

    MainActivity:

     

    [java] view plain copy
     
    1. import java.util.Iterator;  
    2. import java.util.Set;  
    3.   
    4. import android.app.Activity;  
    5. import android.bluetooth.BluetoothAdapter;  
    6. import android.bluetooth.BluetoothDevice;  
    7. import android.content.Intent;  
    8. import android.os.Bundle;  
    9. import android.view.View;  
    10. import android.view.View.OnClickListener;  
    11. import android.widget.Button;  
    12.   
    13. public class MainActivity extends Activity {  
    14.     private Button button = null;  
    15.     /** Called when the activity is first created. */  
    16.     @Override  
    17.     public void onCreate(Bundle savedInstanceState) {  
    18.         super.onCreate(savedInstanceState);  
    19.         setContentView(R.layout.main);  
    20.           
    21.         button = (Button)findViewById(R.id.buttonId);  
    22.         button.setOnClickListener(new OnClickListener(){  
    23.   
    24.             @Override  
    25.             public void onClick(View v) {  
    26.                 //获得BluetoothAdapter对象,该API是android 2.0开始支持的  
    27.                 BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();  
    28.                 //adapter不等于null,说明本机有蓝牙设备  
    29.                 if(adapter != null){  
    30.                     System.out.println("本机有蓝牙设备!");  
    31.                     //如果蓝牙设备未开启  
    32.                     if(!adapter.isEnabled()){  
    33.                         Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);  
    34.                         //请求开启蓝牙设备  
    35.                         startActivity(intent);  
    36.                     }  
    37.                     //获得已配对的远程蓝牙设备的集合  
    38.                     Set<BluetoothDevice> devices = adapter.getBondedDevices();  
    39.                     if(devices.size()>0){  
    40.                         for(Iterator<BluetoothDevice> it = devices.iterator();it.hasNext();){  
    41.                             BluetoothDevice device = (BluetoothDevice)it.next();  
    42.                             //打印出远程蓝牙设备的物理地址  
    43.                             System.out.println(device.getAddress());  
    44.                         }  
    45.                     }else{  
    46.                         System.out.println("还没有已配对的远程蓝牙设备!");  
    47.                     }  
    48.                 }else{  
    49.                     System.out.println("本机没有蓝牙设备!");  
    50.                 }  
    51.             }  
    52.         });  
    53.     }  
    54. }  

    转载于:https://www.cnblogs.com/Free-Thinker/p/6798529.html

    展开全文
  •  本文已授权微信公众号 fanfan程序媛独家发布 扫一扫文章底部的二维码或在微信搜索 fanfan程序媛 即可关注 这篇文章主要说一下手机是如何通过蓝牙接收文件的。 1 创建rfcomm层sever 要想通过蓝牙接收文件,首先...
  • Android版本差异适配方案(5.0-9.0) 一个好的APP最好支持90%设备,由于不同版本系统提供的API可能不同,所以了解不同版本间系统差异很重要,这样才能更好的适配更多的智能设备。你的应用足足够健壮要看你的应用在...
  • 此文介绍一些获取Android手机硬件信息的方法 主要是从Build和TelephonyManager中获取 以及使用反射获取SystemProperties  并使用他的get方法获取一些系统隐藏掉的API 以及某些ROM独有的数据 比如OPPO手机自己定制...
  • Android开发中,要实现分享功能,可能首先想到第三方的ShareSDK,其实,想要分享一些图片,文本之类的完全没必要在App中集成第三方SDK,利用原声的SDK就可以轻松实现分享功能。
  • STM32循迹小车/Android蓝牙控制小车(三) 循迹蓝牙小车的第三篇终于来了,这篇开篇先来介绍一下整个开发过程中得构思思路。本来这应该放在第一篇,但是实际思路会因为开发过程中遇到的问题而改变,今天为止小车的...
  • Android Media Playback 中的MediaPlayer的用法及注意事项 声明:以下内容翻译自Android官网,由于译者水平有限,本文出现的错误请大家批评指正,谢谢! 尊重劳动成果,转载请注明出处,谢谢! 0.概述 安卓多媒体...
  • Android 10 适配攻略

    2020-02-26 18:15:10
    相比较去年的写的Android 9适配,这次Android 10的内容有点多。没想到写了我整整两天,吐血中。。。
  • 前言: IPhone 可以通过 ibeacon 设备发出的蓝牙广播来唤醒应用,但android有没有类似的机制来进行唤醒app...在android 8.0 的 API中,蓝牙库中的android.bluetooth.le.BluetoothLeScanner类增加了一个新方法,看下...
  • 项目需求,要求本公司自己的外发设备必须只能用指定授权的白名单的应用。一开始,我是有抵触的,认为是黑科技。没办法,工作还是要认真做的。...2、广播+服务,广播Intent.ACTION_CLOSE_SYSTEM_DIALOGS 可以监听Ho...
  • 这个学期学习安卓应用开发,Android Studio在使用手机调试时遇到了一点问题,下面根据我自己操作的实际经验,总结一下我的方法: 1、新建一个项目时,它会自动运行,可能会出现错误,只需要点击右下方的install即可...
  • uniapp-蓝牙篇1

    2020-05-26 10:18:26
    1.uniapp官网蓝牙api地址,参考模块(蓝牙,低功耗蓝牙)https://uniapp.dcloud.io/api/system/bluetooth 2.其他人写的demo(10个赞),看完基本上逻辑能清楚https://ask.dcloud.net.cn/article/36374 3.其他人写...
  • Android的死机、重启问题分析方法 原文链接:https://blog.csdn.net/jinlu7611/article/details/50592385 1.死机现象 1.1 死机定义 当手机长时间无法再被用户控制操作时,我们称为死机。在这里我们强调长时间,如果...
  • 本文章包含内容Wifi连接控制、Wifi广播接收,适配了Android6.0以上的版本Wifi下的TCP通信Wifi下的UDP通信Github项目地址码云项目地址最近公司要开发智能家居,APP要作为遥控器和控制中心,其中的原理就是智能设备...
  • 下载地址 最后更新共计113个分类5177套源码29.2 GB。...│ │ Android TagCloudView云标签的灵活运用.rar │ │ Android 实现 标签 拖动 改变位置.rar │ │ android 流式布局和热门标签.zip │ │ ...
  •  Wifi p2p是Android4.0以上用于用户之间端对端传输文件的协议,其耗流量,且传输效率比蓝牙传输高的多。其操作流程如下: 点击进入Wifi p2p设置页,华为手机一般在WLAN页的列表中可见,OPPO手机一般在其他无线...
1 2 3
收藏数 57
精华内容 22
关键字:

oppor11 搜索不到蓝牙android