Android 藍牙(一)藍牙通信

1 藍牙基本操作

隨著可穿戴設(shè)備的流行,研究藍牙是必不可少的一門技術(shù)了。
總結(jié)了下藍牙開發(fā)使用的一些東西分享一下。
藍牙權(quán)限
首先需要AndroidManifest.xml文件中添加操作藍牙的權(quán)限。

<uses-permissionandroid:name="android.permission.BLUETOOTH" />
//允許程序連接到已配對的藍牙設(shè)備。
<uses-permissionandroid:name="android.permission.BLUETOOTH_ADMIN" />
//允許程序發(fā)現(xiàn)和配對藍牙設(shè)備。

BluetoothAdapter
操作藍牙主要用到的類 BluetoothAdapter類,使用時導(dǎo)包import android.bluetooth.BluetoothAdapter;
源碼具體位置:frameworks/base/core/Java/android/bluetooth/BluetoothAdapter.javaBluetoothAdapter

代表本地設(shè)備的藍牙適配器。該BluetoothAdapter可以執(zhí)行基本的藍牙任務(wù),例如啟動設(shè)備發(fā)現(xiàn),查詢配對的設(shè)備列表,使用已知的MAC地址實例化一個BluetoothDevice類,并創(chuàng)建一個BluetoothServerSocket監(jiān)聽來自其他設(shè)備的連接請求。

獲取藍牙適配器

BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();

開啟藍牙

 if(!mBluetoothAdapter.isEnabled()){  
//彈出對話框提示用戶是后打開  
Intent enabler = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);  
startActivityForResult(enabler, REQUEST_ENABLE);  
      //不做提示,直接打開,不建議用下面的方法,有的手機會有問題。  
      // mBluetoothAdapter.enable();  
}

獲取本地藍牙信息

//獲取本機藍牙名稱  
String name = mBluetoothAdapter.getName();  
//獲取本機藍牙地址  
String address = mBluetoothAdapter.getAddress();  
Log.d(TAG,"bluetooth name ="+name+" address ="+address);  
//獲取已配對藍牙設(shè)備  
Set<BluetoothDevice> devices = mBluetoothAdapter.getBondedDevices();  
Log.d(TAG, "bonded device size ="+devices.size());  
for(BluetoothDevice bonddevice:devices){  
    Log.d(TAG, "bonded device name ="+bonddevice.getName()+" address"+bonddevice.getAddress());  
}

搜索設(shè)備
mBluetoothAdapter.startDiscovery();
停止搜索
mBluetoothAdapter.cancelDiscovery();

監(jiān)聽掃描結(jié)果
搜索藍牙設(shè)備,該過程是異步的,通過下面注冊廣播接受者,可以監(jiān)聽是否搜到設(shè)備。

IntentFilter filter = new IntentFilter();  
//發(fā)現(xiàn)設(shè)備  
filter.addAction(BluetoothDevice.ACTION_FOUND);  
//設(shè)備連接狀態(tài)改變  
filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);  
//藍牙設(shè)備狀態(tài)改變  
filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);  
registerReceiver(mBluetoothReceiver, filter);

通過廣播接收者查看掃描到的藍牙設(shè)備,每掃描到一個設(shè)備,系統(tǒng)都會發(fā)送此廣播(BluetoothDevice.ACTION_FOUNDE)。其中參數(shù)intent可以獲取藍牙設(shè)備BluetoothDevice。
該demo中是連接指定名稱的藍牙設(shè)備,BLUETOOTH_NAME為"Galaxy Nexus",如果掃描不到,記得改這個藍牙名稱。

