Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android開發之藍牙通信

Android開發之藍牙通信

編輯:關於Android編程

藍牙開發必練基本功

藍牙權限

為了在您的應用程序中使用藍牙功能,您必須聲明藍牙權限藍牙。您需要此權限來執行任何藍牙通信,如請求一個連接、接受一個連接和傳輸數據。如果你想讓你的應用啟動設備發現或操縱藍牙設置,你也必須申報bluetooth_admin許可。大多數應用程序都需要此權限,僅用於發現本地藍牙設備的能力。此權限授予的其他權限不應被使用,除非應用程序是一個“電源管理器”,將修改用戶請求後的藍牙設置。注意:如果你使用bluetooth_admin許可,那麼你也必須有藍牙許可。


  
  ...
設置藍牙

在應用程序可以通過藍牙進行通信之前,您需要驗證是否支持藍牙設備上的支持,如果是這樣的話,請確保它已啟用。如果不支持藍牙,那麼您應該優雅地禁用任何藍牙功能。如果藍牙是支持的,但禁用,那麼你可以要求用戶啟用藍牙,而不會離開你的應用程序。這個設置是在兩個步驟來完成,使用藍牙適配器。

BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (mBluetoothAdapter == null) {
    // 設備不支持藍牙 
}

下一步,您需要確保藍牙功能已啟用。isenabled()檢查藍牙目前是否啟用。如果此方法返回錯誤,則禁用藍牙。要求藍牙啟用,startactivityforresult()與action_request_enable動作意圖。這將發出一個請求,使藍牙通過系統設置(不阻止您的應用程序)。例如:

if (!mBluetoothAdapter.isEnabled()) {
    Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
    startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}
查詢配對設備

使用藍牙適配器 BluetoothAdapter,您可以通過查詢名單發現遠程藍牙設備配對。
設備發現是一個掃描程序,搜索本地區域的藍牙功能的設備,然後要求一些信息(這是有時被稱為“發現”,“查詢”或“掃描”)然而,局域網內的藍牙設備將響應發現請求只有在能夠被發現。如果一個設備可以被發現,它會響應發現請求並共享一些信息,比如設備名稱、類別,並以其獨特的MAC地址。使用此信息,執行發現的設備可以選擇啟動一個連接到所發現的設備。

一旦一個連接是第一次與一個遠程設備,一個配對請求自動提交給用戶。當一個設備配對,該設備的基本信息(如設備名稱,類和地址)被保存,並可以讀取使用藍牙耳機。使用一個遠程設備的已知的mac地址,可以在任何時間啟動一個連接,而不進行發現(假設設備處於范圍內)。

記住,有一個配對和被連接之間的區別。要配對意味著兩個設備都知道彼此的存在,有一個共享的鏈路密鑰,可用於認證,並能夠建立一個加密的相互連接。要連接意味著目前的設備共享一個RFCOMM通道可以互相傳遞數據。目前安卓藍牙API的設備需要被配對在RFCOMM連接可以建立。(當您啟動與藍牙接口的加密連接時,自動執行配對)。

getBondedDevices獲取與本機藍牙所有綁定的遠程藍牙信息,以BluetoothDevice類實例返回,如果藍牙開啟,該函數會返回一個空集合

Set pairedDevices = mBluetoothAdapter.getBondedDevices();
// If there are paired devices
if (pairedDevices.size() > 0) {
    // Loop through paired devices
    for (BluetoothDevice device : pairedDevices) {
        // Add the name and address to an array adapter to show in a ListView
        mArrayAdapter.add(device.getName() + "\n" + device.getAddress());
    }
}
發現設備

開始發現設備,調用startdiscovery()。該進程是異步的,該方法將立即返回一個布爾值,指示是否已成功啟動。發現過程通常涉及一個約12秒的查詢掃描,然後通過一個頁面掃描每個發現的設備來檢索其藍牙名稱。

應用程序必須注冊ACTION_FOUND意圖接收有關每個設備發現BroadcastReceiver。我們通過注冊廣播接收器接受廣播,來處理一些事情:

// Create a BroadcastReceiver for ACTION_FOUND
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        // When discovery finds a device
        if (BluetoothDevice.ACTION_FOUND.equals(action)) {
            // Get the BluetoothDevice object from the Intent
            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
            // Add the name and address to an array adapter to show in a ListView
            mArrayAdapter.add(device.getName() + "\n" + device.getAddress());
        }
    }
};
// Register the BroadcastReceiver
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver(mReceiver, filter); // Don't forget to unregister during onDestroy

警告:執行設備發現是藍牙適配器的一個沉重的過程,並會消耗大量的資源。一旦你找到了一個設備連接,就調用canceldiscovery()取消之前的掃描。

