zoukankan      html  css  js  c++  java
  • 【转】Android4.4(MT8685)源码蓝牙解析--BLE搜索

    原文网址:http://blog.csdn.net/u013467735/article/details/41962075

    BLE:全称为Bluetooth Low Energy。蓝牙规范4.0最重要的一个特性就是低功耗。BLE使得蓝牙设备可通过一粒纽扣电池供电以维持续工作数年之久。很明显,BLE使得蓝牙设备在钟表、远程控制、医疗保健及运动感应器等市场具有极光明的应用场景。

    Google从Android 4.3开始添加了对蓝牙4.0的支持。本文一个demo为入口分析 BLE 搜索的流程。

    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. package com.dy.ble;  
    2.   
    3. import android.annotation.SuppressLint;  
    4. import android.app.Activity;  
    5. import android.bluetooth.BluetoothAdapter;  
    6. import android.bluetooth.BluetoothDevice;  
    7. import android.os.Bundle;  
    8. import android.util.Log;  
    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 static final String TAG = "BLE";  
    15.     private Button scanBtn;  
    16.     private BluetoothAdapter bluetoothAdapter;  
    17.       
    18.     @Override  
    19.     protected void onCreate(Bundle savedInstanceState) {  
    20.         super.onCreate(savedInstanceState);  
    21.         setContentView(R.layout.main);  
    22.           
    23.         bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();  
    24.         if(!bluetoothAdapter.isEnabled()){  
    25.             bluetoothAdapter.enable();  
    26.         }  
    27.         scanBtn = (Button) this.findViewById(R.id.btn_scan);  
    28.         scanBtn.setOnClickListener(new OnClickListener(){  
    29.   
    30.             @SuppressLint("NewApi")  
    31.             @Override  
    32.             public void onClick(View arg0) {  
    33.                 if(bluetoothAdapter.isEnabled()){  
    34.                     bluetoothAdapter.startLeScan(callback);  
    35.                 }  
    36.             }  
    37.               
    38.         });  
    39.           
    40.     }  
    41.       
    42.     @SuppressLint("NewApi")  
    43.     private BluetoothAdapter.LeScanCallback callback = new BluetoothAdapter.LeScanCallback(){  
    44.   
    45.         @Override  
    46.         public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {  
    47.             Log.d(TAG, "onLeScan device = " + device + ",rssi = " + rssi + "scanRecord = " + scanRecord);  
    48.         }  
    49.     };  
    50.   
    51. }  

    点击按钮就会开始扫描,扫描到设备时,就会触发onLeScan这个回调方法,并且可以从参数中获取扫描到的蓝牙设备信息。下面分析BluetoothAdapter中的startLeScan方法。

    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. public boolean startLeScan(LeScanCallback callback) {  
    2.        return startLeScan(null, callback);  
    3.    }  


    这里调用了一个同名的方法,

    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. public boolean startLeScan(UUID[] serviceUuids, LeScanCallback callback) {  
    2.         if (DBG) Log.d(TAG, "startLeScan(): " + serviceUuids);  
    3.   
    4.         if (callback == null) {  
    5.             if (DBG) Log.e(TAG, "startLeScan: null callback");  
    6.             return false;  
    7.         }  
    8.   
    9.         synchronized(mLeScanClients) {  
    10.             if (mLeScanClients.containsKey(callback)) {  
    11.                 if (DBG) Log.e(TAG, "LE Scan has already started");  
    12.                 return false;  
    13.             }  
    14.   
    15.             try {  
    16.                 IBluetoothGatt iGatt = mManagerService.getBluetoothGatt();  
    17.                 if (iGatt == null) {  
    18.                      if (DBG) Log.e("BluetoothAdapterReceiver", "iGatt == null");  
    19.                     // BLE is not supported  
    20.                     return false;  
    21.                 }  
    22.   
    23.                 UUID uuid = UUID.randomUUID();  
    24.                 GattCallbackWrapper wrapper = new GattCallbackWrapper(this, callback, serviceUuids);  
    25.                 iGatt.registerClient(new ParcelUuid(uuid), wrapper);  
    26.                 if (wrapper.scanStarted()) {  
    27.                     if (DBG) Log.e("BluetoothAdapterReceiver", "wrapper.scanStarted()==true");  
    28.                     mLeScanClients.put(callback, wrapper);  
    29.                     return true;  
    30.                 }  
    31.             } catch (RemoteException e) {  
    32.                 Log.e(TAG,"",e);  
    33.             }  
    34.         }  
    35.         return false;  
    36.     }  


    这个方法需要BLUETOOTH_ADMIN权限,第一个参数是各种蓝牙服务的UUID数组,UUID是“Universally Unique Identifier”的简称,通用唯一识别码的意思。对于蓝牙设备,每个服务都有通用、独立、唯一的UUID与之对应。也就是说,在同一时间、同一地点,不可能有两个相同的UUID标识的不同服务。第二个参数是前面传进来的LeScanCallback对象。

    接下来分析下mManagerService,它是一个IBluetoothManager对象,IBluetoothManager是一个AIDL,可以实现跨进程通信,其在源码中的路径为:/alps/frameworks/base/core/java/android/bluetooth/IBluetoothManager.aidl。下面来看看mManagerService的实例化,

    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. BluetoothAdapter(IBluetoothManager managerService) {  
    2.   
    3.        if (managerService == null) {  
    4.            throw new IllegalArgumentException("bluetooth manager service is null");  
    5.        }  
    6.        try {  
    7.            mService = managerService.registerAdapter(mManagerCallback);  
    8.        } catch (RemoteException e) {Log.e(TAG, "", e);}  
    9.        mManagerService = managerService;  
    10.        mLeScanClients = new HashMap<LeScanCallback, GattCallbackWrapper>();  
    11.    }  


    直接将BluetoothAdapter构造方法的参数传给了它,来看看这个参数到底是什么?

    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. public static synchronized BluetoothAdapter getDefaultAdapter() {  
    2.         if (sAdapter == null) {  
    3.             IBinder b = ServiceManager.getService(BLUETOOTH_MANAGER_SERVICE);  
    4.             if (b != null) {  
    5.                 IBluetoothManager managerService = IBluetoothManager.Stub.asInterface(b);  
    6.                 sAdapter = new BluetoothAdapter(managerService);  
    7.             } else {  
    8.                 Log.e(TAG, "Bluetooth binder is null");  
    9.             }  
    10.         }  
    11.         return sAdapter;  
    12.     }  


    首先通过Binder机制获取了BLUETOOTH_MANAGER_SERVICE服务的IBinder对象,这个服务是在系统启动的时候添加进去的,在SystemServer.java中

    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. <pre name="code" class="java"> bluetooth = new BluetoothManagerService(context);  
    2.  ServiceManager.addService(BluetoothAdapter.BLUETOOTH_MANAGER_SERVICE, bluetooth);  
    
    
    

    这里实际就是实例化了一个BluetoothManagerService对象,然后把这个对象通过Binder保存在BLUETOOTH_MANAGER_SERVICE服务中。最后把这个IBinder对象转化为IBluetoothManager对象。所以managerService实际就是一个BluetoothManagerService对象。

    现在回到BluetoothAdapter的startLeScan方法中,

    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. IBluetoothGatt iGatt = mManagerService.getBluetoothGatt();  

    这里实际就是调用BluetoothManagerService中的getBluetoothGatt方法了,我们进去看看

    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. public IBluetoothGatt getBluetoothGatt() {  
    2.         // sync protection  
    3.         return mBluetoothGatt;  
    4.     }  


    这里直接返回一个IBluetoothGatt对象,那我们就来看看这个对象时在哪里得到的呢?其实通过对代码的研究发现, 这个对象是在蓝牙开启的时候得到的!

    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. public boolean enable() {  
    2.         if ((Binder.getCallingUid() != Process.SYSTEM_UID) &&  
    3.             (!checkIfCallerIsForegroundUser())) {  
    4.             Log.w(TAG,"enable(): not allowed for non-active and non system user");  
    5.             return false;  
    6.         }  
    7.   
    8.         mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,  
    9.                                                 "Need BLUETOOTH ADMIN permission");  
    10.         if (DBG) {  
    11.             Log.d(TAG,"enable():  mBluetooth =" + mBluetooth +  
    12.                     " mBinding = " + mBinding);  
    13.         }  
    14.         /// M: MoMS permission check @{  
    15.         if(FeatureOption.MTK_MOBILE_MANAGEMENT) {  
    16.             checkEnablePermission();  
    17.             return true;  
    18.         }  
    19.         /// @}  
    20.         synchronized(mReceiver) {  
    21.             mQuietEnableExternal = false;  
    22.             mEnableExternal = true;  
    23.             // waive WRITE_SECURE_SETTINGS permission check  
    24.             long callingIdentity = Binder.clearCallingIdentity();  
    25.             persistBluetoothSetting(BLUETOOTH_ON_BLUETOOTH);  
    26.             Binder.restoreCallingIdentity(callingIdentity);  
    27.             sendEnableMsg(false);  
    28.         }  
    29.         return true;  
    30.     }  


    这是开启蓝牙的代码,sendEnableMsg(false);这里看来要发送一个消息,

    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. private void sendEnableMsg(boolean quietMode) {  
    2.         mHandler.sendMessage(mHandler.obtainMessage(MESSAGE_ENABLE,  
    3.                              quietMode ? 1 : 0, 0));  
    4.     }  


    果然,看看在哪里接收了

    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. @Override  
    2.         public void handleMessage(Message msg) {  
    3.             if (DBG) Log.d (TAG, "Message: " + msg.what);  
    4.             switch (msg.what) {  
    5. <span style="white-space:pre">    </span>    case MESSAGE_ENABLE:  
    6.                     if (DBG) {  
    7.                         Log.d(TAG, "MESSAGE_ENABLE: mBluetooth = " + mBluetooth);  
    8.                     }  
    9.                     mHandler.removeMessages(MESSAGE_RESTART_BLUETOOTH_SERVICE);  
    10.                     mEnable = true;  
    11.                     handleEnable(msg.arg1 == 1);  
    12.                     break;  
    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. <span style="white-space:pre">        </span>}  
    2. }  


    进入handleEnable方法看看

    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. private void handleEnable(boolean quietMode) {  
    2.        mQuietEnable = quietMode;  
    3.   
    4.        synchronized(mConnection) {  
    5.            if (DBG) Log.d(TAG, "handleEnable: mBluetooth = " + mBluetooth +   
    6.                    ", mBinding = " + mBinding + "quietMode = " + quietMode);  
    7.            if ((mBluetooth == null) && (!mBinding)) {  
    8.                if (DBG) Log.d(TAG, "Bind AdapterService");  
    9.                //Start bind timeout and bind  
    10.                Message timeoutMsg=mHandler.obtainMessage(MESSAGE_TIMEOUT_BIND);  
    11.                mHandler.sendMessageDelayed(timeoutMsg,TIMEOUT_BIND_MS);  
    12.                mConnection.setGetNameAddressOnly(false);  
    13.                Intent i = new Intent(IBluetooth.class.getName());  
    14.                if (!doBind(i, mConnection,Context.BIND_AUTO_CREATE, UserHandle.CURRENT)) {  
    15.                    mHandler.removeMessages(MESSAGE_TIMEOUT_BIND);  
    16.                    Log.e(TAG, "Fail to bind to: " + IBluetooth.class.getName());  
    17.                } else {  
    18.                    mBinding = true;  
    19.                }  
    20.            } else if (mBluetooth != null) {  
    21.                if (mConnection.isGetNameAddressOnly()) {  
    22.                    // if GetNameAddressOnly is set, we can clear this flag,  
    23.                    // so the service won't be unbind  
    24.                    // after name and address are saved  
    25.                    mConnection.setGetNameAddressOnly(false);  
    26.                    //Register callback object  
    27.                    try {  
    28.                        mBluetooth.registerCallback(mBluetoothCallback);  
    29.                    } catch (RemoteException re) {  
    30.                        Log.e(TAG, "Unable to register BluetoothCallback",re);  
    31.                    }  
    32.                    //Inform BluetoothAdapter instances that service is up  
    33.                    sendBluetoothServiceUpCallback();  
    34.                }  
    35.   
    36.                //Enable bluetooth  
    37.                try {  
    38.                    if (!mQuietEnable) {  
    39.                        if(!mBluetooth.enable()) {  
    40.                            Log.e(TAG,"IBluetooth.enable() returned false");  
    41.                        }  
    42.                    }  
    43.                    else {  
    44.                        if(!mBluetooth.enableNoAutoConnect()) {  
    45.                            Log.e(TAG,"IBluetooth.enableNoAutoConnect() returned false");  
    46.                        }  
    47.                    }  
    48.                } catch (RemoteException e) {  
    49.                    Log.e(TAG,"Unable to call enable()",e);  
    50.                }  
    51.            }  
    52.        }  
    53.    }  


    这里会调用doBinder方法来绑定服务,

    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. boolean doBind(Intent intent, ServiceConnection conn, int flags, UserHandle user) {  
    2.         ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);  
    3.         intent.setComponent(comp);  
    4.         if (comp == null || !mContext.bindServiceAsUser(intent, conn, flags, user)) {  
    5.             Log.e(TAG, "Fail to bind to: " + intent);  
    6.             return false;  
    7.         }  
    8.         return true;  
    9.     }  


    这个conn就是mConnection,那么mConnection是什么呢?

    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. private BluetoothServiceConnection mConnection = new BluetoothServiceConnection();  
    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. private class BluetoothServiceConnection implements ServiceConnection {  
    2.   
    3.        private boolean mGetNameAddressOnly;  
    4.   
    5.        public void setGetNameAddressOnly(boolean getOnly) {  
    6.            mGetNameAddressOnly = getOnly;  
    7.        }  
    8.   
    9.        public boolean isGetNameAddressOnly() {  
    10.            return mGetNameAddressOnly;  
    11.        }  
    12.   
    13.        public void onServiceConnected(ComponentName className, IBinder service) {  
    14.            if (DBG) Log.d(TAG, "BluetoothServiceConnection: " + className.getClassName());  
    15.            Message msg = mHandler.obtainMessage(MESSAGE_BLUETOOTH_SERVICE_CONNECTED);  
    16.            // TBD if (className.getClassName().equals(IBluetooth.class.getName())) {  
    17.            if (className.getClassName().equals("com.android.bluetooth.btservice.AdapterService")) {  
    18.                msg.arg1 = SERVICE_IBLUETOOTH;  
    19.                // } else if (className.getClassName().equals(IBluetoothGatt.class.getName())) {  
    20.            } else if (className.getClassName().equals("com.android.bluetooth.gatt.GattService")) {  
    21.                msg.arg1 = SERVICE_IBLUETOOTHGATT;  
    22.            } else {  
    23.                Log.e(TAG, "Unknown service connected: " + className.getClassName());  
    24.                return;  
    25.            }  
    26.            msg.obj = service;  
    27.            mHandler.sendMessage(msg);  
    28.        }  
    29.   
    30.        public void onServiceDisconnected(ComponentName className) {  
    31.            // Called if we unexpected disconnected.  
    32.            if (DBG) Log.d(TAG, "BluetoothServiceConnection, disconnected: " +  
    33.                           className.getClassName());  
    34.            Message msg = mHandler.obtainMessage(MESSAGE_BLUETOOTH_SERVICE_DISCONNECTED);  
    35.            if (className.getClassName().equals("com.android.bluetooth.btservice.AdapterService")) {  
    36.                msg.arg1 = SERVICE_IBLUETOOTH;  
    37.            } else if (className.getClassName().equals("com.android.bluetooth.gatt.GattService")) {  
    38.                msg.arg1 = SERVICE_IBLUETOOTHGATT;  
    39.            } else {  
    40.                Log.e(TAG, "Unknown service disconnected: " + className.getClassName());  
    41.                return;  
    42.            }  
    43.            mHandler.sendMessage(msg);  
    44.        }  
    45.    }  

    现在我们就知道原来这个mConnection是一个绑定服务的连接对象,所以现在BluetoothManagerService绑定了一个IBluetooth的AIDL服务,这时onServiceConnected方法会执行,并且会发送一个MESSAGE_BLUETOOTH_SERVICE_CONNECTED消息,来看接收消息的地方

    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. case MESSAGE_BLUETOOTH_SERVICE_CONNECTED:  
    2.                 {  
    3.                     if (DBG) Log.d(TAG,"MESSAGE_BLUETOOTH_SERVICE_CONNECTED: " + msg.arg1);  
    4.   
    5.                     IBinder service = (IBinder) msg.obj;  
    6.                     synchronized(mConnection) {  
    7.                         if (msg.arg1 == SERVICE_IBLUETOOTHGATT) {  
    8.                             mBluetoothGatt = IBluetoothGatt.Stub.asInterface(service);  
    9.                             break;  
    10.                         } // else must be SERVICE_IBLUETOOTH  
    11.   
    12.                         //Remove timeout  
    13.                         mHandler.removeMessages(MESSAGE_TIMEOUT_BIND);  
    14.   
    15.                         mBinding = false;  
    16.                         mBluetooth = IBluetooth.Stub.asInterface(service);  
    17.   
    18.                         try {  
    19.                             boolean enableHciSnoopLog = (Settings.Secure.getInt(mContentResolver,  
    20.                                 Settings.Secure.BLUETOOTH_HCI_LOG, 0) == 1);  
    21.                             if (!mBluetooth.configHciSnoopLog(enableHciSnoopLog)) {  
    22.                                 Log.e(TAG,"IBluetooth.configHciSnoopLog return false");  
    23.                             }  
    24.                         } catch (RemoteException e) {  
    25.                             Log.e(TAG,"Unable to call configHciSnoopLog", e);  
    26.                         }  
    27.   
    28.                         if (mConnection.isGetNameAddressOnly()) {  
    29.                             //Request GET NAME AND ADDRESS  
    30.                             Message getMsg = mHandler.obtainMessage(MESSAGE_GET_NAME_AND_ADDRESS);  
    31.                             mHandler.sendMessage(getMsg);  
    32.                             if (!mEnable) return;  
    33.                         }  
    34.   
    35.                         mConnection.setGetNameAddressOnly(false);  
    36.                         //Register callback object  
    37.                         try {  
    38.                             mBluetooth.registerCallback(mBluetoothCallback);  
    39.                         } catch (RemoteException re) {  
    40.                             Log.e(TAG, "Unable to register BluetoothCallback",re);  
    41.                         }  
    42.                         //Inform BluetoothAdapter instances that service is up  
    43.                         sendBluetoothServiceUpCallback();  
    44.   
    45.                         //Do enable request  
    46.                         try {  
    47.                             if (mQuietEnable == false) {  
    48.                                 if(!mBluetooth.enable()) {  
    49.                                     Log.e(TAG,"IBluetooth.enable() returned false");  
    50.                                 }  
    51.                             }  
    52.                             else  
    53.                             {  
    54.                                 if(!mBluetooth.enableNoAutoConnect()) {  
    55.                                     Log.e(TAG,"IBluetooth.enableNoAutoConnect() returned false");  
    56.                                 }  
    57.                             }  
    58.                         } catch (RemoteException e) {  
    59.                             Log.e(TAG,"Unable to call enable()",e);  
    60.                         }  
    61.                     }  
    62.   
    63.                     if (!mEnable) {  
    64.                         waitForOnOff(true, false);  
    65.                         handleDisable();  
    66.                         waitForOnOff(false, false);  
    67.                     }  
    68.                     break;  
    69.                 }  


    当msg的参数1为SERVICE_IBLUETOOTHGATT时,实例化mBluetoothGatt对象,至此我们就可以得到mBluetoothGatt。

    再一次回到BluetoothAdapter的startLeScan方法中,

    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. public boolean startLeScan(UUID[] serviceUuids, LeScanCallback callback) {  
    2.         if (DBG) Log.d(TAG, "startLeScan(): " + serviceUuids);  
    3.   
    4.         if (callback == null) {  
    5.             if (DBG) Log.e(TAG, "startLeScan: null callback");  
    6.             return false;  
    7.         }  
    8.   
    9.         synchronized(mLeScanClients) {  
    10.             if (mLeScanClients.containsKey(callback)) {  
    11.                 if (DBG) Log.e(TAG, "LE Scan has already started");  
    12.                 return false;  
    13.             }  
    14.   
    15.             try {  
    16.                 IBluetoothGatt iGatt = mManagerService.getBluetoothGatt();  
    17.                 if (iGatt == null) {  
    18.                      if (DBG) Log.e("BluetoothAdapterReceiver", "iGatt == null");  
    19.                     // BLE is not supported  
    20.                     return false;  
    21.                 }  
    22.   
    23.                 UUID uuid = UUID.randomUUID();  
    24.                 GattCallbackWrapper wrapper = new GattCallbackWrapper(this, callback, serviceUuids);  
    25.                 iGatt.registerClient(new ParcelUuid(uuid), wrapper);  
    26.                 if (wrapper.scanStarted()) {  
    27.                     if (DBG) Log.e("BluetoothAdapterReceiver", "wrapper.scanStarted()==true");  
    28.                     mLeScanClients.put(callback, wrapper);  
    29.                     return true;  
    30.                 }  
    31.             } catch (RemoteException e) {  
    32.                 Log.e(TAG,"",e);  
    33.             }  
    34.         }  
    35.         return false;  
    36.     }  


    接着创建了一个GattCallbackWrapper对象,这是个BluetoothAdapter的内部类,主要用于获取回调信息,然后iGatt注册一个client,由BluetoothManagerService中的分析可知,iGatt实际是一个GattService内部类BluetoothGattBinder的对象

    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. public void registerClient(ParcelUuid uuid, IBluetoothGattCallback callback) {  
    2.             GattService service = getService();  
    3.             if (service == null) return;  
    4.             service.registerClient(uuid.getUuid(), callback);  
    5.         }  


    这里还是调用GattService的registerClient方法

    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. void registerClient(UUID uuid, IBluetoothGattCallback callback) {  
    2.        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");  
    3.   
    4.        if (DBG) Log.d(TAG, "registerClient() - UUID=" + uuid);  
    5.        mClientMap.add(uuid, callback);  
    6.        gattClientRegisterAppNative(uuid.getLeastSignificantBits(),  
    7.                                    uuid.getMostSignificantBits());  
    8.    }  


    这里面调用了本地方法,对应的JNI文件是Com_android_bluetooth_gatt.cpp,

    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. static void gattClientRegisterAppNative(JNIEnv* env, jobject object,  
    2.                                         jlong app_uuid_lsb, jlong app_uuid_msb )  
    3. {  
    4.     bt_uuid_t uuid;  
    5.   
    6.     if (!sGattIf) return;  
    7.     set_uuid(uuid.uu, app_uuid_msb, app_uuid_lsb);  
    8.     sGattIf->client->register_client(&uuid);  
    9. }  


    分析到这里其实差不多了,因为这里系统会调用MTK提供的蓝牙库来实现搜索,源码我们无法看到。

    至此,蓝牙BLE搜索分析完毕!

  • 相关阅读:
    JS reduce方法的使用
    面试娱录
    sticky置顶功能影响了锚点定位
    postcss-px-to-viewport移动端自适应
    axios请求参数自动拼接到了地址那里
    ping 不通。无法访问目标主机
    JS前后台方法的相互调用
    SQL server2008 无法连接服务器
    Assembly.Load未能加载文件或程序集“”或它的某一个依赖项。系统找不到指定的文件
    JS判断IE和非IE
  • 原文地址:https://www.cnblogs.com/wi100sh/p/4325213.html
Copyright © 2011-2022 走看看