淺談藍(lán)牙通信流程——(新手上路)

? ??話不多說(shuō)我們直接步入正題,下面是一個(gè)思維導(dǎo)圖:

? ??????????首先我們先與另外兩種通信方案進(jìn)行一下對(duì)比:


配對(duì)流程:

1. 注冊(cè)適配器打開(kāi)藍(lán)牙

2.搜索附近設(shè)備和已配對(duì)設(shè)備

3. 選擇未配對(duì)設(shè)備進(jìn)行配對(duì)

4.選擇已配對(duì)設(shè)備進(jìn)行連接通信


通信工作流程:?????????????????????????????????????????????????????

1.服務(wù)端先建立一個(gè)服務(wù)端套接字Socket,然后該套接字開(kāi)始監(jiān)聽(tīng)客戶端的連接。????????????????????????

2 .客戶端也建立一個(gè)socket,然后向服務(wù)端發(fā)起連接,這時(shí)候如果沒(méi)有異常就算兩個(gè)設(shè)備連接成功了。??

3.這時(shí)候客戶端和服務(wù)端都會(huì)持有一個(gè)Socket,利用該Socket可以發(fā)送和接收消息。


注冊(cè)適配器并打開(kāi)藍(lán)牙

<!-- 藍(lán)牙通訊權(quán)限 -->

<uses-permission android:name="android.permission.BLUETOOTH" />//一些配置連接藍(lán)牙的權(quán)限

<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />//進(jìn)行操作的權(quán)限

<!-- 6.0以上需要的權(quán)限 -->

<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

在onCreate()方法里,獲取藍(lán)牙適配器

BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();

BluetoothAdapter這個(gè)類(lèi)常用的方法有:

getDefaultAdapter: 獲取本地的藍(lán)牙適配器

enable(); 打開(kāi)藍(lán)牙(不帶提示框 但是手機(jī)有自帶的。。。)

disable(); 關(guān)閉藍(lán)牙

isEnabled(): 本地藍(lán)牙是否打開(kāi)

getAddress(); 獲取自己的藍(lán)牙MAC地址

cancelDiscovery()?停止掃描

isDiscovering()?是否正在處于掃描過(guò)程中

getState():獲取本地藍(lán)牙適配器的狀態(tài)

getScanMode(): 獲取本地藍(lán)牙適配器的當(dāng)前藍(lán)牙掃描模式。

詳細(xì)用法表

接下來(lái)為了保險(xiǎn)起見(jiàn)先判斷設(shè)備是否支持藍(lán)牙:

if(mBluetoothAdapter == null){

????????Toast.makeText(this,"本地藍(lán)牙不可用",Toast.LENGTH_SHORT).show();

????????finish(); //退出應(yīng)用

}

確認(rèn)支持藍(lán)牙的話就可以調(diào)用藍(lán)牙適配器的方法了:

String Address = bluetoothAdapter.getAddress(); //獲取本機(jī)藍(lán)牙MAC地址

String Name = bluetoothAdapter.getName(); //獲取本機(jī)藍(lán)牙名稱(chēng)

// 若藍(lán)牙沒(méi)打開(kāi)

if(!bluetoothAdapter.isEnabled()){

????????bluetoothAdapter.enable(); //打開(kāi)藍(lán)牙

}

打開(kāi)藍(lán)牙的兩種方式:

第一種打開(kāi)方法:調(diào)用enable

第二種打開(kāi)方法,調(diào)用系統(tǒng)API去打開(kāi)藍(lán)牙

mBluetoothAdapter.enable();

//不會(huì)自動(dòng)提示用戶默認(rèn)打開(kāi) 有的手機(jī)還是會(huì)提示的

Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);

startActivityForResult(intent, REQUEST_OPEN_BT_CODE); //CODE值只是標(biāo)記可以更改

//會(huì)以Dialog樣式顯示一個(gè)Activity , 我們可以在onActivityResult()方法去處理返回值


搜索附近設(shè)備和已配對(duì)設(shè)備

設(shè)置可以被搜索到

//設(shè)置可以被搜索到

//判斷藍(lán)牙適配器的當(dāng)前藍(lán)牙掃描模式

