初步探索Android的藍牙實現

藍牙是十世紀的一位國王Harald Bluetooth的綽號(相傳他喜歡吃藍莓,所以牙齒變成了藍色),他將紛爭不斷的丹麥部落統一為一個王國,傳說中他還引入了基督教。剛好偉大的Jim Kardach在讀一本和藍牙國王有關的書籍,這位開發(fā)了允許電話和計算機通訊的系統的員工,就把他公司(瑞典愛立信,藍牙創(chuàng)始人)做的統一了各種移動電子設備之間的通訊問題的技術叫做了藍牙。藍牙統一了王國,而藍牙技術統一了移動設備之間的通訊方式。

好的,介紹扯完了,下面講一下Android上實現藍牙的方法:

1.藍牙的工作機制(參考博文

首先兩個設備上都要有藍牙設備或者專業(yè)一點叫藍牙適配器,以手機和電腦為例我畫了如下流程圖。其次在手機上進行掃描,掃描周圍藍藍牙設備,先找到手機附近的電腦,然后給它發(fā)出一個信號需要進行藍牙的配對,再次返回一個信號說明手機和電腦已經配對成功了,最后配對成功后可以進行文件傳輸了。這是一個最基本的一個流程。


2.與藍牙相關的類

網上找了一下,最重要的就是兩個類:BluetoothAdapter(可以理解為當前設備)和BluetoothDevice(遠程設備).

3.配置藍牙的Permission

在AndroidMenifest.xml里面設置藍牙使用的權限。

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

4.搭建藍牙

在你的機子和別的機子藍牙配對之前,你首先要知道,藍牙在你這臺機子上支不支持,能不能用,有沒有啟用。
你只要通過靜態(tài)方法BluetoothAdapter.getDefaultAdapter(),就可以獲得BluetoothAdapter的一個實例。看看他有沒有空就可以知道你的機子能不能用藍牙了。

BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (mBluetoothAdapter == null) {
    //設備不支持藍牙
}

然后再看看你機子有沒有啟動藍牙,直接通過你BluetoothAdapter的獲得的實例的isEnabled方法就可以了。
如果藍牙在這臺機子上支持,能用,但是沒有啟動,就要調用Intent去問問看系統要不要啟動藍牙。

f (!mBluetoothAdapter.isEnabled()) {
    Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
    startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}

然后你的機子會跳到藍牙的程序,去啟動藍牙。啟動之后返回當前Activity,調用Activity的onActivityResult()回調方法,藍牙程序會返回給你兩個整數常量:RESULT_OK和RESULT_CANCELED,看英文想必就大概知道什么意思了吧。
這兩個常量都是Activity里面設定的常量,直接用就可以了。

protected void onActivityResult(int requestCode, int resultCode, Intent data) {  
        switch (requestCode) {  
            case RESULT_OK:  
                //用戶開啟了藍牙
                break;  
            case RESULT_CANCELED:  
                //用戶拒絕開啟藍牙
                break;  
        }  
    }

5.尋找設備

Using the BluetoothAdapter, you can find remote Bluetooth devices either through device discovery or by querying the list of paired (bonded) devices.

這是AndroidDeveloper上的原話,就是說要用BluetoothAdapter找其他的藍牙設備,要通過設備discovery,通過查詢配對的設備。
在講具體實現之前,講一下藍牙配對(Paired)和藍牙連接(Connected)的區(qū)別:
配對:兩個機子只是意識到了彼此的存在,互相有一個共有的密碼(用于驗證),具備了加密傳輸的能力。好比一個男單身狗和一個女單身狗相遇搭訕····
連接:兩個機子連在了一起,通過RFCOMM(藍牙的通訊協議)傳輸數據。好比剛才的男單身狗和女單身狗在一起了····
接下來將如何查詢設備。

  1. 查詢Paired的設備
//通過mBluetoothAdapter.getBondedDevices()返回一個BluetoothDevice的Set
Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
// 如果有設備
if (pairedDevices.size() > 0) {
    // 循環(huán)遍歷
    for (BluetoothDevice device : pairedDevices) {
        // 通過Adapter把這些加到ListView中
        mArrayAdapter.add(device.getName() + "\n" + device.getAddress());
    }
}
  1. Discovery設備,通過廣播接收者實現
//創(chuàng)建一個廣播接收者用于接收信息
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
    public void onReceive(Context context, Intent intent) {
        //收到獲取的廣播的Action,好比你聽廣播的時候的頻率
        String action = intent.getAction();
        //我要的廣播的“頻率”符合要求,Action符合要求
        if (BluetoothDevice.ACTION_FOUND.equals(action)) {
            //從Intent中獲取BluetoothDevice對象
            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
            //信息加到ListView中去
            mArrayAdapter.add(device.getName() + "\n" + device.getAddress());
        }
    }
};
//注冊廣播
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver(mReceiver, filter); //不要忘了在onDestroy中銷毀廣播

另外要讓機子可以被掃描,還有一步是通過Intent開啟藍牙的掃描。通過startActivityForResult()方法

Intent discoverableIntent = new
Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
//掃描藍牙設備的間隔時間,默認120秒,最大3600秒
discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
startActivity(discoverableIntent);

然后會顯示如下的界面,就是說我們的軟件要讓你的手機可以被其他設備所掃描300s,你愿意嗎?(我愿意~ :-))

6.連接設備

這里有一個很重要的類叫BluetoothSocket,其實他和TCP Socket的原理是一樣的,一個服務端,一個客戶端,客戶端觸發(fā)連接,服務端處理請求。只不過這里的Socket基于藍牙的RFCOMM協議罷了,這個協議的內容我們不作深入探討。我們之研究他們如何實現。
不過這里有一種實現的技術,就是讓兩個設備都成為服務端和客戶端,這樣兩個設備都可以觸發(fā)和接收請求啦~
注意:兩個機子配對了,那么Android會自動產生一個對話框(如下圖),然后Pair就連在一起了。
回想一下你用藍牙的時候的怎么用的,比如電腦說要連手機,然后電腦手機上都會產生一個密碼,然后讓你確認密碼是否一致,你說是的話就他們就連在一起了。
先說說服務端的實現,大概就是:服務端要監(jiān)聽來自客戶端的連接請求(要用到一個BluetoothServerSocket對象),被允許后可以產生一個BluetoothSocket對象。獲得BluetoothSocket對象后就可以將BluetoothServerSocket對象給“丟棄”了。
實現過程如下:

  1. 通過listenUsingRfcommWithServiceRecord(String, UUID)方法獲取BluetoothServerSocket 對象。String可以理解為你服務端名字的代號。UUID的相關內容參考以下英文文獻,我沒有深入研究過,因為在藍牙協議中要用到這個信息所以有了這個內容:

About UUID
A Universally Unique Identifier (UUID) is a standardized 128-bit format for a string ID used to uniquely identify information. The point of a UUID is that it's big enough that you can select any random and it won't clash. In this case, it's used to uniquely identify your application's Bluetooth service. To get a UUID to use with your application, you can use one of the many random UUID generators on the web, then initialize a UUID withfromString(String).

  1. 調用accept()實現監(jiān)聽
  2. 通過close()關閉BluetoothServerSocket
private class AcceptThread extends Thread {
    private final BluetoothServerSocket mmServerSocket;
 
    public AcceptThread() {
    
        // 建立BluetoothServerSocket的tmp,因為mmServerSocket是final類型的只能賦值一次,
        //所以要這個tmp作中轉站
        BluetoothServerSocket tmp = null;
        
        try {
            // MY_UUID 是這款應用的UUID
            tmp = mBluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);
        } catch (IOException e) { }
        mmServerSocket = tmp;
    }
 
    public void run() {
        BluetoothSocket socket = null;
        //保持監(jiān)聽知道發(fā)生異常
        while (true) {
            try {
                socket = mmServerSocket.accept();
            } catch (IOException e) {
                break;
            }
            //連接被允許了
            if (socket != null) {
                //調用管理這個socket的函數(自己寫的)
                manageConnectedSocket(socket);
                //關閉ServerSocket
                mmServerSocket.close();
                break;
            }
        }
    }
 
    /** 線程關閉mServerSocket也會關閉 */
    public void cancel() {
        try {
            mmServerSocket.close();
        } catch (IOException e) { }
    }
}

下面講講客戶端的實現:

  1. 為了從serverSocket上獲取BluetoothSocket,你必須要先獲取BluetoothDevice。
  2. 通過BluetoothDevice的createRfcommSocketToServiceRecord(UUID)方法獲取BluetoothSocket的對象
  3. 通過connect()方法觸發(fā)連接,conect()方法要在主線程之外實現!!!
private class ConnectThread extends Thread {
    private final BluetoothSocket mmSocket;
    private final BluetoothDevice mmDevice;
 
    public ConnectThread(BluetoothDevice device) {
        //和Server端一樣的原理
        BluetoothSocket tmp = null;
        //獲取藍牙設備
        mmDevice = device;

        try {
            // MY_UUID is the app's UUID string, also used by the server code
            tmp = device.createRfcommSocketToServiceRecord(MY_UUID);
        } catch (IOException e) { }
        mmSocket = tmp;
    }
 
    public void run() {
        // 停止搜索設備,會降低連接的速度
        mBluetoothAdapter.cancelDiscovery();
 
        try {
            // 通過Socket建立連接
            //直到他成功或者拋出異常
            mmSocket.connect();
        } catch (IOException connectException) {
            // 不能連接,關閉Socket
            try {
                mmSocket.close();
            } catch (IOException closeException) { }
            return;
        }
 
        // Do work to manage the connection (in a separate thread)
        manageConnectedSocket(mmSocket);
    }
 
    /** Will cancel an in-progress connection, and close the socket */
    public void cancel() {
        try {
            mmSocket.close();
        } catch (IOException e) { }
    }
}

7.管理連接

終于到最后一步了,這里我們通過BluetoothSocket獲得InputStream和OutputStream,通過read和write方法就可以做數據傳輸的事情了!注意:讀取這些數據要異步處理(因為read和write方法會阻塞主線程)!

private class ConnectedThread extends Thread {
    private final BluetoothSocket mmSocket;
    private final InputStream mmInStream;
    private final OutputStream mmOutStream;
    public ConnectedThread(BluetoothSocket socket) {
        mmSocket = socket;
        //這里的tmp和之前的原理一樣
        InputStream tmpIn = null;
        OutputStream tmpOut = null;
 
        //獲取輸出流和輸入流
        try {
            tmpIn = socket.getInputStream();
            tmpOut = socket.getOutputStream();
        } catch (IOException e) { }
 
        mmInStream = tmpIn;
        mmOutStream = tmpOut;
    }
 
    public void run() {
        byte[] buffer = new byte[1024];  //存儲流數據的載體
        int bytes; //read的bytes數
 
        //保持監(jiān)聽輸入流直到出現異常
        while (true) {
            try {
                //從輸入流中讀取信息
                bytes = mmInStream.read(buffer);
                //利用Handler向主線程發(fā)送這些信息
                mHandler.obtainMessage(MESSAGE_READ, bytes, -1, buffer)
                        .sendToTarget();
            } catch (IOException e) {
                break;
            }
        }
    }
 
    /*從主線程中調用這個方法實現寫出*/
    public void write(byte[] bytes) {
        try {
            mmOutStream.write(bytes);
        } catch (IOException e) { }
    }
 
    /*調用這個方法關閉連接*/
    public void cancel() {
        try {
            mmSocket.close();
        } catch (IOException e) { }
    }
}

(END)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容