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搜索分析完毕!

  • 相关阅读:
    【LeetCode】Validate Binary Search Tree
    【LeetCode】Search in Rotated Sorted Array II(转)
    【LeetCode】Search in Rotated Sorted Array
    【LeetCode】Set Matrix Zeroes
    【LeetCode】Sqrt(x) (转载)
    【LeetCode】Integer to Roman
    贪心算法
    【LeetCode】Best Time to Buy and Sell Stock III
    【LeetCode】Best Time to Buy and Sell Stock II
    CentOS 6 上安装 pip、setuptools
  • 原文地址:https://www.cnblogs.com/wi100sh/p/4325213.html
Copyright © 2011-2022 走看看