private BroadcastReceiver mBluetoothReceiver = new BroadcastReceiver(){  
        @Override  
        public void onReceive(Context context, Intent intent) {  
            String action = intent.getAction();  
            Log.d(TAG,"mBluetoothReceiver action ="+action);  
            if(BluetoothDevice.ACTION_FOUND.equals(action)){//每掃描到一個設(shè)備,系統(tǒng)都會發(fā)送此廣播。  
                //獲取藍牙設(shè)備  
                BluetoothDevice scanDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);  
                if(scanDevice == null || scanDevice.getName() == null) return;  
                Log.d(TAG, "name="+scanDevice.getName()+"address="+scanDevice.getAddress());  
                //藍牙設(shè)備名稱  
                String name = scanDevice.getName();  
                if(name != null && name.equals(BLUETOOTH_NAME)){  
                    mBluetoothAdapter.cancelDiscovery();  
                    //取消掃描  
                    mProgressDialog.setTitle(getResources().getString(R.string.progress_connecting));                   //連接到設(shè)備。  
                    mBlthChatUtil.connect(scanDevice);  
                }  
            }else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)){  
  
            }  
        }            
};

設(shè)置藍牙可見性
有時候掃描不到某設(shè)備,這是因為該設(shè)備對外不可見或者距離遠,需要設(shè)備該藍牙可見,這樣該才能被搜索到。
可見時間默認(rèn)值為120s,最多可設(shè)置300。

if (mBluetoothAdapter.isEnabled()) {  
    if (mBluetoothAdapter.getScanMode() !=   
            BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {  
        Intent discoverableIntent = new Intent(  
                BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);  
        discoverableIntent.putExtra(  
                BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 120);  
        startActivity(discoverableIntent);  
    }  
}

2 服務(wù)端

android 藍牙之間可以通過SDP協(xié)議建立連接進行通信,通信方式類似于平常使用socket。
首先創(chuàng)建BluetoothServerSocket ,BluetoothAdapter中提供了兩種創(chuàng)建BluetoothServerSocket 方式,如下圖所示為創(chuàng)建安全的RFCOMM Bluetooth socket,該連接是安全的需要進行配對。而通過listenUsingInsecureRfcommWithServiceRecord創(chuàng)建的RFCOMM Bluetooth socket是不安全的,連接時不需要進行配對。
其中的uuid需要服務(wù)器端和客戶端進行統(tǒng)一。

private class AcceptThread extends Thread {  
        // 本地服務(wù)器套接字  
        private final BluetoothServerSocket mServerSocket;  
        public AcceptThread() {           
            BluetoothServerSocket tmp = null;  
            // 創(chuàng)建一個新的偵聽服務(wù)器套接字  
            try {  
                tmp = mAdapter.listenUsingRfcommWithServiceRecord(  
                        SERVICE_NAME, SERVICE_UUID);  
                //tmp = mAdapter.listenUsingInsecureRfcommWithServiceRecord(SERVICE_NAME, SERVICE_UUID);  
            } catch (IOException e) {  
                Log.e(TAG, "listen() failed", e);  
            }  
            mServerSocket = tmp;  
        }  
  
        public void run() {  
            BluetoothSocket socket = null;  
            // 循環(huán),直到連接成功  
            while (mState != STATE_CONNECTED) {  
                try {  
                    // 這是一個阻塞調(diào)用 返回成功的連接  
                    // mServerSocket.close()在另一個線程中調(diào)用,可以中止該阻塞  
                    socket = mServerSocket.accept();  
                } catch (IOException e) {  
                    Log.e(TAG, "accept() failed", e);  
                    break;  
                }  
                // 如果連接被接受  
                if (socket != null) {  
                    synchronized (BluetoothChatUtil.this) {  
                        switch (mState) {  
                        case STATE_LISTEN:  
                        case STATE_CONNECTING:  
                            // 正常情況。啟動ConnectedThread。  
                            connected(socket, socket.getRemoteDevice());  
                            break;  
                        case STATE_NONE:  
                        case STATE_CONNECTED:  
                            // 沒有準(zhǔn)備或已連接。新連接終止。  
                            try {  
                                socket.close();  
                            } catch (IOException e) {  
                                Log.e(TAG, "Could not close unwanted socket", e);  
                            }  
                            break;  
                        }  
                    }  
                }  
            }  
            if (D) Log.i(TAG, "END mAcceptThread");  
        }  
  
        public void cancel() {  
            if (D) Log.d(TAG, "cancel " + this);  
            try {  
                mServerSocket.close();  
            } catch (IOException e) {  
                Log.e(TAG, "close() of server failed", e);  
            }  
        }  
}

mServerSocket通過accept()等待客戶端的連接(阻塞),直到連接成功或失敗。


3 客戶端

客戶端主要用來創(chuàng)建RFCOMM socket,并連接服務(wù)端。
先掃描周圍的藍牙設(shè)備,如果掃描到指定設(shè)備則進行連接。mBlthChatUtil.connect(scanDevice)連接到設(shè)備,
連接過程主要在ConnectThread線程中進行,先創(chuàng)建socket,方式有兩種,
如下代碼中是安全的(createRfcommSocketToServiceRecord)。另一種不安全連接對應(yīng)的函數(shù)是createInsecureRfcommSocketToServiceRecord。

private class ConnectThread extends Thread {  
        private BluetoothSocket mmSocket;  
        private final BluetoothDevice mmDevice;  
        public ConnectThread(BluetoothDevice device) {  
            mmDevice = device;  
            BluetoothSocket tmp = null;  
            // 得到一個bluetoothsocket  
            try {  
                mmSocket = device.createRfcommSocketToServiceRecord  
                        (SERVICE_UUID);  
            } catch (IOException e) {  
                Log.e(TAG, "create() failed", e);  
                mmSocket = null;  
            }  
        }  
  
        public void run() {  
            Log.i(TAG, "BEGIN mConnectThread");  
            try {   
                // socket 連接,該調(diào)用會阻塞,直到連接成功或失敗  
                mmSocket.connect();  
            } catch (IOException e) {  
                connectionFailed();  
                try {//關(guān)閉這個socket  
                    mmSocket.close();  
                } catch (IOException e2) {  
                    e2.printStackTrace();  
                }  
                return;  
            }  
            // 啟動連接線程  
            connected(mmSocket, mmDevice);  
        }  
  
        public void cancel() {  
            try {  
                mmSocket.close();  
            } catch (IOException e) {  
                Log.e(TAG, "close() of connect socket failed", e);  
            }  
        }  
}

接著客戶端socket主動連接服務(wù)端。連接過程中會自動進行配對,需要雙方同意才可以連接成功。


4 數(shù)據(jù)傳輸

客戶端與服務(wù)端連接成功后都會調(diào)用connected(mmSocket, mmDevice),創(chuàng)建一個ConnectedThread線程()。
該線程主要用來接收和發(fā)送數(shù)據(jù)。客戶端和服務(wù)端處理方式一樣。該線程通過socket獲得輸入輸出流。
private InputStream mmInStream = socket.getInputStream();
private OutputStream mmOutStream =socket.getOutputStream();
發(fā)送數(shù)據(jù)

public void write(byte[] buffer) {  
    try {  
        mmOutStream.write(buffer);  
        // 分享發(fā)送的信息到Activity  
        mHandler.obtainMessage(MESSAGE_WRITE, -1, -1, buffer)  
                .sendToTarget();  
    } catch (IOException e) {  
        Log.e(TAG, "Exception during write", e);  
    }  
}

接收數(shù)據(jù)
線程循環(huán)進行接收數(shù)據(jù)。

public void run() {  
    // 監(jiān)聽輸入流  
    while (true) {  
        try {  
            byte[] buffer = new byte[1024];  
            // 讀取輸入流  
            int bytes = mmInStream.read(buffer);  
            // 發(fā)送獲得的字節(jié)的ui activity  
            Message msg = mHandler.obtainMessage(MESSAGE_READ);  
            Bundle bundle = new Bundle();  
            bundle.putByteArray(READ_MSG, buffer);  
            msg.setData(bundle);  
            mHandler.sendMessage(msg);            
        } catch (IOException e) {  
            Log.e(TAG, "disconnected", e);  
                connectionLost();  
                break;  
            }  
        }  
}

DEMO下載:http://www.demodashi.com/demo/10676.html

歡迎大家關(guān)注、評論、點贊。
你們的支持是我堅持的動力。

歡迎關(guān)注我的微信公眾號

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容