發現設備的Intent意圖默認掃描時間120秒,一個應用程序可以設置最大持續時間為3600秒,0值表示設備總是發現。任何值低於0或高於3600自動設置為120秒)(可以這樣理解:藍牙設備運行在多少時間內可以被其他用於掃描到並連接)

Intent discoverableIntent = new
Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
startActivity(discoverableIntent);

關於藍牙與服務端、客戶端的鏈接涉及到BluetoothServerSocket 、BluetoothSocket ,沒啥好說的,稍後通過官方simple一觀即可。


藍牙低功耗基於Android4.3

相比較上面這塊,權限必須有以下兩項(4.3的低功耗藍牙的掃描startLeScan方法必須具備BLUETOOTH_ADMIN權限)


如果你確定你的應用程序是4.3以上的手機設備安裝,支持低功耗藍牙,可以在清單文件聲明如下

如果你不確定你的app使用的設備是否支持低功耗藍牙,但又想讓支持的設備使用低功耗藍牙,那麼你可以這樣做



if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
    Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show();
    finish();
}

或BluetoothAdapter可以通過BluetoothManager得到,因為在BluetoothManager被裝載時構造函數調用了BluetoothAdapter.getDefualtA..初始化了adapter對象


 // Initializes Bluetooth adapter.
final BluetoothManager bluetoothManager =
        (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bluetoothManager.getAdapter();

 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;
    }

藍牙設備的掃描也發生了變化,通過adapter.startScan stopScan控制掃描的開關,掃描的時間可以通過postDelayed設定掃描時間

/**
 * Activity for scanning and displaying available BLE devices.
 */
public class DeviceScanActivity extends ListActivity {

    private BluetoothAdapter mBluetoothAdapter;
    private boolean mScanning;
    private Handler mHandler;

    // Stops scanning after 10 seconds.
    private static final long SCAN_PERIOD = 10000;
    ...
    private void scanLeDevice(final boolean enable) {
        if (enable) {
            // Stops scanning after a pre-defined scan period.
            mHandler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    mScanning = false;
                    mBluetoothAdapter.stopLeScan(mLeScanCallback);
                }
            }, SCAN_PERIOD);

            mScanning = true;
            mBluetoothAdapter.startLeScan(mLeScanCallback);
        } else {
            mScanning = false;
            mBluetoothAdapter.stopLeScan(mLeScanCallback);
        }
        ...
    }
...
}

通過LeScanCallback 回調接口的實現,來處理返回結果刷新UI等方面功能的實現。

private LeDeviceListAdapter mLeDeviceListAdapter;
...
// Device scan callback.
private BluetoothAdapter.LeScanCallback mLeScanCallback =
        new BluetoothAdapter.LeScanCallback() {
    @Override
    public void onLeScan(final BluetoothDevice device, int rssi,
            byte[] scanRecord) {
        runOnUiThread(new Runnable() {
           @Override
           public void run() {
               mLeDeviceListAdapter.addDevice(device);
               mLeDeviceListAdapter.notifyDataSetChanged();
           }
       });
   }
};

BluetoothGatt BluetoothGattCallback等相關的Gat類都大同小異,根據google官方提供的simple過一遍即可。
藍牙開發調試如果你想使用adb命令可以參考https://developer.android.com/training/wearables/apps/bt-debugging.html


谷歌官方藍牙開源項目

通過官網找到三個開源項目,但是貌似都不能下載,只能通過github檢索google的倉庫了

BluetoothAdvertisements

BluetoothLeGatt

BluetoothChat

先看android-BluetoothAdvertisements運行效果圖

\

從BluetoothAdvertisements開源項目首先get到一點,剝離生命周期!相關代碼塊如下

public class ScannerFragment extends ListFragment {


    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setHasOptionsMenu(true);
        setRetainInstance(true);
        // 這裡傳入的不是Activity,而是通過Activity獲取Application,讓其不再跟隨活動Activity的生命周期
        mAdapter = new ScanResultAdapter(getActivity().getApplicationContext(),
                LayoutInflater.from(getActivity()));
        mHandler = new Handler();

    }

掃描代碼塊並沒有用BluetoothAdapter.startScan,經過源碼查看發現依然是過時方法,simple裡面直接通過adapter獲取BluetoothLeScanner,調用了scanner的startScan

public void startScanning() {
  // .........略............

  mScanCallback = new SampleScanCallback();
  mBluetoothLeScanner.startScan(buildScanFilters(), buildScanSettings(), mScanCallback);
}