?if(mBluetoothAdapter.getScanMode()!=BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE){

????????Intent?discoverableIntent=new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);

????????// 設(shè)置被發(fā)現(xiàn)時(shí)間,最大值是3600秒,0表示設(shè)備總是可以被發(fā)現(xiàn)的(小于0或者大于3600則會(huì)被自動(dòng)設(shè)置為120秒)

????????discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 120);

????????startActivity(discoverableIntent);

}

接下來(lái)開(kāi)始搜索附近:

判斷是否正在搜索如果是就停止搜索之類(lèi)的

if (!mBluetoothAdapter.isDiscovering()) {//判斷是否正在搜索

????????mBluetoothAdapter.startDiscovery();//開(kāi)始搜索附近設(shè)備

????????textView2.setText("正在搜索...");

} else {

????mBluetoothAdapter.cancelDiscovery();//停止搜索

}

搜索附近需要先注冊(cè)個(gè)廣播來(lái)監(jiān)聽(tīng)查詢附近藍(lán)牙設(shè)備:

藍(lán)牙掃描時(shí),掃描到任一遠(yuǎn)程藍(lán)牙設(shè)備時(shí),會(huì)發(fā)送此廣播。兩種廣播用一個(gè)就行

靜態(tài)注冊(cè)就是在AndroidManifest.xml文件中定義,

注冊(cè)的廣播接收器必須繼承BroadReceiver 動(dòng)態(tài)注冊(cè)就是在程序中使用Context.registerReceiver注冊(cè)。

動(dòng)態(tài)廣播

//注冊(cè)廣播

IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);//搜索到設(shè)備

registerReceiver(receiver, filter);

IntentFilter filter2 = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);//搜索結(jié)束

registerReceiver(receiver, filter2);

靜態(tài)廣播

<!-- 廣播接收 -->

<receiver android:name="包名.類(lèi)名" >

<intent-filter android:priority="1000">

<action android:name="android.bluetooth.adapter.action.DISCOVERY_FINISHED"/>

<action android:name="android.bluetooth.device.action.FOUND" />

</intent-filter>

</receiver>

new個(gè)BroadcastReceiver來(lái)接收:

廣播一旦發(fā)送這邊就會(huì)調(diào)用onReceive()方法

BroadcastReceiver receiver = new BroadcastReceiver() {

????????@Override

????????public void onReceive(Context context, Intent intent) {

????????????????String action = intent.getAction();

????????????????if (action.equals(BluetoothDevice.ACTION_FOUND)) {

????????????????//獲取已配對(duì)藍(lán)牙設(shè)備

????????????????????????Set<BluetoothDevice> devices = mBluetoothAdapter.getBondedDevices();

????????????????????????for (BluetoothDevice bonddevice : devices) {

????????????????????????????????mPeiDuiList.add(bonddevice);

????????????????????????????????peiDuiAdapter.notifyDataSetChanged();

????????????????????????}

????????????????????????BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);

????????????????????????//判斷未配對(duì)的設(shè)備

????????????????????????if (device.getBondState() != BluetoothDevice.BOND_BONDED) {

????????????????????????????????mFuJinList.add(device);

????????????????????????????????fuJinAdapter.notifyDataSetChanged();

? ? ? ? ? ? ? ? ? ? ? ? ?}

????????????????} else if (action.equals(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)) {

????????????????????????textView2.setText("搜索完成...");

????????????????}

????????}

};

我這邊是創(chuàng)建了兩個(gè)集合分別把已配對(duì)的和附近的儲(chǔ)存進(jìn)去

private ArrayList<BluetoothDevice> mPeiDuiList = new ArrayList<>();

private ArrayList<BluetoothDevice> mFuJinList = new ArrayList<>();


選擇未配對(duì)設(shè)備進(jìn)行配對(duì)

這里用到了藍(lán)牙設(shè)備BluetoothDevice

BluetoothDevice用于指代某個(gè)藍(lán)牙設(shè)備,通常表示對(duì)方設(shè)備

getName:獲得該設(shè)備的名稱(chēng);?

getAddress:獲得該設(shè)備的地址;?

