藍牙通信,完整的通信流程!

無線通信方案,有三種方案可以實施:
1、NFC 2、藍牙 3、WIFI
下面是對這三個知識點做的一個總結,參照對比可以選擇合適的方案。而本章着重講的藍牙之間通信。
這裏寫圖片描述

首先介紹一下藍牙的兩個廣播Receiver。
第一個:藍牙狀態的改變是通過廣播接收到的。

 // 註冊藍牙狀態接收廣播
  IntentFilter intentFilter = new  ntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
   registerReceiver(mReceiverBluetoothStatus,intentFilter);

    /**
     * 定義接收藍牙狀態廣播receiver
     *
     * @param savedInstanceState
     */
    private BroadcastReceiver mReceiverBluetoothStatus = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context,Intent intent) {
            int status = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,-1);
            switch (status) {
                case BluetoothAdapter.STATE_OFF:
                    Log.d(TAG,"藍牙已關閉");
                    break;
                case BluetoothAdapter.STATE_ON:
                    Log.d(TAG,"藍牙已打開");
                    break;
                case BluetoothAdapter.STATE_TURNING_OFF:
                    Log.d(TAG,"藍牙關閉中...");
                    break;
                case BluetoothAdapter.STATE_TURNING_ON:
                    Log.d(TAG,"藍牙打開中...");
                    break;
                default:

                    break;
            }
        }
    };

第二個:藍牙搜索到設備、綁定設備(配對)也是通過廣播接收的。(搜索到設備系統會自動發一個廣播)

   // 註冊藍牙device接收廣播
        IntentFilter intentFilterDevice = new IntentFilter();
        // 開始查找
        intentFilterDevice.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
        // 結束查找
        intentFilterDevice.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
        // 查找設備(查找到設備)
        intentFilterDevice.addAction(BluetoothDevice.ACTION_FOUND);
        // 設備掃描模式改變 (自己狀態的改變action,當設置可見或者不見時都會發送此廣播)
        intentFilterDevice.addAction(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED);
        // 綁定狀態
        intentFilterDevice.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
        registerReceiver(mReceiverDeceiver,intentFilterDevice);
  /**
     * 定義接收藍牙device廣播Receiver
     */
    private BroadcastReceiver mReceiverDeceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context,Intent intent) {
            String action = intent.getAction();
            if (BluetoothAdapter.ACTION_DISCOVERY_STARTED.equals(action)) {
                // 開始搜索        ——接收廣播
                Log.d(TAG,"開始搜索");
                mList.clear();
                mAdapter.refresh(mList);

            } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {
                // 查找到設備完成   —— 接收廣播
                Log.d(TAG,"查找到設備完成");

            } else if (BluetoothDevice.ACTION_FOUND.equals(action)) {
                // 搜索到設備       —— 接收廣播
                Log.d(TAG,"搜索到設備");
                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                mList.add(device);
                mAdapter.refresh(mList);


            } else if (BluetoothAdapter.ACTION_SCAN_MODE_CHANGED.equals(action)) {
                // 當自己設備設置藍牙可見時或者不可見時 —— 接收廣播
                int scanMode = intent.getIntExtra(BluetoothAdapter.EXTRA_SCAN_MODE,0);
                // 可見時
                if (scanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
                    Log.d(TAG,"設備可見監聽");
                } else {
                    Log.d(TAG,"設備不可見監聽");
                }

            } else if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)) {
                // 綁定狀態改變回調
                BluetoothDevice remoteDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                if (remoteDevice == null) {
                    Log.d(TAG,"沒有綁定設備");
                    return;
                }

                int status = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,0);
                if (status == BluetoothDevice.BOND_BONDED) {
                    Log.d(TAG,"綁定設備完成: " + remoteDevice.getName());
                } else if (status == BluetoothDevice.BOND_BONDING) {
                    Log.d(TAG,"綁定設備中: " + remoteDevice.getName());
                } else if (status == BluetoothDevice.BOND_NONE) {
                    Log.d(TAG,"取消綁定: ");
                }
            }
        }

    };

以上基本上就是藍牙接收狀態和搜索到設備,綁定設備一系列改變的操作!基本上已經很全了。下面介紹一個藍牙通信的流程。

左爲客戶端Socket連接的一個流程:首先獲取一個客戶端Socket(),然後連接上,就可以讀取數據和發送數據了。

右爲服務端Socket操作流程:

藍牙通信原理介紹:
藍牙通信和socket通信原理基本上是一致的,下面我給大家上一張圖(圖爲Socket通信圖)。分析一下。
這裏寫圖片描述

藍牙客戶端Socket的與Sokcet流程是一樣的,只不過參數不同而已。如下:
1、創建客戶端藍牙Sokcet
2、創建連接
3、讀寫數據
4、關閉

服務端socket:
1、創建服務端藍牙Socket
2、綁定端口號(藍牙忽略)
3、創建監聽listen(藍牙忽略, 藍牙沒有此監聽,而是通過whlie(true)死循環來一直監聽的)
4、通過accept(),如果有客戶端連接,會創建一個新的Socket,體現出併發性,可以同時與多個socket通訊)
5、讀寫數據
6、關閉

下面看客戶端代碼:

/**
 * <p>Title: ConnectThread</p >
 * <p>Description: 客戶端邏輯: 客戶端的線程,處理客戶端socket</p >
 * <p>Company: ihaveu</p >
 *
 * @author MaWei
 * @date 2017/12/26
 */
public class ConnectThread extends Thread{
    private static final UUID MY_UUID = UUID.fromString(Constant.CONNECTTION_UUID);
    /** 客戶端socket*/
    private final BluetoothSocket mmSoket;
    /** 要連接的設備*/
    private final BluetoothDevice mmDevice;
    private BluetoothAdapter mBluetoothAdapter;
    /** 主線程通信的Handler*/
    private final Handler mHandler;
    /** 發送和接收數據的處理類*/
    private ConnectedThread mConnectedThread;

    public ConnectThread(BluetoothDevice device, BluetoothAdapter bluetoothAdapter, Handler mUIhandler) {
        mmDevice = device;
        mBluetoothAdapter = bluetoothAdapter;
        mHandler = mUIhandler;

        BluetoothSocket tmp = null;
        try {
            // 創建客戶端Socket
            tmp = device.createRfcommSocketToServiceRecord(MY_UUID);
        } catch (IOException e) {
            e.printStackTrace();
        }

        mmSoket = tmp;
    }

    @Override
    public void run() {
        super.run();
        // 關閉正在發現設備.(如果此時又在查找設備,又在發送數據,會有衝突,影響傳輸效率)
        mBluetoothAdapter.cancelDiscovery();

        try {
            // 連接服務器
            mmSoket.connect();
        } catch (IOException e) {
            // 連接異常就關閉
            try {
                mmSoket.close();
            } catch (IOException e1) {
            }
            return;
        }

        manageConnectedSocket(mmSoket);
    }

    private void manageConnectedSocket(BluetoothSocket mmSoket) {
        // 通知主線程連接上了服務端socket,更新UI
        mHandler.sendEmptyMessage(Constant.MSG_CONNECTED_TO_SERVER);
        // 新建一個線程進行通訊,不然會發現線程堵塞
        mConnectedThread = new ConnectedThread(mmSoket,mHandler);
        mConnectedThread.start();
    }

