Android:藍芽實現一對一聊天

哦豁技術發表於2019-06-10

前言

原來藍芽現在還分經典藍芽、低功耗藍芽和雙模藍芽,技術的發展真的超過個人的認知速度,不學習意味退步。本來寫著低功耗藍芽和智慧藍芽音響的互動,但寫到最後,因為藍芽音響還沒有做好,沒辦法給本文的結果做個保障,故最後改成藍芽聊天。藍芽聊天可能適合在搭飛機和高鐵這種沒有網路或者網路不好等特殊情況下使用。本文的Demo可以正常使用。

本文總體流程:發現藍芽->配對藍芽->連線藍芽->資料互動

在這個流程,主要是一些細節和異常的處理,如何更好的體現使用者體驗。

宣告許可權

在專案的配置檔案AndroidManifest.xml加入如下程式碼即可,讓APP具有藍芽訪問許可權和發現周邊藍芽許可權。

//使用藍芽需要該許可權
<uses-permission android:name="android.permission.BLUETOOTH"/>
//使用掃描和設定需要許可權
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
//Android 6.0以上宣告一下兩個許可權之一即可。宣告位置許可權,不然掃描或者發現藍芽功能用不了哦
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
複製程式碼

Android 6.0以上動態申請許可權位置許可權,否則預設是禁止的,無法獲取到藍芽列表。

/**
 * Android 6.0 動態申請授權定位資訊許可權,否則掃描藍芽列表為空
 */
private void requestPermissions() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        if (ContextCompat.checkSelfPermission(this,
                Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {

            if (ActivityCompat.shouldShowRequestPermissionRationale(this,
                    Manifest.permission.ACCESS_COARSE_LOCATION)) {
                Toast.makeText(this, "使用藍芽需要授權定位資訊", Toast.LENGTH_LONG).show();
            }
            //請求許可權
            ActivityCompat.requestPermissions(this,
                    new String[]{Manifest.permission.ACCESS_COARSE_LOCATION},
                    REQUEST_ACCESS_COARSE_LOCATION_PERMISSION);
        }
    }
}

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    if (requestCode == REQUEST_ACCESS_COARSE_LOCATION_PERMISSION) {
        if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            //使用者授權
        } else {
            finish();
        }

    }

    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
複製程式碼

這裡處理授權的使用者體驗比較不好,授權則繼續,不授權則退出。可以根據自己的需求對授權邏輯另外處理。

裝置是否支援藍芽

並不是所有的Android 裝置都支援藍芽,所以在使用之前,檢測當前裝置是否支援藍芽。

    private void isSupportBluetooth() {
        bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();

        if (bluetoothAdapter == null
                || !getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)) {
            showNotSupportBluetoothDialog();
            Log.e(TAG, "not support bluetooth");
        } else {
            Log.e(TAG, " support bluetooth");
        }
    }
複製程式碼

類BluetoothAdapter代表著當前裝置的藍芽,可以通過BluetoothAdapter獲取所有已繫結的藍芽,掃描藍芽,自身的名字和地址,通過地址可以獲取周邊藍芽裝置。可以通過兩種方式獲得BluetoothAdapter物件。

//方式一
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
//方式二
BluetoothManager manager= (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter= manager.getAdapter();
複製程式碼

開啟藍芽

先檢測藍芽是否開啟。沒有開啟則提示使用者開啟,或者直接開啟。

    //提示使用者開啟藍芽,會有彈出框讓使用者選擇是否同意開啟
    private void enableBLE() {
        if (!bluetoothAdapter.isEnabled()) {
            Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            startActivityForResult(intent, REQUEST_BLE_OPEN);
        }
    }
    
        @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        if (requestCode == 1) {
            if (resultCode == RESULT_OK) {
                scanBle();
            }
        }
    }
    
    //直接設定程式碼開啟
   private void enableBLE() {
        if (!bluetoothAdapter.isEnabled()) {
            bluetoothAdapter.enable();
        }
    }
複製程式碼

設定可被發現

藍芽開啟後,要將自身設定為可以被周邊藍芽搜尋到,以便可以進行下一步操作。藍芽預設可被周邊裝置在120秒內搜尋到,最長設定不過300秒。聽說設定0可以永久被搜尋哦。

    /**
     * 設定藍芽可以被其他裝置搜尋到
     */
    private void beDiscovered() {
        if (mBluetoothAdapter.getScanMode() != BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
            Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
            discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 0);
            startActivity(discoverableIntent);
        }
    }
複製程式碼

收集周邊藍芽裝置

通過註冊廣播監聽,對發現的藍芽裝置新增到集合中,在listview進行展示。

    private void registerBluetoothReceiver() {
        IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
        filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
        registerReceiver(bluetoothReceiver, filter);
    }
    
    BroadcastReceiver bluetoothReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();

            if (action.equals(BluetoothDevice.ACTION_FOUND)) {
                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                list.add(device);
                Log.e(TAG, "discovery:" + device.getName());
                bleAdapter.notifyDataSetChanged();
            } else if (action.equals(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)) {
                // Toast.makeText(MainActivity.this, "discovery finished", Toast.LENGTH_LONG).show();
                btnSearch.setText("重新搜尋");
                mBluetoothAdapter.cancelDiscovery();
            }
        }
    };
複製程式碼

搜尋結果如下,可以看到,部分藍芽裝置只有Mac地址,沒有名稱。這裡沒有進一步處理,如果需要處理,參考藍芽名為空處理方案

搜尋到周邊裝置

裝置詳情

獲取藍芽列表之後,點選對應的藍芽,進入藍芽詳情。一個BluetoothDeviced物件代表著一個周邊藍芽裝置,通過該物件,可以獲取該藍芽裝置的名稱,繫結狀態,Mac地址,uuid等。

未配對藍芽
未配對藍芽裝置通過mDevice.createBond();進行配對。

聊天框實現

配對成功後,通過BluetoothDevice的createRfcommSocketToServiceRecord()方法和藍芽裝置連線,連線成功會返回BluetoothSocket物件,進而獲得輸入輸出流,進行資料互動。

mSocket = device.createRfcommSocketToServiceRecord(UUID.fromString(uuid));

 mSocket.connect();
 
 mOutputStream = mSocket.getOutputStream();

mInputStream = mSocket.getInputStream();
複製程式碼

連線過程會堵塞執行緒,請在子執行緒執行。uuid和服務端的一致且唯一就可以(這裡自己定義),通過uuid和服務端繫結BluetoothSocket。我們將輸入輸出的內容同步到聊天框,就達到了聊天效果。

服務端的BluetoothSocket物件的實現和此類似,具體的話可以看原始碼哦。

聊天框

總結

本文簡單描述藍芽連線到實現聊天框的流程,原始碼可開啟GitHub下載執行,有用就star一下。

由於BluetoothSocket的關閉或者讀寫異常,還有一些未能同步到,各位客官根據自己需要進行處理。另外一些耗時的操作,例如連線藍芽,沒有做進度條反饋,可以根據自己需求進行定製。Demo可以正常食用。

點個贊,老鐵

如果覺得文章有用,給文章點個贊,鐵子

相關文章