然而你如果想下載完這個開源項目就想運行看效果,伙計,不得不說你是絕對不會成功的,buildScanFilters裡面添加了掃描過濾規則,uuid無法匹配你是看不到任何東西的,根據代碼提示注釋掉下面的過濾條件可以看到所有的藍牙設備。

  private List buildScanFilters() {
        List scanFilters = new ArrayList<>();

        ScanFilter.Builder builder = new ScanFilter.Builder();
        // 注釋掉下面的掃描過濾規則,才能掃描到(uuid不匹配沒法掃描到的)
       // builder.setServiceUuid(Constants.Service_UUID);
        //scanFilters.add(builder.build());

        return scanFilters;
    }

在低功耗模式下執行藍牙LE掃描。這是默認掃描模式,因為它消耗最小功率

  private ScanSettings buildScanSettings() {
        ScanSettings.Builder builder = new ScanSettings.Builder();
        builder.setScanMode(ScanSettings.SCAN_MODE_LOW_POWER);
        return builder.build();
    }

掃描的結果回調我們可以根據自己的需求做具現

  private class SampleScanCallback extends ScanCallback {

        @Override
        public void onBatchScanResults(List results) {
            super.onBatchScanResults(results);
            //批處理掃描結果
            for (ScanResult result : results) {
                mAdapter.add(result);
            }
            mAdapter.notifyDataSetChanged();
        }

        @Override
        public void onScanResult(int callbackType, ScanResult result) {
            super.onScanResult(callbackType, result);
            //掃描到一個立即回調
        }

        @Override
        public void onScanFailed(int errorCode) {
            super.onScanFailed(errorCode);
          //掃描失敗
        }
    }

掃描開始之前的邏輯判斷自行參考simple MainActivity.至於AdvertiserService相關的差不多的就略過啦、
android-BluetoothLeGatt這個simple個人覺得沒多少特別之處用到的核心上面都有提到,基於android4.3+版本的藍牙開發simple,mainfest可以看看,service個人感覺有點亂糟糟的。

再看BluetoothChat,首先需要讓藍牙被感知掃描鏈接,設置相應的模式和允許被掃描到的時長。

  /**
     * Makes this device discoverable.
     */
    private void ensureDiscoverable() {
        if (mBluetoothAdapter.getScanMode() !=
                BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
            Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
            discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
            startActivity(discoverableIntent);
        }
    }

藍牙適配器getRemoteDevice(address)後進行連接,核心代碼參考BluetoothChatService.ConnectThread線程部分。而數據的發送核心代碼只需要把發送的string message轉換byte[] ,調用ChatServiced的write方法

   /**
     * Write to the ConnectedThread in an unsynchronized manner
     *
     * @param out The bytes to write
     * @see ConnectedThread#write(byte[])
     */
    public void write(byte[] out) {
        // Create temporary object
        ConnectedThread r;
        // Synchronize a copy of the ConnectedThread
        synchronized (this) {
            if (mState != STATE_CONNECTED) return;
            r = mConnectedThread;
        }
        // Perform the write unsynchronized
        r.write(out);
    }

最後提供一個工具類BluetoothHelper.java (這個工具類整理的並不完善,關於掃描和鏈接兼容都沒寫進去,具體開發鏈接參照官方simple的ChartService,LE相關的掃描參照simple吧,兼容主要是api11 、api18LE 、api21)


/**
 * Created by idea on 2016/7/4.
 */
public class BluetoothHelper {

    private static BluetoothHelper instance;
    private BluetoothAdapter mBluetoothAdapter;
    private Activity mContext;

    /**
     * 獲取BluetoothHelper實例,采用Application剝離Activity生命周期
     * @param activity
     * @return BluetoothHelper
     */
    public static BluetoothHelper getInstance(Activity activity) {

        if (instance == null) {
            synchronized (BluetoothHelper.class) {
                if (instance == null) {
                    instance = new BluetoothHelper(activity);
                }
            }
        }

        return instance;
    }

    /**
     * 私有構造函數
     * @param activity
     * @hide
     */
    private BluetoothHelper(Activity activity) {
        this.mContext = activity;
        mBluetoothAdapter = getAdapter();
    }