    /**
     * 關閉當前客戶端
     */
    public void cancle() {
        try {
            mmSoket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 發送數據
     * @param data
     */
    public void sendData(byte[] data) {
        if(mConnectedThread != null) {
            mConnectedThread.write(data);
        }
    }
}

服務端代碼:

/**
 * <p>Title: AccepThread</p >
 * <p>Description: 服務端Socket通過accept()一直監聽客戶端連接的線程</p >
 * <p>Company: ihaveu</p >
 *
 * @author MaWei
 * @date 2017/12/26
 */
public class AccepThread extends Thread {

    /** 連接的名稱*/
    private static final String NAME = "BluetoothClass";
    /** UUID*/
    private static final UUID MY_UUID = UUID.fromString(Constant.CONNECTTION_UUID);
    /** 服務端藍牙Sokcet*/
    private final BluetoothServerSocket mmServerSocket;
    private final BluetoothAdapter mBluetoothAdapter;
    /** 線程中通信的更新UI的Handler*/
    private final Handler mHandler;
    /** 監聽到有客戶端連接,新建一個線程單獨處理,不然在此線程中會堵塞*/
    private ConnectedThread mConnectedThread;

    public AccepThread(BluetoothAdapter adapter, Handler handler) throws IOException {
        mBluetoothAdapter = adapter;
        this.mHandler = handler;

        // 獲取服務端藍牙socket
        mmServerSocket = mBluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);
    }

    @Override
    public void run() {
        super.run();
        // 連接的客戶端soacket
        BluetoothSocket socket = null;

        // 服務端是不退出的,要一直監聽連接進來的客戶端,所以是死循環
        while (true){
            // 通知主線程更新UI,客戶端開始監聽
            mHandler.sendEmptyMessage(Constant.MSG_START_LISTENING);
            try {
                // 獲取連接的客戶端socket
                socket =  mmServerSocket.accept();
            } catch (IOException e) {
                // 通知主線程更新UI, 獲取異常
                mHandler.sendEmptyMessage(Constant.MSG_ERROR);
                e.printStackTrace();
                // 服務端退出一直監聽線程
                break;
            }

            if(socket != null) {
                // 管理連接的客戶端socket
                manageConnectSocket(socket);

                // 這裏應該是手動斷開,案例應該是隻保證連接一個客戶端,所以連接完以後,關閉了服務端socket
//                try {
//                    mmServerSocket.close();
//                    mHandler.sendEmptyMessage(Constant.MSG_FINISH_LISTENING);
//                } catch (IOException e) {
//                    e.printStackTrace();
//                }
            }
        }
    }

    /**
     * 管理連接的客戶端socket
     * @param socket
     */
    private void manageConnectSocket(BluetoothSocket socket) {
        // 只支持同時處理一個連接
        // mConnectedThread不爲空,踢掉之前的客戶端
        if(mConnectedThread != null) {
            mConnectedThread.cancle();
        }

        // 主線程更新UI,連接到了一個客戶端
        mHandler.sendEmptyMessage(Constant.MSG_GOT_A_CLINET);
        // 新建一個線程,處理客戶端發來的數據
        mConnectedThread = new ConnectedThread(socket, mHandler);
        mConnectedThread.start();
    }

    /**
     * 斷開服務端,結束監聽
     */
    public void cancle() {
        try {
            mmServerSocket.close();
            mHandler.sendEmptyMessage(Constant.MSG_FINISH_LISTENING);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 發送數據
     * @param data
     */
     public void sendData(byte[] data){
         if(mConnectedThread != null) {
             mConnectedThread.write(data);
         }
     }
}

下面看一個共同通訊處理類:

/**
 * <p>Title: ConnectedThread</p >
 * <p>Description: 客戶端和服務端 處理 發送數據 和獲取數據</p >
 * <p>Company: ihaveu</p >
 *
 * @author MaWei
 * @date 2017/12/26
 */
public class ConnectedThread extends Thread{
    /** 當前連接的客戶端BluetoothSocket*/
    private final BluetoothSocket mmSokcet;
    /** 讀取數據流*/
    private final InputStream mmInputStream;
    /** 發送數據流*/
    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 {
                // 讀取數據
                int bytes = mmInputStream.read(buffer);

                if(bytes > 0) {
                    String data = new String(buffer,0,bytes,"utf-8");
                    // 把數據發送到主線程, 此處還可以用廣播
                    Message message = mHandler.obtainMessage(Constant.MSG_GOT_DATA,data);
                    mHandler.sendMessage(message);
                }

                Log.d(TAG, "messge size :" + bytes);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    // 踢掉當前客戶端
    public void cancle() {
        try {
            mmSokcet.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 服務端發送數據
     * @param data
     */
    public void write(byte[] data) {
        try {
            mmOutputStream.write(data);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

下面是自己寫的一個聊天demo

/**
 * <p>Title: ChatController</p >
 * <p>Description: 聊天控制器</p >
 * <p>Company: ihaveu</p >
 *
 * @author MaWei
 * @date 2017/12/26
 */
public class ChatController {
    /** 客戶端的線程*/
    private ConnectThread mConnectThread;
    /** 服務端的線程*/
    private AccepThread mAccepThread;
    private ChatProtocol mProtocol = new ChatProtocol();

    /**
     * 網絡協議的處理函數
     */
    private class ChatProtocol implements ProtocoHandler<String>{
        private static final String CHARSET_NAME = "utf-8";

        /**
         * 封包(發送數據)
         * 把發送的數據變成  數組 2進制流
         */
        @Override
        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];
                }
            }
        }

        /**
         * 解包(接收處理數據)
         * 把網絡上數據變成自己想要的數據體
         */
        @Override
        public String decodePackage(byte[] netData) {
            if(netData == null) {
                return "";
            }else {
                try {
                    return new String(netData, CHARSET_NAME);
                } catch (UnsupportedEncodingException e) {
                    e.printStackTrace();
                    return "";
                }
            }
        }
    }

    /**
     * 與服務器連接進行聊天
     */
    public void startChatWith(BluetoothDevice device,BluetoothAdapter adapter,Handler handler){
        mConnectThread = new ConnectThread(device, adapter, handler);
        mConnectThread.start();
    }

    /**
     * 等待客戶端來連接
     * handler : 用來跟主線程通信,更新UI用的
     */
    public void waitingForFriends(BluetoothAdapter adapter, Handler handler) {
        try {
            mAccepThread = new AccepThread(adapter,handler);
            mAccepThread.start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 發出消息
     */
    public void sendMessage(String msg){
        // 封包
        byte[] data = mProtocol.encodePackge(msg);

        if(mConnectThread != null) {
            mConnectThread.sendData(data);
        }else if(mAccepThread != null) {
            mAccepThread.sendData(data);
        }

    }

    /**
     * 網絡數據解碼
     */
    public String decodeMessage(byte[] data){
        return mProtocol.decodePackage(data);
    }

    /**
     * 停止聊天
     */
    public void stopChart(){
        if(mConnectThread != null) {
            mConnectThread.cancle();
        }
        if(mAccepThread != null) {
            mAccepThread.cancle();
        }
    }


    /**
     * 以下是單例寫法
     */
    private static class ChatControlHolder{
        private static ChatController mInstance = new ChatController();
    }

    public static ChatController getInstance(){
        return ChatControlHolder.mInstance;
    }
}

OK,完成,大家主要看上面的廣播,和藍牙sokcet流程,其他的根據自己業務要求寫就OK

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章