createRfcommSocketToServiceRecord:根據(jù)UUID創(chuàng)建并返回一個(gè)BluetoothSocket。

1 配對(duì)

兩個(gè)設(shè)備建立連接以后,就可以進(jìn)行一個(gè)配對(duì)的過(guò)程。

配對(duì)進(jìn)行的時(shí)候,會(huì)產(chǎn)生一個(gè)密鑰用來(lái)加密與鑒別連接。一個(gè)典型的情況是,從機(jī)設(shè)備會(huì)要求主機(jī)設(shè)備提供一個(gè)密碼來(lái)完成一個(gè)配對(duì)過(guò)程。

這個(gè)密碼可以是固定的例如“000000”,也可以是提供給上層的隨機(jī)產(chǎn)生的一個(gè)值。當(dāng)主機(jī)設(shè)備發(fā)送一個(gè)正確的密碼是,這兩個(gè)設(shè)備就會(huì)相互交換密鑰來(lái)加密并鑒別連接。

2 綁定

很多情況下,兩個(gè)設(shè)備會(huì)需要經(jīng)常性的斷開(kāi)連接,連接這樣的過(guò)程,BLE有一個(gè)安全功能,允許兩臺(tái)設(shè)備配對(duì)的時(shí)候給對(duì)方一個(gè)長(zhǎng)期的一套安全密鑰。

這種機(jī)制稱(chēng)作為綁定。允許兩個(gè)設(shè)備再次連接時(shí)不需要再次進(jìn)行配對(duì)就能從新加密與鑒別,只要他們存儲(chǔ)了這個(gè)安全密鑰。

//點(diǎn)擊附近的開(kāi)始配對(duì)

mBluetoothAdapter.cancelDiscovery();//停止搜索

String address = mFuJinList.get(position).getAddress();

Toast.makeText(MainActivity.this, mFuJinList.get(position).getName() + "配對(duì)中。。。", Toast.LENGTH_SHORT).show();

try {

????????//調(diào)用工具類(lèi)與設(shè)備配對(duì)

????????//已知自己的地址 點(diǎn)擊獲取對(duì)方的地址 配對(duì)

????????remoteDevice = mBluetoothAdapter.getRemoteDevice(address);//通過(guò)mac地址獲取藍(lán)牙設(shè)備

????????ClsUtils.createBond(remoteDevice.getClass(), remoteDevice);

} catch (Exception e) {

e.printStackTrace();

}

這里用到了一個(gè)配對(duì)工具類(lèi)ClsUtils

package com.example.lin.mylanya.utils;

import java.lang.reflect.Field;

import java.lang.reflect.Method;

import android.bluetooth.BluetoothDevice;

import android.util.Log;

public class ClsUtils {

/**

* 與設(shè)備配對(duì) 參考源碼:platform/packages/apps/Settings.git

* /Settings/src/com/android/settings/bluetooth/CachedBluetoothDevice.java

*/

static public boolean createBond(Class btClass,BluetoothDevice btDevice) throws Exception {

????????Method createBondMethod = btClass.getMethod("createBond");

????????Boolean returnValue = (Boolean) createBondMethod.invoke(btDevice);

????????return returnValue.booleanValue();

}

/**

* 與設(shè)備解除配對(duì) 參考源碼:platform/packages/apps/Settings.git

* /Settings/src/com/android/settings/bluetooth/CachedBluetoothDevice.java

*/

????????static public boolean removeBond(Class btClass,BluetoothDevice btDevice) throws Exception {

????????????????Method removeBondMethod = btClass.getMethod("removeBond");

????????????????Boolean returnValue = (Boolean) removeBondMethod.invoke(btDevice);

????????????????return returnValue.booleanValue();

????????}

}

?

選擇已配對(duì)設(shè)備進(jìn)行連接通信

通信需要?jiǎng)?chuàng)建客戶端和服務(wù)器來(lái)建立連接

服務(wù)器一般是在打開(kāi)藍(lán)牙后創(chuàng)建這里的ChatController是通信的工具類(lèi) 這個(gè)方法里面調(diào)用了服務(wù)器線程(AccepThread)的啟動(dòng)方法

if (!mBluetoothAdapter.isEnabled()) {//判斷藍(lán)牙是否打開(kāi)

????????mBluetoothAdapter.enable();//打開(kāi)藍(lán)牙

}

ChatController.getInstance().waitingForFriends(mBluetoothAdapter, handler);//等待客戶端來(lái)連接

?服務(wù)器端建立套接字,等待客戶端連接,

調(diào)用BluetoothAdapter的listenUsingRfcommWithServiceRecord方法,產(chǎn)生一個(gè)BluetoothServerSocket對(duì)象,

然后調(diào)用BluetoothServerSocket對(duì)象的accept方法,

注意accept方法會(huì)產(chǎn)生阻塞,直到一個(gè)客戶端連接建立,所以服務(wù)器端的socket的建立需要在一個(gè)子線程中去做,

AccepThread?

public class AccepThread extends Thread {

????????/** 連接的名稱(chēng)*/

????????private static final String NAME = "BluetoothClass";

????????/** UUID*/

????????private static final UUID MY_UUID = UUID.fromString(Constant.CONNECTTION_UUID);

????????/** 服務(wù)端藍(lán)牙Sokcet*/

????????private final BluetoothServerSocket mmServerSocket;

????????private final BluetoothAdapter mBluetoothAdapter;

????????/** 線程中通信的更新UI的Handler*/

????????private final Handler mHandler;

????????/** 監(jiān)聽(tīng)到有客戶端連接,新建一個(gè)線程單獨(dú)處理,不然在此線程中會(huì)堵塞*/

????????private ConnectedThread mConnectedThread;

? ? ? ? public AccepThread(BluetoothAdapter adapter, Handler handler) throws IOException {

????????????????mBluetoothAdapter = adapter;

????????????????this.mHandler = handler;

????????????????// 獲取服務(wù)端藍(lán)牙socket;

????????????????mmServerSocket = mBluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);

? ? ? ? ? }

@Override

public void run() {

????????super.run();

????????// 連接的客戶端soacket

????????BluetoothSocket socket = null;

????????// 服務(wù)端是不退出的,要一直監(jiān)聽(tīng)連接進(jìn)來(lái)的客戶端,所以是死循環(huán)

????????while (true){

????????????????try {

????????????????????????// 獲取連接的客戶端socket

????????????????????????socket = mmServerSocket.accept();

????????????????} catch (IOException e) {

? ? ? ? ? ? ? ? ? ? ? ?// 通知主線程更新UI, 獲取異常

????????????????????????mHandler.sendEmptyMessage(Constant.MSG_ERROR);

????????????????????????e.printStackTrace();

????????????????????????// 服務(wù)端退出一直監(jiān)聽(tīng)線程

????????????????????????????break;

????????????????}

????????if(socket != null) {

????????????????????????// 管理連接的客戶端socket

????????????????????????manageConnectSocket(socket);

????????}

????}

}

/**

* 管理連接的客戶端socket

* @param socket

*/

private void manageConnectSocket(BluetoothSocket socket) {

????????// 只支持同時(shí)處理一個(gè)連接

????????// mConnectedThread不為空,踢掉之前的客戶端

????????if(mConnectedThread != null) {

????????????????????mConnectedThread.cancle();

????????}

????????// 新建一個(gè)線程,處理客戶端發(fā)來(lái)的數(shù)據(jù)

????????mConnectedThread = new ConnectedThread(socket, mHandler);

????????mConnectedThread.start();

}

/**

* 斷開(kāi)服務(wù)端,結(jié)束監(jiān)聽(tīng)

*/

public void cancle() {

????????try {

????????????????mmServerSocket.close();

????????????????mHandler.sendEmptyMessage(Constant.MSG_FINISH_LISTENING);

????????} catch (IOException e) {

????????????????e.printStackTrace();

????????}

}

/**

* 發(fā)送數(shù)據(jù)

* @param data

*/

????????public void sendData(byte[] data){

????????????????if(mConnectedThread != null) {

????????????????mConnectedThread.write(data);

????????}

????}

}

藍(lán)牙客戶端套接字BluetoothSocket

BluetoothSocket是客戶端的Socket,用于與對(duì)方設(shè)備進(jìn)行數(shù)據(jù)通信。

connect:建立藍(lán)牙的socket連接;?

close:關(guān)閉藍(lán)牙的socket連接;?

getInputStream:獲取socket連接的輸入流對(duì)象;?

getOutputStream:獲取socket連接的輸出流對(duì)象;?

getRemoteDevice:獲取遠(yuǎn)程設(shè)備信息。

服務(wù)器和客戶端連接上后都需要新建一個(gè)線程進(jìn)行通信不然會(huì)線程阻塞

發(fā)送數(shù)據(jù)需要調(diào)用BluetoothSocket的getOutputStream(),接收數(shù)據(jù)需要調(diào)用getInputStream()方法

String uuid = java.util.UUID.randomUUID().toString();

一般在創(chuàng)建Socket時(shí)需要UUID作為端口的唯一性,如果兩臺(tái)Android設(shè)備互聯(lián),則沒(méi)有什么特殊的,

如果讓非Android的藍(lán)牙設(shè)備連接Android藍(lán)牙設(shè)備,則UUID必須使用某個(gè)固定保留的UUID

Android中創(chuàng)建UUID:UUID ?uuid = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB")

?ConnectedThread

public class ConnectedThread extends Thread{

????????/** 當(dāng)前連接的客戶端BluetoothSocket*/

????????private final BluetoothSocket mmSokcet;

????????/** 讀取數(shù)據(jù)流*/

????????private final InputStream mmInputStream;

????????/** 發(fā)送數(shù)據(jù)流*/

????????private final OutputStream mmOutputStream;

????????/** 與主線程通信Handler*/

????????private Handler mHandler;

????????private String TAG = "ConnectedThread";

????????public ConnectedThread(BluetoothSocket socket,Handler handler) {

????????mmSokcet = socket;

????????mHandler = handler;

????????InputStream tmpIn = null;

????????OutputStream tmpOut = null;

????????try {

????????????????tmpIn = socket.getInputStream();

????????????????tmpOut = socket.getOutputStream();

????????} catch (IOException e) {

????????????????e.printStackTrace();

????????}

????????mmInputStream = tmpIn;

????????mmOutputStream = tmpOut;

}

@Override

public void run() {

????????super.run();

????????byte[] buffer = new byte[1024];

????????while (true) {

????????????????try {

????????????????????????// 讀取數(shù)據(jù)

????????????????????????int bytes = mmInputStream.read(buffer);

????????????????????????if(bytes > 0) {

????????????????????????????????????????String data = new String(buffer,0,bytes,"utf-8");

????????????????????????????????????????// 把數(shù)據(jù)發(fā)送到主線程, 此處還可以用廣播

????????????????????????????????????????Message message = mHandler.obtainMessage(Constant.MSG_GOT_DATA,data);

????????????????????????????????????????mHandler.sendMessage(message);

????????????????????????}

????????????????????????Log.d(TAG, "messge size :" + bytes);

????????????????} catch (IOException e) {

????????????????????????e.printStackTrace();

????????????????}

????????????}

}

// 踢掉當(dāng)前客戶端

public void cancle() {

????????try {

????????????????mmSokcet.close();

????????} catch (IOException e) {

????????????????e.printStackTrace();

????????}

}

/**

* 服務(wù)端發(fā)送數(shù)據(jù)

* @param data

*/

public void write(byte[] data) {

????????try {

????????????????mmOutputStream.write(data);

????????} catch (IOException e) {

????????????????e.printStackTrace();

????????}

????}

}

ChatController

public class ChatController {

????????/**

????????* 客戶端的線程

????????*/

????????private ConnectThread mConnectThread;

????????/**

????????* 服務(wù)端的線程

????????*/

? ? ? ? ? private AccepThread mAccepThread;

? ? ? ? ? private ChatProtocol mProtocol = new ChatProtocol();

????????/**

????????* 網(wǎng)絡(luò)協(xié)議的處理函數(shù)

????????*/

????????private class ChatProtocol {

????????????????private static final String CHARSET_NAME = "utf-8";

????????????????/**

????????????????* 封包(發(fā)送數(shù)據(jù))

????????????????* 把發(fā)送的數(shù)據(jù)變成 數(shù)組 2進(jìn)制流

????????????????*/

????????????????public byte[] encodePackge(String data) {

???????????????????????????if (data == null) {

????????????????????????????????????return new byte[0];

????????????????????????????} else {

????????????????????????????????????try {

????????????????????????????????????????????return data.getBytes(CHARSET_NAME);

????????????????????????????????????} catch (UnsupportedEncodingException e) {

????????????????????????????????????????????e.printStackTrace();

????????????????????????????????????????????return new byte[0];

????????????????????????????????????}

? ? ? ? ? ? ? ? ? }

}

/**

* 解包(接收處理數(shù)據(jù))

* 把網(wǎng)絡(luò)上數(shù)據(jù)變成自己想要的數(shù)據(jù)體

*/

public String decodePackage(byte[] netData) {

????????????if (netData == null) {

????????????????return "";

????????????} else {

????????????????????????try {

? ? ? ? ? ? ? ? ? ? ? ? ?????????return new String(netData, CHARSET_NAME);

????????????????????????} catch (UnsupportedEncodingException e) {

????????????????????????????????e.printStackTrace();

???????????????????????????????return "";

????????????????????????}

? ?????????}

????????}

}

/**

* 與服務(wù)器連接進(jìn)行聊天

*/

public void startChatWith(BluetoothDevice device, BluetoothAdapter adapter, Handler handler) {

????????mConnectThread = new ConnectThread(device, adapter, handler);

????????mConnectThread.start();

}

/**

* 等待客戶端來(lái)連接

* handler : 用來(lái)跟主線程通信,更新UI用的

*/

public void waitingForFriends(BluetoothAdapter adapter, Handler handler) {

? ? try {

????????????????mAccepThread = new AccepThread(adapter, handler);

????????????????mAccepThread.start();

????????} catch (IOException e) {

????????????????e.printStackTrace();

????????}

}

/**

* 發(fā)出消息

*/

public void sendMessage(String msg) {

????????// 封包

????????byte[] data = mProtocol.encodePackge(msg);

????????if (mConnectThread != null) {

????????????????mConnectThread.sendData(data);

????????} else if (mAccepThread != null) {

????????????????mAccepThread.sendData(data);

????????}

}


/**

* 網(wǎng)絡(luò)數(shù)據(jù)解碼

*/

public String decodeMessage(byte[] data){

????????return mProtocol.decodePackage(data);

}

/**

* 停止聊天

*/

public void stopChart(){

????????if(mConnectThread != null) {

????????????????mConnectThread.cancle();

????????}

????????if(mAccepThread != null) {

????????????????mAccepThread.cancle();

????????}

}

/**

* 以下是單例寫(xiě)法

*/

????????private static class ChatControlHolder{

????????????????private static ChatController mInstance = new ChatController();

????????}

????????public static ChatController getInstance(){

????????????????return ChatControlHolder.mInstance;

????????}

}


發(fā)送數(shù)據(jù)

//客戶端向服務(wù)器發(fā)送數(shù)據(jù)

String s = mEdit.getText().toString();

//發(fā)出消息

ChatController.getInstance().sendMessage(s);


//handler接收數(shù)據(jù)

private Handler handler = new Handler() {

@Override

public void handleMessage(Message msg) {

????super.handleMessage(msg);

????????switch (msg.what) {

? ? ? ? ????????????case MSG_GOT_DATA:

????????????????????//接收消息

????????????????????String data = (String) msg.obj;

????????????????????Toast.makeText(MainActivity.this, data, Toast.LENGTH_SHORT).show();

? ? ? ? ? ? ? ? ? ? break;

? ?????}

????}

};


最后銷(xiāo)毀:

@Override

protected void onDestroy() {

????????super.onDestroy();

????????unregisterReceiver(receiver);//關(guān)閉服務(wù)

????????mBluetoothAdapter.disable();//關(guān)閉藍(lán)牙

????????ChatController.getInstance().stopChart();//停止聊天

}

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

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

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