藍牙是十世紀的一位國王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(藍牙的通訊協議)傳輸數據。好比剛才的男單身狗和女單身狗在一起了····
接下來將如何查詢設備。
- 查詢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());
}
}
- 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對象給“丟棄”了。
實現過程如下:
- 通過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).
- 調用accept()實現監(jiān)聽
- 通過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) { }
}
}
下面講講客戶端的實現:
- 為了從serverSocket上獲取BluetoothSocket,你必須要先獲取BluetoothDevice。
- 通過BluetoothDevice的createRfcommSocketToServiceRecord(UUID)方法獲取BluetoothSocket的對象
- 通過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)