Android BLE 總結-源碼篇(BluetoothLeAdvertiser)

在做Android BLE的應用程序時,我們發出廣播數據是調用BluetoothLeAdvertiser的startAdvertising方法,如下所示:

mBluetoothLeAdvertiser.startAdvertising(advertiseSettings,
				advertiseData, myAdvertiseCallback);


那麼我打算寫的BLE總結之源碼篇就以此爲線索來分析Android BLE FrameWork方面的東西。

 public void startAdvertising(AdvertiseSettings settings,
            AdvertiseData advertiseData, final AdvertiseCallback callback) {
        startAdvertising(settings, advertiseData, null, callback);
    }
public void startAdvertising(AdvertiseSettings settings,
            AdvertiseData advertiseData, AdvertiseData scanResponse,
            final AdvertiseCallback callback) {
        synchronized (mLeAdvertisers) {

//該check只是檢查mBluetoothAdater是否爲null和其狀態是否爲State_ON

            BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter);
            if (callback == null) {
                throw new IllegalArgumentException("callback cannot be null");
            }

            if (!mBluetoothAdapter.isMultipleAdvertisementSupported() &&
                    !mBluetoothAdapter.isPeripheralModeSupported()) {//是否支持廣播和作爲外圍設備
                postStartFailure(callback,
                        AdvertiseCallback.ADVERTISE_FAILED_FEATURE_UNSUPPORTED);
                return;
            }
            boolean isConnectable = settings.isConnectable();
            if (totalBytes(advertiseData, isConnectable) > MAX_ADVERTISING_DATA_BYTES ||
                    totalBytes(scanResponse, false) > MAX_ADVERTISING_DATA_BYTES) {
                postStartFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_DATA_TOO_LARGE);
                return;
            }
            if (mLeAdvertisers.containsKey(callback)) {
                postStartFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_ALREADY_STARTED);
                return;
            }
            IBluetoothGatt gatt;
            try {
                gatt = mBluetoothManager.getBluetoothGatt();
            } catch (RemoteException e) {
                Log.e(TAG, "Failed to get Bluetooth gatt - ", e);
                postStartFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR);
                return;
            }
            AdvertiseCallbackWrapper wrapper = new AdvertiseCallbackWrapper(callback, advertiseData,
                    scanResponse, settings, gatt);
            wrapper.startRegisteration();
        }
    }


大家可以看到在startAdvertising內部,首先經過了一系列的判斷,然後包裝了一個叫作AdvertiseCallbackWrapper的類來做發廣播數據的行爲。

我們先看一下startAdvertising內部都是做了哪些判斷:
1.判斷藍牙是否已經打開,否則拋出異常。

2.判斷回調callback是否爲空

3.判斷當前設備是否支持廣播數據和作爲外圍設備

4.判斷廣播數據包的長度是否超過了31字節

5.判斷廣播是否已經開始

經過了這5步初步的判斷,下面來到了最重要的地方,mBluetoothManager.getBluetoothGatt();獲取一個引用,最終的發送廣播和停止廣播都是通過這個引用來進行實現的。這裏不進行展開,因爲本文主要是對BluetoothLeAdvertiser的解讀。

下面我們就來看看剛纔提到的AdvertiseCallbackWrapper,代碼如下:

