Android:藍牙實現一對一聊天

前言

原來藍牙如今還分經典藍牙、低功耗藍牙和雙模藍牙,技術的發展真的超過我的的認知速度,不學習意味退步。原本寫着低功耗藍牙和智能藍牙音箱的交互,但寫到最後,由於藍牙音箱尚未作好,沒辦法給本文的結果作個保障,故最後改爲藍牙聊天。藍牙聊天可能適合在搭飛機和高鐵這種沒有網絡或者網絡很差等特殊狀況下使用。本文的Demo能夠正常使用。android

本文整體流程:發現藍牙->配對藍牙->鏈接藍牙->數據交互git

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

聲明權限

在項目的配置文件AndroidManifest.xml加入以下代碼便可,讓APP具備藍牙訪問權限和發現周邊藍牙權限。bash

//使用藍牙須要該權限
<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);
}
複製代碼

這裏處理受權的用戶體驗比較很差,受權則繼續,不受權則退出。能夠根據本身的需求對受權邏輯另外處理。ide

設備是否支持藍牙

並非全部的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對象。ui

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

開啓藍牙

先檢測藍牙是否打開。沒有打開則提示用戶打開,或者直接打開。this

//提示用戶開啓藍牙,會有彈出框讓用戶選擇是否贊成打開
    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能夠永久被搜索哦。spa

/**
     * 設置藍牙能夠被其餘設備搜索到
     */
    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能夠正常食用。

點個贊,老鐵

若是以爲文章有用,給文章點個贊,鐵子
相關文章
相關標籤/搜索