    /***
     * 獲取BluetoothAdapter
     *
     * @return BluetoothAdapter
     * @hide
     */
    private BluetoothAdapter getAdapter() {
        BluetoothAdapter mBluetoothAdapter;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
            mBluetoothAdapter = ((BluetoothManager) mContext.getSystemService(Context.BLUETOOTH_SERVICE)).getAdapter();
        } else {
            mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
        }
        return mBluetoothAdapter;
    }

    /**
     * 獲取已經存在的adapter實例
     * @return
     */
    public BluetoothAdapter getBluetoothAdapter() {
        return mBluetoothAdapter;
    }

    /**
     * 判斷手機手否支持藍牙通信
     *
     * @return boolean
     */
    public boolean checkSupperBluetooth() {

        return mBluetoothAdapter == null;
    }

    /**
     * 檢查藍牙狀態是否打開可用
     *
     * @return boolean
     */
    public boolean checkBluetoothEnable() {

        return mBluetoothAdapter.isEnabled();
    }


    /**
     * 如果藍牙設備支持的情況下,並未打開藍牙,需要請求藍牙打開
     */
    public void requestOpenBluetooth() {
        Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
        mContext.startActivityForResult(enableBtIntent, Constants.REQUEST_ENABLE_BT);
    }

    /**
     * 判斷我們請求打開藍牙的結果
     *
     * @param requestCode
     * @param resultCode
     * @param onOpenBluetoothLisenter
     */
    public void performResult(int requestCode, int resultCode, OnBluetoothListener.OnOpenBluetoothLisenter onOpenBluetoothLisenter) {

        switch (requestCode) {
            case Constants.REQUEST_ENABLE_BT:

                if (onOpenBluetoothLisenter != null) {
                    if (resultCode == Activity.RESULT_OK) {

                        if (checkBluetoothEnable()) {
                            onOpenBluetoothLisenter.onSuccess();
                        } else {
                            onOpenBluetoothLisenter.onFail("藍牙不可用,會影響到相關功能的使用");
                        }

                    } else {
                        onOpenBluetoothLisenter.onFail("Error");
                    }
                }
        }
    }

    /***
     * 是否支持藍牙低功耗廣播(4.3+)
     *
     * @return boolean
     * @hide
     */
    @Deprecated
    public boolean isSupperBluetoothLE() {

        return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE);
    }


    /**
     * 判斷是否支持LE
     *
     * @return boolean
     * @hide
     */
    @Deprecated
    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public boolean isMultipleAdvertisementSupported() {
        return mBluetoothAdapter.isMultipleAdvertisementSupported();
    }

    /**
     * 檢測是否支持藍牙低功耗,檢查方式走版本分之
     *
     * @return boolean
     */
    public boolean checkSupperBluetoothLE() {
        final int version = Build.VERSION.SDK_INT;
        if (version >= Build.VERSION_CODES.LOLLIPOP) {
            return isMultipleAdvertisementSupported();
        } else {
            return isSupperBluetoothLE();
        }
    }

    /**
     * 允許藍牙在某段時間內被掃描鏈接到
     *
     * @param duration
     */
    private void ensureDiscoverable(int duration) {
        if (mBluetoothAdapter.getScanMode() != BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
            Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
            discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, duration);
            mContext.startActivity(discoverableIntent);
        }
    }

    /**
     * 基於APi21的掃描藍牙
     * @param mScanCallback
     */
    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public void startScanningApi21(ScanCallback mScanCallback){
        mBluetoothAdapter.getBluetoothLeScanner().startScan(buildScanFilters(), buildScanSettings(),mScanCallback);
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    private List buildScanFilters() {
        List scanFilters = new ArrayList<>();

        ScanFilter.Builder builder = new ScanFilter.Builder();
        // 注釋掉下面的掃描過濾規則,才能掃描到(uuid不匹配沒法掃描到的)
        // builder.setServiceUuid(Constants.Service_UUID);
        //scanFilters.add(builder.build());

        return scanFilters;
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    private ScanSettings buildScanSettings() {
        ScanSettings.Builder builder = new ScanSettings.Builder();
        builder.setScanMode(ScanSettings.SCAN_MODE_LOW_POWER);
        return builder.build();
    }

    /**
     * 注冊廣播用於接收掃描結果,Api11的方法需要用到
     */
    public void registerReceiver(){
        IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
        mContext.registerReceiver(receiver, filter);

        filter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
        mContext.registerReceiver(receiver, filter);
    }

    /**
     * 銷毀同步生命周期
     */
    protected void onDestroy() {
        if (mBluetoothAdapter != null) {
            mBluetoothAdapter.cancelDiscovery();
        }
        mContext.unregisterReceiver(receiver);
    }

    /**
     * Api11+都可以使用的藍顏掃描
     */
    private void doDiscovery() {

        if (mBluetoothAdapter.isDiscovering()) {
            mBluetoothAdapter.cancelDiscovery();
        }
        mBluetoothAdapter.startDiscovery();
    }


    /**
     * The BroadcastReceiver that listens for discovered devices and changes the title when
     * discovery is finished
     */
    private final BroadcastReceiver receiver = new BroadcastReceiver() {

        ArrayList results = new ArrayList<>();

        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (BluetoothDevice.ACTION_FOUND.equals(action)) {
                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                if (device.getBondState() != BluetoothDevice.BOND_BONDED) {
                    results.add(device);
                }
            } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {

            }
        }
    };
}

小結

梳理知識,掌握脈絡,萬變不離其宗。

參考資料

  • https://developer.android.com/guide/topics/connectivity/bluetooth.html
  • https://developer.android.com/guide/topics/connectivity/bluetooth-le.html
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved