Android實(shí)現(xiàn)藍(lán)牙聊天功能
藍(lán)牙,時(shí)下最流行的智能設(shè)備傳輸數(shù)據(jù)的方式之一,通過手機(jī)app和智能設(shè)備進(jìn)行連接,獲取設(shè)備上的測量數(shù)據(jù),我們生活中隨處可見的比如藍(lán)牙智能手環(huán),藍(lán)牙電子秤,藍(lán)牙心電測量設(shè)備等等。
本篇我將緊接著上篇結(jié)尾所寫,一起來看下手機(jī)之間如何通過藍(lán)牙實(shí)現(xiàn)文字聊天。
先貼出上篇的一些demo;
當(dāng)點(diǎn)擊圖上的兩個(gè)列表中的任何一個(gè)列表,執(zhí)行如下代碼:
mBtAdapter.cancelDiscovery(); String info = ((TextView) v).getText().toString(); String address = info.substring(info.length() - 17); Intent intent = new Intent(); intent.putExtra(EXTRA_DEVICE_ADDRESS, address); setResult(Activity.RESULT_OK, intent); finish();
此藍(lán)牙聊天工具最后實(shí)現(xiàn)的效果是這樣的:
將回到聊天主界面:
public void onActivityResult(int requestCode, int resultCode, Intent data) { LogUtils.getInstance().e(getClass(), "onActivityResult " + resultCode); switch (requestCode) { case REQUEST_CONNECT_DEVICE: // 當(dāng)DeviceListActivity返回與設(shè)備連接的消息 if (resultCode == Activity.RESULT_OK) { // 連接設(shè)備的MAC地址 String address = data.getExtras().getString( DeviceListActivity.EXTRA_DEVICE_ADDRESS); // 得到藍(lán)牙對象 BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address); // 開始連接設(shè)備 mChatService.connect(device); } break; case REQUEST_ENABLE_BT: // 判斷藍(lán)牙是否啟用 if (resultCode == Activity.RESULT_OK) { // 建立連接 setupChat(); } else { LogUtils.getInstance().e(getClass(), "藍(lán)牙未啟用"); Toast.makeText(this, R.string.bt_not_enabled_leaving, Toast.LENGTH_SHORT).show(); finish(); } } }
在此,我將重點(diǎn)介紹下BluetoothChatService類中的連接流程;
因?yàn)樗{(lán)牙聊天是兩個(gè)手機(jī)之間進(jìn)行通訊,所以他們互為主機(jī)和從機(jī),主要思路以及步驟如下:
1.開一個(gè)線程獲取socket去連接藍(lán)牙;
2.開一個(gè)線程獲監(jiān)聽藍(lán)牙傳入的連接,如果連接被接受的話,再開啟第三個(gè)線程去處理所有傳入和傳出的數(shù)據(jù);
public synchronized void connect(BluetoothDevice device) { if (mState == STATE_CONNECTING) { if (mConnectThread != null) { mConnectThread.cancel(); mConnectThread = null; } } if (mConnectedThread != null) { mConnectedThread.cancel(); mConnectedThread = null; } mConnectThread = new ConnectThread(device); mConnectThread.start(); setState(STATE_CONNECTING); }
開線程去連接
/** * @description:藍(lán)牙連接線程 * @author:zzq * @time: 2016-8-6 下午1:18:41 */ private class ConnectThread extends Thread { private final BluetoothSocket mmSocket; private final BluetoothDevice mmDevice; public ConnectThread(BluetoothDevice device) { mmDevice = device; BluetoothSocket tmp = null; try { tmp = device.createRfcommSocketToServiceRecord(MY_UUID); } catch (IOException e) { LogUtils.getInstance().e(getClass(), "socket獲取失敗:" + e); } mmSocket = tmp; } public void run() { LogUtils.getInstance().e(getClass(), "開始mConnectThread"); setName("ConnectThread"); // mAdapter.cancelDiscovery(); try { mmSocket.connect(); } catch (IOException e) { // 連接失敗,更新ui connectionFailed(); try { mmSocket.close(); } catch (IOException e2) { LogUtils.getInstance().e(getClass(), "關(guān)閉連接失敗" + e2); } // 開啟聊天接收線程 startChat(); return; } synchronized (BluetoothChatService.this) { mConnectThread = null; } connected(mmSocket, mmDevice); } public void cancel() { try { mmSocket.close(); } catch (IOException e) { LogUtils.getInstance().e(getClass(), "關(guān)閉連接失敗" + e); } } }
/** * 監(jiān)聽傳入的連接 */ private class AcceptThread extends Thread { private final BluetoothServerSocket mmServerSocket; public AcceptThread() { BluetoothServerSocket tmp = null; try { tmp = mAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID); } catch (IOException e) { LogUtils.getInstance().e(getClass(), "--獲取socket失敗:" + e); } mmServerSocket = tmp; } public void run() { setName("AcceptThread"); BluetoothSocket socket = null; while (mState != STATE_CONNECTED) { LogUtils.getInstance().e(getClass(), "----accept-循環(huán)執(zhí)行中-"); try { socket = mmServerSocket.accept(); } catch (IOException e) { LogUtils.getInstance().e(getClass(), "accept() 失敗" + e); break; } // 如果連接被接受 if (socket != null) { synchronized (BluetoothChatService.this) { switch (mState) { case STATE_LISTEN: case STATE_CONNECTING: // 開始連接線程 connected(socket, socket.getRemoteDevice()); break; case STATE_NONE: case STATE_CONNECTED: // 沒有準(zhǔn)備好或已經(jīng)連接 try { socket.close(); } catch (IOException e) { LogUtils.getInstance().e(getClass(),"不能關(guān)閉這些連接" + e); } break; } } } } LogUtils.getInstance().e(getClass(), "結(jié)束mAcceptThread"); } public void cancel() { LogUtils.getInstance().e(getClass(), "取消 " + this); try { mmServerSocket.close(); } catch (IOException e) { LogUtils.getInstance().e(getClass(), "關(guān)閉失敗" + e); } } } /** * 連接成功后的線程 處理所有傳入和傳出的傳輸 */ private class ConnectedThread extends Thread { private final BluetoothSocket mmSocket; private final InputStream mmInStream; private final OutputStream mmOutStream; public ConnectedThread(BluetoothSocket socket) { mmSocket = socket; InputStream tmpIn = null; OutputStream tmpOut = null; // 得到BluetoothSocket輸入和輸出流 try { tmpIn = socket.getInputStream(); tmpOut = socket.getOutputStream(); } catch (IOException e) { LogUtils.getInstance().e(getClass(),"temp sockets not created" + e); } mmInStream = tmpIn; mmOutStream = tmpOut; } public void run() { int bytes; String str1 = ""; // 循環(huán)監(jiān)聽消息 while (true) { try { byte[] buffer = new byte[256]; bytes = mmInStream.read(buffer); String readStr = new String(buffer, 0, bytes);// 字節(jié)數(shù)組直接轉(zhuǎn)換成字符串 String str = bytes2HexString(buffer).replaceAll("00", "").trim(); if (bytes > 0) {// 將讀取到的消息發(fā)到主線程 mHandler.obtainMessage(BluetoothChatActivity.MESSAGE_READ, bytes, -1,buffer).sendToTarget(); } else { LogUtils.getInstance().e(getClass(),"disconnected"); connectionLost(); if (mState != STATE_NONE) { LogUtils.getInstance().e(getClass(), "disconnected"); startChat(); } break; } } catch (IOException e) { LogUtils.getInstance().e(getClass(), "disconnected" + e); connectionLost(); if (mState != STATE_NONE) { // 在重新啟動(dòng)監(jiān)聽模式啟動(dòng)該服務(wù) startChat(); } break; } } } /** * 寫入OutStream連接 * * @param buffer * 要寫的字節(jié) */ public void write(byte[] buffer) { try { mmOutStream.write(buffer); // 把消息傳給UI mHandler.obtainMessage(BluetoothChatActivity.MESSAGE_WRITE, -1,-1, buffer).sendToTarget(); } catch (IOException e) { LogUtils.getInstance().e(getClass(), "Exception during write:" + e); } } public void cancel() { try { mmSocket.close(); } catch (IOException e) { LogUtils.getInstance().e(getClass(),"close() of connect socket failed:" + e); } } }
大概的流程就是上面三個(gè)線程里面所展現(xiàn)的,當(dāng)然具體情況,根據(jù)項(xiàng)目來,比如藍(lán)牙協(xié)議協(xié)議解析這塊的根據(jù)協(xié)議定義的方式來進(jìn)行解析;
代碼中牽扯的到的藍(lán)牙連接狀態(tài)的改變,用到的handle,直接把狀態(tài)發(fā)送至activity,通知activity更新;
/** * 無法連接,通知Activity */ private void connectionFailed() { setState(STATE_LISTEN); Message msg = mHandler.obtainMessage(BluetoothChatActivity.MESSAGE_TOAST); Bundle bundle = new Bundle(); bundle.putString(BluetoothChatActivity.TOAST, "無法連接設(shè)備"); msg.setData(bundle); mHandler.sendMessage(msg); } /** * 設(shè)備斷開連接,通知Activity */ private void connectionLost() { Message msg = mHandler.obtainMessage(BluetoothChatActivity.MESSAGE_TOAST); Bundle bundle = new Bundle(); bundle.putString(BluetoothChatActivity.TOAST, "設(shè)備斷開連接"); msg.setData(bundle); mHandler.sendMessage(msg); }
當(dāng)點(diǎn)擊發(fā)送按鈕時(shí),將文本輸入框中的文字發(fā)送數(shù)據(jù)的方法:
private void sendMessage(String message) { if (mChatService.getState() != BluetoothChatService.STATE_CONNECTED) { Toast.makeText(this, R.string.not_connected,Toast.LENGTH_SHORT).show(); return; } if (message.length() > 0) { byte[] send = message.getBytes(); mChatService.write(send); } } //調(diào)用BluetoothChatService類中的write進(jìn)行數(shù)據(jù)發(fā)送 public void write(byte[] out) { ConnectedThread r; synchronized (this) { if (mState != STATE_CONNECTED) return; r = mConnectedThread; } r.write(out); }
如此,藍(lán)牙聊天的流程就是這樣,如果退出聊天的時(shí)候,停止所有線程;
public synchronized void stop() { LogUtils.getInstance().e(getClass(), "---stop()"); setState(STATE_NONE); if (mConnectThread != null) { mConnectThread.cancel(); mConnectThread = null; } if (mConnectedThread != null) { mConnectedThread.cancel(); mConnectedThread = null; } if (mAcceptThread != null) { mAcceptThread.cancel(); mAcceptThread = null; } }
相信看完本篇文章,在安卓藍(lán)牙連接這塊應(yīng)該問題不大了(spp協(xié)議)。
源碼地址:點(diǎn)我查看源碼
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android開發(fā)實(shí)現(xiàn)長按返回鍵彈出關(guān)機(jī)框功能
這篇文章主要介紹了Android開發(fā)實(shí)現(xiàn)長按返回鍵彈出關(guān)機(jī)框功能,涉及Android針對長按事件的響應(yīng)與處理相關(guān)操作技巧,需要的朋友可以參考下2017-09-09Android實(shí)現(xiàn)Unity3D下RTMP推送的示例
像Unity3D下的RTMP或RTSP播放器一樣,好多開發(fā)者苦于在Unity環(huán)境下,如何高效率低延遲的把數(shù)據(jù)采集并編碼實(shí)時(shí)推送到流媒體服務(wù)器,實(shí)現(xiàn)Unity場景下的低延遲推拉流方案。本文介紹幾種RTMP推送的方案2021-06-06Android ListView 實(shí)例講解清晰易懂
這篇文章主要通過實(shí)例介紹了Android ListView,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-09-09Android實(shí)現(xiàn)圓形圖片或者圓角圖片
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)圓形圖片或者圓角圖片的代碼,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-06-06關(guān)于Android Studio封裝SDK的那些事兒
這篇文章主要給大家介紹了關(guān)于Android Studio封裝SDK的那些事兒,文中通過圖文以及示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-09-09詳解Kotlin Android開發(fā)中的環(huán)境配置
這篇文章主要介紹了詳解Kotlin Android開發(fā)中的環(huán)境配置的相關(guān)資料,需要的朋友可以參考下2017-06-06