/**
     * Bluetooth GATT interface callbacks for advertising.
     */
    private class AdvertiseCallbackWrapper extends BluetoothGattCallbackWrapper {
        private static final int LE_CALLBACK_TIMEOUT_MILLIS = 2000;
        private final AdvertiseCallback mAdvertiseCallback;
        private final AdvertiseData mAdvertisement;
        private final AdvertiseData mScanResponse;
        private final AdvertiseSettings mSettings;
        private final IBluetoothGatt mBluetoothGatt;
        // mClientIf 0: not registered
        // -1: advertise stopped or registration timeout
        // >0: registered and advertising started
        private int mClientIf;
        private boolean mIsAdvertising = false;
        public AdvertiseCallbackWrapper(AdvertiseCallback advertiseCallback,
                AdvertiseData advertiseData, AdvertiseData scanResponse,
                AdvertiseSettings settings,
                IBluetoothGatt bluetoothGatt) {
            mAdvertiseCallback = advertiseCallback;
            mAdvertisement = advertiseData;
            mScanResponse = scanResponse;
            mSettings = settings;
            mBluetoothGatt = bluetoothGatt;
            mClientIf = 0;
        }
        public void startRegisteration() {
            synchronized (this) {
                if (mClientIf == -1) return;//這個就不解釋了
                try {
                    UUID uuid = UUID.randomUUID();
                    mBluetoothGatt.registerClient(new ParcelUuid(uuid), this);//註冊
                    wait(LE_CALLBACK_TIMEOUT_MILLIS);//等待2秒,在過程中會依次回調onClientRegistered和onMultiAdvertiseCallback
                } catch (InterruptedException | RemoteException e) {
                    Log.e(TAG, "Failed to start registeration", e);
                }

//註冊成功並且廣播成功,加入廣播緩存,以callback爲key的Hashmap,callback爲用戶自己定義的Callback

                if (mClientIf > 0 && mIsAdvertising) {
                    mLeAdvertisers.put(mAdvertiseCallback, this);
                } else if (mClientIf <= 0) {//註冊失敗
                    // Registration timeout, reset mClientIf to -1 so no subsequent operations can
                    // proceed.
                    if (mClientIf == 0) mClientIf = -1;
                    // Post internal error if registration failed.
                    postStartFailure(mAdvertiseCallback,
                            AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR);
                } else {//註冊成功但廣播開啓失敗
                    // Unregister application if it's already registered but advertise failed.
                    try {
                        mBluetoothGatt.unregisterClient(mClientIf);
                        mClientIf = -1;
                    } catch (RemoteException e) {
                        Log.e(TAG, "remote exception when unregistering", e);
                    }
                }
            }
        }
        public void stopAdvertising() {
            synchronized (this) {
                try {
                    mBluetoothGatt.stopMultiAdvertising(mClientIf);
                    wait(LE_CALLBACK_TIMEOUT_MILLIS);
                } catch (InterruptedException | RemoteException e) {
                    Log.e(TAG, "Failed to stop advertising", e);
                }
                // Advertise callback should have been removed from LeAdvertisers when
                // onMultiAdvertiseCallback was called. In case onMultiAdvertiseCallback is never
                // invoked and wait timeout expires, remove callback here.
                if (mLeAdvertisers.containsKey(mAdvertiseCallback)) {
                    mLeAdvertisers.remove(mAdvertiseCallback);
                }
            }
        }
        /**
         * Application interface registered - app is ready to go
         */
        @Override
        public void onClientRegistered(int status, int clientIf) {
            Log.d(TAG, "onClientRegistered() - status=" + status + " clientIf=" + clientIf);
            synchronized (this) {
                if (status == BluetoothGatt.GATT_SUCCESS) {
                    try {
                        if (mClientIf == -1) {//在2秒內未完成註冊,超時
                            // Registration succeeds after timeout, unregister client.
                            mBluetoothGatt.unregisterClient(clientIf);
                        } else {//完成註冊,並開始廣播
                            mClientIf = clientIf;
                            mBluetoothGatt.startMultiAdvertising(mClientIf, mAdvertisement,
                                    mScanResponse, mSettings);
                        }
                        return;
                    } catch (RemoteException e) {
                        Log.e(TAG, "failed to start advertising", e);
                    }
                }
                // Registration failed.
                mClientIf = -1;
                notifyAll();
            }
        }
        @Override
        public void onMultiAdvertiseCallback(int status, boolean isStart,
                AdvertiseSettings settings) {
            synchronized (this) {
                if (isStart) {//廣播成功時的回調
                    if (status == AdvertiseCallback.ADVERTISE_SUCCESS) {
                        // Start success
                        mIsAdvertising = true;
                        postStartSuccess(mAdvertiseCallback, settings);
                    } else {
                        // Start failure.
                        postStartFailure(mAdvertiseCallback, status);
                    }
                } else {//stop 時的回調,用來反註冊和清除緩存的callback
                    // unregister client for stop.
                    try {
                        mBluetoothGatt.unregisterClient(mClientIf);
                        mClientIf = -1;
                        mIsAdvertising = false;
                        mLeAdvertisers.remove(mAdvertiseCallback);
                    } catch (RemoteException e) {
                        Log.e(TAG, "remote exception when unregistering", e);
                    }
                }
                notifyAll();
            }
        }
    }
    private void postStartFailure(final AdvertiseCallback callback, final int error) {
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                callback.onStartFailure(error);
            }
        });
    }
    private void postStartSuccess(final AdvertiseCallback callback,
            final AdvertiseSettings settings) {
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                callback.onStartSuccess(settings);
            }
        });
    }


AdvertiseCallbackWrapper的成員變量mClientIf非常重要,在廣播發送和停止的過程中起着重要的作用。這裏先簡單的記住該屬性的以下特徵:

mClientIf=0——>未註冊

mClinetIf=-1——>廣播停止或註冊超時

mClientIf>0——>已註冊並且已經廣播成功

mClientIf默認值爲0

這時我們追蹤到startRegisteration這個方法了,該方法裏面調用了registerClient方法,經過IPC通信後會回調到onClientRegistered方法,繼續調用到了startMultiAdvertising方法,接着觸發onMultiAdvertiseCallback,成功發送廣播後,將該AdvertiseCallbackWrapper對象加入mLeAdvertisers。

這裏我們需要注意和了解以下幾點:

1.在調用startRegisteration的2秒的時間內,如果沒有註冊成功且廣播成功,這次廣播數據的行爲均爲失敗。

2.即使2秒之後onClientRegistered回調,也將視爲註冊未成功,並進行解註冊操作。


startAdvertising方法就到這,至於更底層的細節後續的文章會展開,下面我們看一下其對應的stopAdvertising方法

  /**
     * Stop Bluetooth LE advertising. The {@code callback} must be the same one use in
     * {@link BluetoothLeAdvertiser#startAdvertising}.
     * <p>
     * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
     *
     * @param callback {@link AdvertiseCallback} identifies the advertising instance to stop.
     */
    public void stopAdvertising(final AdvertiseCallback callback) {
        synchronized (mLeAdvertisers) {
            if (callback == null) {
                throw new IllegalArgumentException("callback cannot be null");
            }
            AdvertiseCallbackWrapper wrapper = mLeAdvertisers.get(callback);
            if (wrapper == null) return;
            wrapper.stopAdvertising();
        }
    }


大家可以看到,stopAdvertising方法內部是調用AdvertiseCallbackWrapper.stopAdvertising方法。這裏必須注意stopAdvertising方法的callback必須和start時傳入的callback參數是同一個。否則在mLeAdvertisers緩存裏是找不到相應的AdvertiseCallbackWrapper的實例的,就無法正常停止廣播。

轉載請註明:http://blog.csdn.net/android_jiangjun/article/details/77946857


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章