Android Studio 藍牙開發實例——基於Android 6.0

 

 

       因項目須要作一個Android 的藍牙app來經過手機藍牙傳輸數據以及控制飛行器,在此,我對這段時間裏寫的藍牙app的代碼進行知識梳理和出現錯誤的總結。android

       該應用的Compile Sdk Version 和targetSdkVersion均爲26,Min Sdk Version爲22,基於Android studio平臺開發。app

 1、聲明藍牙權限異步

       首先,要在新建項目中的AndroidManifest.xml中聲明兩個權限:BLUETOOTH權限和BLUETOOTH_ADMIN權限。其中,BLUETOOTH權限用於請求鏈接和傳送數據;BLUETOOTH_ADMIN權限用於啓動設備、發現或進行藍牙設置,若是要擁有該權限,必須現擁有BLUETOOTH權限。socket

       其次,由於android 6.0以後採用新的權限機制來保護用戶的隱私,若是咱們設置的targetSdkVersion大於或等於23,則須要另外添加ACCESS_COARSE_LOCATION和ACCESS_FINE_LOCATION權限,不然,可能會出現搜索不到藍牙設備的問題。ide

 

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission-sdk-23 android:name="android.permission.ACCESS_COARSE_LOCATION"/>

 


 2、 啓動和關閉藍牙post

1.首先,要獲取BluetoothAdapter藍牙適配器的對象,而後檢測設備是否支持藍牙。ui

 

BluetoothAdapter blueadapter = BluetoothAdapter.getDefaultAdapter();
//獲取藍牙適配器

 

if(blueadapter==bull)
//表示手機不支持藍牙
return;

 


2.啓動藍牙功能:isEnable()方法用來檢查藍牙當前狀態,若是方法返回false,則藍牙沒啓動。enable()方法用來打開本地藍牙適配器。

this

 if (!blueadapter.isEnabled())
        //判斷本機藍牙是否打開
        {//若是沒打開,則打開藍牙
        blueadapter.enable();
        }

 

3.使用disable()能夠關閉本地藍牙適配器。spa

 

3、發現藍牙設備線程

1.開啓當前藍牙的可見性
       Android 設備默認是不能被搜索的,若是想要本機設備可被搜索,能夠以BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE動做爲startActivity()方法的參數,這個方法會提交一個開啓藍牙可見的請求。默認的狀況下,設備在120秒內能夠被搜索,也能夠自定義一個間隔時間,可是規定的最大值爲300秒,0秒則表示設備能夠一直被搜索,自定義時間經過EXTRA_DISCOVERABLE_DURATION來定義,代碼以下。

if (blueadapter.getScanMode() != BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) //不在可被搜索的範圍
        {
        Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
        discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);//設置本機藍牙在300秒內可見
        startActivity(discoverableIntent);
        }

 


2.調用startDiscover()搜索藍牙

       開啓藍牙後,調用startDiscover()方法搜索藍牙,注意,只有開啓了藍牙可見性的設備纔會響應。該搜索過程爲異步操做,調用後講以廣播的機制返回搜索到的對象,搜索的過程通常爲12秒,搜索過程頁面會顯示搜索到的設備。

 

public void doDiscovry() {
    if (blueadapter.isDiscovering()) {
        //判斷藍牙是否正在掃描,若是是調用取消掃描方法;若是不是,則開始掃描
        blueadapter.cancelDiscovery();
    } else
        blueadapter.startDiscovery();

}

 


3.註冊廣播
       經過blueadapter.startDiscovery()來搜索藍牙設備,要獲取到搜索的結果須要註冊廣播。

 

       定義一個列表

public ArrayAdapter adapter;
ListView listView = (ListView) findViewById(R.id.list);//控件 列表

 

 

//定義一個列表,存藍牙設備的地址。
public ArrayList<String> arrayList=new ArrayList<>();
//定義一個列表,存藍牙設備地址,用於顯示。
public ArrayList<String> deviceName=new ArrayList<>();

 

 

       將搜索到的顯示在控件列表上

adapter = new ArrayAdapter(this, android.R.layout.simple_expandable_list_item_1, deviceName);
listView.setAdapter(adapter);

 

 

       定義廣播和處理廣播消息

IntentFilter intentFilter = new IntentFilter(BluetoothDevice.ACTION_FOUND);//註冊廣播接收信號
registerReceiver(bluetoothReceiver, intentFilter);//用BroadcastReceiver 來取得結果

private final BroadcastReceiver bluetoothReceiver = new BroadcastReceiver() {
    @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);
            deviceName.add("設備名:"+device.getName()+"\n" +"設備地址:"+device.getAddress() + "\n");//將搜索到的藍牙名稱和地址添加到列表。
            arrayList.add( device.getAddress());//將搜索到的藍牙地址添加到列表。
            adapter.notifyDataSetChanged();//更新
        }
    }
 };

 


       搜索完設備後,要記得註銷廣播。註冊後的廣播對象在其餘地方有強引用,若是不取消,activity會釋放不了資源 。

protected void onDestroy(){
    super.onDestroy();//解除註冊
    unregisterReceiver(bluetoothReceiver);
}



4.瞭解targetSdkVersion是否大於或等於23

       如果大於或等於23,除了添加了藍牙權限外,還要動態獲取位置權限,才能將搜索到的藍牙設備顯示出來。如果小於,則不須要動態獲取權限。
動態申請權限,網上例子以下。

 

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);

    if (requestCode == REQUEST_ENABLE_BT) {
        if (resultCode == RESULT_OK) {
            textView.setText("打開藍牙成功");
        }
        if (resultCode == RESULT_CANCELED) {
            textView.setText("放棄打開藍牙");
        }
    } else {
        textView.setText("藍牙異常");
    }
}

@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
    switch (requestCode) {
        case PERMISSION_REQUEST_COARSE_LOCATION:
            if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            }
            break;
    }
}

 

 

 4、配對藍牙設備

       藍牙的配對和鏈接有兩種方式。一種是每一個設備做爲一個客戶端去鏈接一個服務端,向對方發起鏈接。另外一種則是做爲服務端來接收客戶端發來鏈接的消息。藍牙之間的數據傳輸採用的是和TCP傳輸相似的傳輸機制。

1.做爲客戶端鏈接
       首先要獲取一個表明遠程設備BluetoothDevice的對象,而後使用該BluetoothDevice的對象來獲取一個BluetoothSocket對象。BluetoothSocket對象調用connect()能夠創建鏈接。

       藍牙鏈接整個過程須要在子線程中執行的,而且要將 scoket.connect()放在一個新的子線程中,由於若是將這個方法也放在同一個子線程中解決的話,就會永遠報錯read failed, socket might closed or timeout, read ret: -1;借鑑網上的方法:再開一個子線程專門執行socket.connect()方法,問題能夠解決;

       另外,借鑑網上方法和建議,在得到socket的時候 ,儘可能不使用uuid方式;由於這樣雖然可以獲取到socket 可是不能進行自動,因此使用的前提是已經配對了的設備鏈接;

       使用反射的方式,可以自動提示配對,也適合手機間通訊。

 

final BluetoothSocket socket = (BluetoothSocket) device.getClass().getDeclaredMethod("createRfcommSocket", new Class[]{int.class}).invoke(device, 1);

 


       代碼中的device須要把註冊廣播時的device做爲參數傳進線程中。注意,傳進來的device的值要爲遠程設備的地址,若不是或有出入,則可能會出現NullPointerException異常,並提示嘗試調用一個空的對象。爲了解決這個問題,能夠把顯示得到的device名字、地址和傳入線程的device的地址分在不一樣的集合類。傳入線程的device使用只有設備地址的集合類。

       在鏈接藍牙以前,還要先取消藍牙設備的掃描,不然容易鏈接失敗。

 

adapter.cancelDiscovery();//adapter爲獲取到的藍牙適配器
socket.connect();//鏈接

 

 

2.做爲服務端鏈接
       服務端接收鏈接須要使用BluetoothServerSocket類,它的做用是監聽進來的鏈接,在一個鏈接被接收以後,會返回一個BluetoothSocket對象,這個對象能夠用來和客戶端進行通訊。

       與客戶端同樣,服務端也要在子線程中實現。經過調用listenUsingRfcommWithServiceRecord(String,UUID)方法能夠獲得一個BluetoothServerSocket的對象,而後再用這個對象來調用accept()來返回一個BluetoothSocket對象。因爲accept()是個阻塞的方法,它會直到接收到一個鏈接或異常以後纔會返回,因此要放在子線程中。

 
bluetoothServerSocket=bluetoothAdapter.listenUsingRfcommWithServiceRecord(bluetoothAdapter.getDefaultAdapter().getName(), UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"));
//bluetoothServerSocket= (BluetoothServerSocket) bluetoothAdapter.getClass().getMethod("listenUsingRfcommOn",new Class[]{int.class}).invoke(bluetoothAdapter,10);
socket=bluetoothServerSocket.accept();//接收鏈接

 


       代碼中註釋掉的內容是經過反射的方式來接收,因爲我使用時出現了異常,因此暫時不考慮這個方法。

       還有,與TCP不一樣的是,這個鏈接時只容許一個客戶端鏈接,所以在BluetoothServerSocket對象接收到一個鏈接請求時就要馬上調用close()方法把服務端關閉。


5、客戶端發送數據

       當兩個設備成功鏈接以後,雙方都會有一個BluetoothSocket對象,這時,就能夠在設備之間傳送數據了。

       1.使用getOutputStream()方法來獲取輸出流來處理傳輸。

       2.調用write()。

os = socket.getOutputStream();//獲取輸出流
if (os != null) {//判斷輸出流是否爲空
    os.write(message.getBytes("UTF-8"));
}
os.flush();//將輸出流的數據強制提交
os.close();//關閉輸出流
}

 

       將輸出流中的數據提交後,要記得關閉輸出流,不然,可能會形成只能發送一次數據。

 

6、服務端接收數據

       1.使用getInputStream()方法來獲取輸入流來處理傳輸。

       2.調用read()。

 

   

InputStream im=null;
im=bluetoothSocket.getInputStream();
byte buf[] = new byte[1024];
if (is != null) {
    is.read(buf, 0, buf.length);//讀取發來的數據
    String message = new String(buf);//把發來的數據轉化爲String類型
    BuletoothMainActivity.UpdateRevMsg(message);//更新信息在顯示文本框
    is.close();//關閉輸入流

 

 

       使用服務端接收數據時,要先從客戶端向服務端發起鏈接,只有接收到鏈接請求以後,纔會返回一個BluetoothSocket對象。有BluetoothSocket對象才能獲取到輸入流。

 

       下面是將接收到數據顯示在界面的方法:

        在Activity中定義Handler類的對象handler。

public static void UpdateRevMsg(String revMsg) {
    mRevMsg=revMsg;
    handler.post(RefreshTextView);
}

private static Runnable RefreshTextView=new Runnable() {
    @Override
    public void run() {
        textView2.setText(mRevMsg);
    }
};
相關文章
相關標籤/搜索