1. 程式人生 > >Android 如何獲取已連線的藍芽地址

Android 如何獲取已連線的藍芽地址

專案中有一個需求,就是獲取已連線的藍芽地址

private void getConnectBt() {
        LogUtil.i("getConnectBt");

        int a2dp = _bluetoothAdapter.getProfileConnectionState(BluetoothProfile.A2DP);
        int headset = _bluetoothAdapter.getProfileConnectionState(BluetoothProfile.HEADSET);
        int health = _bluetoothAdapter.getProfileConnectionState(BluetoothProfile.HEALTH);
        int flag = -1;
        if (a2dp == BluetoothProfile.STATE_CONNECTED) {
            flag = a2dp;
        } else if (headset == BluetoothProfile.STATE_CONNECTED) {
            flag = headset;
        } else if (health == BluetoothProfile.STATE_CONNECTED) {
            flag = health;
        }

        Log.d(TAG,"flag:"+flag);
        if (flag != -1) {
        _bluetoothAdapter.getProfileProxy(_context, new BluetoothProfile.ServiceListener() {
            @Override
            public void onServiceDisconnected(int profile) {

            }

            @Override
            public void onServiceConnected(int profile, BluetoothProfile proxy) {
                List<BluetoothDevice> mDevices = proxy.getConnectedDevices();
                if (mDevices != null && mDevices.size() > 0) {
                    for (BluetoothDevice device : mDevices) {
                        Log.d(TAG,device.getName() + "," + device.getAddress());
                    }
                } else {

                }
            }
        }, flag);

        }
    }

從網上看到這段程式碼並沒有作用,由於flag一直等於-1,所以一直返回BluetoothProfile.STATE_DISCONNECTED。也就是說
        int a2dp = _bluetoothAdapter.getProfileConnectionState(BluetoothProfile.A2DP);
        int headset = _bluetoothAdapter.getProfileConnectionState(BluetoothProfile.HEADSET);
        int health = _bluetoothAdapter.getProfileConnectionState(BluetoothProfile.HEALTH);
這三個方法都是返回的BluetoothProfile.STATE_DISCONNECTED
    /**
     * Get the current connection state of a profile.
     * This function can be used to check whether the local Bluetooth adapter
     * is connected to any remote device for a specific profile.
     * Profile can be one of {@link BluetoothProfile#HEALTH}, {@link BluetoothProfile#HEADSET},
     * {@link BluetoothProfile#A2DP}.
     *
     * <p>Requires {@link android.Manifest.permission#BLUETOOTH}.
     *
     * <p> Return value can be one of
     * {@link BluetoothProfile#STATE_DISCONNECTED},
     * {@link BluetoothProfile#STATE_CONNECTING},
     * {@link BluetoothProfile#STATE_CONNECTED},
     * {@link BluetoothProfile#STATE_DISCONNECTING}
     */
    public int getProfileConnectionState(int profile) {
        if (getState() != STATE_ON) return BluetoothProfile.STATE_DISCONNECTED;
        try {
            synchronized(mManagerCallback) {
                if (mService != null) return mService.getProfileConnectionState(profile);
            }
        } catch (RemoteException e) {
            Log.e(TAG, "getProfileConnectionState:", e);
        }
        return BluetoothProfile.STATE_DISCONNECTED;
    }

mService是IBluetooth介面物件
    /**
     * Use {@link #getDefaultAdapter} to get the BluetoothAdapter instance.
     */
    BluetoothAdapter(IBluetoothManager managerService) {

        if (managerService == null) {
            throw new IllegalArgumentException("bluetooth manager service is null");
        }
        try {
            mService = managerService.registerAdapter(mManagerCallback);
        } catch (RemoteException e) {Log.e(TAG, "", e);}
        mManagerService = managerService;
        mLeScanClients = new HashMap<LeScanCallback, GattCallbackWrapper>();
        mHandler = new Handler(Looper.getMainLooper());
    }

    public static synchronized BluetoothAdapter getDefaultAdapter() {
        if (sAdapter == null) {
            IBinder b = ServiceManager.getService(BLUETOOTH_MANAGER_SERVICE);
            if (b != null) {
                IBluetoothManager managerService = IBluetoothManager.Stub.asInterface(b);
                sAdapter = new BluetoothAdapter(managerService);
            } else {
                Log.e(TAG, "Bluetooth binder is null");
            }
        }
        return sAdapter;
    }

我們看到managerService是IBluetoothManager的Proxy,我們來找找Stub端在哪。
private static class AdapterServiceBinder extends IBluetooth.Stub
class BluetoothManagerService extends IBluetoothManager.Stub

    public IBluetooth registerAdapter(IBluetoothManagerCallback callback){
        Message msg = mHandler.obtainMessage(MESSAGE_REGISTER_ADAPTER);
        msg.obj = callback;
        mHandler.sendMessage(msg);
        synchronized(mConnection) {
            return mBluetooth;
        }
    }

我們繼續看BluetoothAdapter的mService到底是什麼
mBluetooth = IBluetooth.Stub.asInterface(service);
private static class AdapterServiceBinder extends IBluetooth.Stub

原來BluetoothAdapter的mService = mBluetooth = IBluetooth.Stub.asInterface(service),BluetoothManagerService只是個門面而已,真正幹活的是IBluetooth.Stub也就是AdapterServiceBinder。我們回到最開始的問題,我們看AdapterService的getProfileConnectionState方法

     int getProfileConnectionState(int profile) {
        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");

        return mAdapterProperties.getProfileConnectionState(profile);
    }
我們繼續看AdapterProperties類
    int getProfileConnectionState(int profile) {
        synchronized (mObject) {
            Pair<Integer, Integer> p = mProfileConnectionState.get(profile);
            if (p != null) return p.first;
            return BluetoothProfile.STATE_DISCONNECTED;
        }
    }

mProfileConnectionState是幹什麼用的,在此之前我們先看看藍芽的一些基本概念。

BluetoothProfile
描述Bluetooth Profile的介面。Bluetooth Profile是兩個裝置基於藍芽通訊的無線介面描述。
(對Bluetooth Profile的詳細解釋,來自百度:為了更容易的保持Bluetooth裝置之間的相容,Bluetooth規範中定義了 Profile。Profile定義了裝置如何實現一種連線或者應用,你可以把Profile理解為連線層或者應用層協議。 比如,如果一家公司希望它們的Bluetooth晶片支援所有的Bluetooth耳機,那麼它只要支援HeadSet Profile即可,而無須考慮該晶片與其它Bluetooth裝置的通訊與相容性問題。如果你想購買Bluetooth產品,你應該瞭解你的應用需要哪 些Profile來完成,並且確保你購買的Bluetooth產品支援這些Profile。)
BluetoothHeadset
提供行動電話的Bluetooth耳機支援。包括Bluetooth耳機和Hands-Free (v1.5) profiles。
BluetoothA2dp
定義兩個裝置間如何通過Bluetooth連線進行高質量的音訊傳輸。
A2DP(Advanced Audio Distribution Profile):高階音訊傳輸模式。

mProfileConnectionState也就是管理各種profile連線情況的一個集合

很顯然,我們最開始那段程式碼不起作用,是因為profile不對,我們來看profile都有哪些

/**
     * Headset and Handsfree profile
     */
    public static final int HEADSET = 1;

    /**
     * A2DP profile.
     */
    public static final int A2DP = 2;

    /**
     * Health Profile
     */
    public static final int HEALTH = 3;

    /**
     * Input Device Profile
     * @hide
     */
    public static final int INPUT_DEVICE = 4;

    /**
     * PAN Profile
     * @hide
     */
    public static final int PAN = 5;

    /**
     * PBAP
     * @hide
     */
    public static final int PBAP = 6;

    /**
     * GATT
     */
    static public final int GATT = 7;

    /**
     * GATT_SERVER
     */
    static public final int GATT_SERVER = 8;

我們列舉了主要的幾種,除了我們測試的HEADSET、A2DP、HEALTH外,還有GATT和GATT_SERVER,其實都是Hide。最開始的程式碼加上GATT和GATT_SERVER之後,依然沒有結果。

實在沒有辦法了,我看到了BluetoothAdapter有一個hide方法直接呼叫了AdapterProperties的getConnectionState方法。

    /**
     * @return the mConnectionState
     */
    int getConnectionState() {
        synchronized (mObject) {
            return mConnectionState;
        }
    }

 Class<BluetoothAdapter> bluetoothAdapterClass = BluetoothAdapter.class;//得到BluetoothAdapter的Class物件
        try {//得到藍芽狀態的方法
            Method method = bluetoothAdapterClass.getDeclaredMethod("getConnectionState", (Class[]) null);
            //開啟許可權
            method.setAccessible(true);
            int state = (int) method.invoke(_bluetoothAdapter, (Object[]) null);

            if(state == BluetoothAdapter.STATE_CONNECTED){

                LogUtil.i("BluetoothAdapter.STATE_CONNECTED");

                Set<BluetoothDevice> devices = _bluetoothAdapter.getBondedDevices();
                LogUtil.i("devices:"+devices.size());

                for(BluetoothDevice device : devices){

                    Method isConnectedMethod = BluetoothDevice.class.getDeclaredMethod("isConnected", (Class[]) null);
                    method.setAccessible(true);
                    boolean isConnected = (boolean) isConnectedMethod.invoke(device, (Object[]) null);

                    if(isConnected){
                        LogUtil.i("connected:"+device.getAddress());
                        return device.getAddress();
                    }

                }

            }

        } catch (Exception e) {
            e.printStackTrace();
        }

利用反射獲取了當前藍芽連線的狀態,根據getBondedDevices獲取已繫結的藍芽連線,然後遍歷BluetoothDevice,同樣利用反射的方法呼叫BluetoothDevice的isConnected方法。成功獲取。

參考文章: