? ??話不多說(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();//停止聊天
}