藍牙開發 java
首先,要操做藍牙,先要在AndroidManifest.xml里加入權限 android
<uses-permissionandroid:name="android.permission.BLUETOOTH_ADMIN" /> api
<uses-permissionandroid:name="android.permission.BLUETOOTH" /> 服務器
而後,看下api,Android全部關於藍牙開發的類都在android.bluetooth包下,只有8個類 app
1.BluetoothAdapter less
顧名思義,藍牙適配器,直到咱們創建bluetoothSocket鏈接以前,都要不斷操做它BluetoothAdapter裏的方法不少,經常使用的有如下幾個: 異步
cancelDiscovery() 根據字面意思,是取消發現,也就是說當咱們正在搜索設備的時候調用這個方法將再也不繼續搜索 socket
disable()關閉藍牙 ide
enable()打開藍牙,這個方法打開藍牙不會彈出提示,更多的時候咱們須要問下用戶是否打開,一下這兩行代碼一樣是打開藍牙,不過會提示用戶: 函數
Intemtenabler=new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enabler,reCode);//同startActivity(enabler);
getAddress()獲取本地藍牙地址
getDefaultAdapter()獲取默認BluetoothAdapter,實際上,也只有這一種方法獲取BluetoothAdapter
getName()獲取本地藍牙名稱
getRemoteDevice(String address)根據藍牙地址獲取遠程藍牙設備
getState()獲取本地藍牙適配器當前狀態(感受可能調試的時候更須要)
isDiscovering()判斷當前是否正在查找設備,是返回true
isEnabled()判斷藍牙是否打開,已打開返回true,不然,返回false
listenUsingRfcommWithServiceRecord(String name,UUID uuid)根據名稱,UUID建立並返回BluetoothServerSocket,這是建立BluetoothSocket服務器端的第一步
startDiscovery()開始搜索,這是搜索的第一步
2.BluetoothDevice
看名字就知道,這個類描述了一個藍牙設備
createRfcommSocketToServiceRecord(UUIDuuid)根據UUID建立並返回一個BluetoothSocket
getState() 藍牙狀態這裏要說一下,只有在 BluetoothAdapter.STATE_ON 狀態下才能夠監聽,具體能夠看andrid api;
這個方法也是咱們獲取BluetoothDevice的目的——建立BluetoothSocket
這個類其餘的方法,如getAddress(),getName(),同BluetoothAdapter
3.BluetoothServerSocket
若是去除了Bluetooth相信你們必定再熟悉不過了,既然是Socket,方法就應該都差很少,這個類一種只有三個方法兩個重載的accept(),accept(inttimeout)二者的區別在於後面的方法指定了過期時間,須要注意的是,執行這兩個方法的時候,直到接收到了客戶端的請求(或是過時以後),都會阻塞線程,應該放在新線程裏運行!
還有一點須要注意的是,這兩個方法都返回一個BluetoothSocket,最後的鏈接也是服務器端與客戶端的兩個BluetoothSocket的鏈接
close()這個就不用說了吧,翻譯一下——關閉!
4.BluetoothSocket
跟BluetoothServerSocket相對,是客戶端一共5個方法,不出意外,都會用到
close(),關閉
connect()鏈接
getInptuStream()獲取輸入流
getOutputStream()獲取輸出流
getRemoteDevice()獲取遠程設備,這裏指的是獲取bluetoothSocket指定鏈接的那個遠程藍牙設備
一、獲取本地藍牙適配器
BluetoothAdapter
mAdapter= BluetoothAdapter.getDefaultAdapter();
二、打開藍牙
if(!mAdapter.isEnabled()){
//彈出對話框提示用戶是後打開
Intent enabler = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enabler, REQUEST_ENABLE);
//不作提示,強行打開
// mAdapter.enable();
}
三、搜索設備
1)剛纔說過了mAdapter.startDiscovery()
是第一步,能夠你會發現沒有返回的藍牙設備,怎麼知道查找到了呢?向下看,不要急
2)定義BroadcastReceiver,關於BroadcastReceiver很少講了,不是今天的討論內容,代碼以下
[java] view plain copy
1. BroadcastReceiver mReceiver = new BroadcastReceiver() {
2. public void onReceive(Context context, Intent intent) {
3. String action = intent.getAction();
4. //找到設備
5. if (BluetoothDevice.ACTION_FOUND.equals(action)) {
6. BluetoothDevice device = intent
7. .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
8.
9. if (device.getBondState() != BluetoothDevice.BOND_BONDED) {
10.
11. Log.v(TAG, "find device:" + device.getName()
12. + device.getAddress());
13. }
14. }
15. //搜索完成
16. else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED
17. .equals(action)) {
18. setTitle("搜索完成");
19. if (mNewDevicesAdapter.getCount() == 0) {
20. Log.v(TAG, "find over");
21. }
22. }
23. }
24. };
這樣,沒當查找到新設備或是搜索完成,相應的操做都在上段代碼的兩個if裏執行了,不過前提是你要先註冊
BroadcastReceiver,具體代碼以下
[java] view plain copy
1. IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
2. registerReceiver(mReceiver, filter);
3. filter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
4. registerReceiver(mReceiver, filter)<span style="font-family:SimSun;">;</span>
(這段代碼,通常寫在onCreate()裏..)
四、創建鏈接,首先Android sdk(2.0以上版本)支持的藍牙鏈接是經過BluetoothSocket創建鏈接(說的不對請高人指正),服務器端(BluetoothServerSocket)和客戶端(BluetoothSocket)需指定一樣的UUID,才能創建鏈接,由於創建鏈接的方法會阻塞線程,因此服務器端和客戶端都應啓動新線程鏈接
1)服務器端:
//UUID格式通常是"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"可到
//http://www.uuidgenerator.com 申請
BluetoothServerSocket serverSocket = mAdapter. listenUsingRfcommWithServiceRecord(serverSocketName,UUID);
serverSocket.accept();
2)客戶端:
//還記得咱們剛纔在BroadcastReceiver獲取了BLuetoothDevice麼?
BluetoothSocket clienSocket=dcvice. createRfcommSocketToServiceRecord(UUID);
clienSocket.connect();
五、數據傳遞,經過以上操做,就已經創建的BluetoothSocket鏈接了,數據傳遞無非是經過流的形式
1)獲取流
inputStream = socket.getInputStream();
outputStream = socket.getOutputStream();
2)寫出、讀入
這是基礎的東西,在這就很少贅述了
終於寫完了,這是我這兩天的學習經驗,但願對有藍牙需求的朋友有所
補充一下,使設備可以被搜索
Intent enabler = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
startActivityForResult(enabler,REQUEST_DISCOVERABLE);
在作android藍牙串口鏈接的時候通常會使用
BluetoothSocket tmp = null;
// Get a BluetoothSocket for a connection with the
// given BluetoothDevice
try {
tmp = device.createRfcommSocketToServiceRecord(MY_UUID);
} catch (IOException e) {
Log.e(TAG, "create() failed", e);
}
而後是tmp賦給BluetoothSocket,接着調用connect方法進行藍牙設備的鏈接。
但是 BluetoothSocket 的connect方法自己就會報不少異常錯誤。
如下根據對藍牙開發的一點研究可經過如下方法解決:
方法1.先進行藍牙自動配對,配對成功,經過UUID得到BluetoothSocket,而後執行connect()方法。
方法2.經過UUID得到BluetoothSocket,而後先根據mDevice.getBondState()進行判斷是否須要配對,最後執行connnect()方法。
/** * <p> * 藍牙鏈接線程 * </p> * * @author lsw * */ private class ConnectThread extends Thread { String macAddress = ""; public ConnectThread(String mac) { macAddress = mac; } public void run() { connecting = true; connected = false; if(mBluetoothAdapter == null){ mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); } mBluetoothDevice = mBluetoothAdapter.getRemoteDevice(macAddress); mBluetoothAdapter.cancelDiscovery(); //initSocket(); try { socket = mBluetoothDevice.createRfcommSocketToServiceRecord(uuid); } catch (IOException e) { // TODO Auto-generated catch block //e.printStackTrace(); Log.e(TAG, "Socket", e); } //adapter.cancelDiscovery(); while (!connected && connetTime <= 10) { connectDevice(); } // 重置ConnectThread //synchronized (BluetoothService.this) { //ConnectThread = null; //} } public void cancel() { try { socket.close(); socket = null; } catch (Exception e) { e.printStackTrace(); } finally { connecting = false; } } }
接下來是調用的鏈接設備方法connectDevice():
protected void connectDevice() { try { // 鏈接創建以前的先配對 if (mBluetoothDevice.getBondState() == BluetoothDevice.BOND_NONE) { Method creMethod = BluetoothDevice.class .getMethod("createBond"); Log.e("TAG", "開始配對"); creMethod.invoke(mBluetoothDevice); } else { } } catch (Exception e) { // TODO: handle exception //DisplayMessage("沒法配對!"); e.printStackTrace(); } mBluetoothAdapter.cancelDiscovery(); try { socket.connect(); //DisplayMessage("鏈接成功!"); //connetTime++; connected = true; } catch (IOException e) { // TODO: handle exception //DisplayMessage("鏈接失敗!"); connetTime++; connected = false; try { socket.close(); socket = null; } catch (IOException e2) { // TODO: handle exception Log.e(TAG, "Cannot close connection when connection failed"); } } finally { connecting = false; } }
方法3.利用反射經過端口得到BluetoothSocket,而後執行connect()方法。
/** * <p> * 藍牙鏈接線程 * </p> * * @author lsw * */ private class ConnectThread extends Thread { String macAddress = ""; public ConnectThread(String mac) { macAddress = mac; } public void run() { connecting = true; connected = false; if(mBluetoothAdapter == null){ mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); } mBluetoothDevice = mBluetoothAdapter.getRemoteDevice(macAddress); mBluetoothAdapter.cancelDiscovery(); initSocket(); //adapter.cancelDiscovery(); while (!connected && connetTime <= 10) { try { socket.connect(); connected = true; } catch (IOException e1) { connetTime++; connected = false; // 關閉 socket try { socket.close(); socket = null; } catch (IOException e2) { //TODO: handle exception Log.e(TAG, "Socket", e2); } } finally { connecting = false; } //connectDevice(); } // 重置ConnectThread //synchronized (BluetoothService.this) { //ConnectThread = null; //} } public void cancel() { try { socket.close(); socket = null; } catch (Exception e) { e.printStackTrace(); } finally { connecting = false; } } }
接下來是初始化並獲得BluetoothSocket的方法
/** * 取得BluetoothSocket */ private void initSocket() { BluetoothSocket temp = null; try { Method m = mBluetoothDevice.getClass().getMethod( "createRfcommSocket", new Class[] { int.class }); temp = (BluetoothSocket) m.invoke(mBluetoothDevice, 1);//這裏端口爲1 } catch (SecurityException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } socket = temp; }
要點:1.藍牙配對和鏈接是兩回事,不可混爲一談。
2.藍牙串口鏈接可經過端口 (1-30)和UUID兩種方法進行操做。
3.經過UUID進行藍牙鏈接最好先進行配對操做。
1. 藍牙鏈接,不須要配對
因爲被曾經使用藍牙的思路所誤導,一直覺得使用藍牙是必須一個配對的過程的。實際上不是這樣。搜索到設備之後,直接去connect設備而不去配對,目前在我這裏是沒問題的,搜索到設備之後,能夠直接用一下代碼進行鏈接:
final String SPP_UUID = "00001101-0000-1000-8000-00805F9B34FB"; UUID uuid = UUID.fromString(SPP_UUID); BluetoothSocket socket; socket = device.createInsecureRfcommSocketToServiceRecord(uuid); adapter.cancelDiscovery(); socket.connect(); 這裏的UUID是比較好用的一個,設備均可以識別。
1. startDiscovey有可能啓動失敗
通常程序中會有兩步:開啓藍牙、開始尋找設備。以前我寫的代碼是用戶按下按鈕就直接順序執行這兩步,致使的結果就是常常性搜索失敗。仔細看了一下API,發現adapter.startDiscovery()函數是有一個boolean返回值的,也就是說若是啓動失敗就返回false。這就解釋了爲何會啓動失敗了:順序執行了開啓藍牙-尋找設備的步驟,可是因爲藍牙尚未徹底打開,就開始尋找設備,致使尋找失敗。因而最後我把代碼改爲了這樣,問題解決:
adapter = BluetoothAdapter.getDefaultAdapter(); if (adapter == null) { // 設備不支持藍牙 } // 打開藍牙 if (!adapter.isEnabled()) { adapter.enable(); adapter.cancelDiscovery(); } // 尋找藍牙設備,android會將查找到的設備以廣播形式發出去 while (!adapter.startDiscovery()){ Log.e("BlueTooth", "嘗試失敗"); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } }
2. 接收數據轉換
使用socket.getInputStream接收到的數據是字節流,這樣的數據是無法分析的。又因爲通常來講廠家給的協議都是相似於"FA 22 89 D0"這樣的十六進制數據,因此不少狀況須要一個byte轉十六進制String的函數:
public static String bytesToHex(byte[] bytes) { char[] hexChars = new char[bytes.length * 2]; for ( int j = 0; j < bytes.length; j++ ) { int v = bytes[j] & 0xFF; hexChars[j * 2] = hexArray[v >>> 4]; hexChars[j * 2 + 1] = hexArray[v & 0x0F]; } return new String(hexChars);}
1. 使用藍牙的響應權限
複製代碼代碼以下:
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
2. 配置本機藍牙模塊
在這裏首先要了解對藍牙操做一個核心類BluetoothAdapter
複製代碼代碼以下:
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
//直接打開系統的藍牙設置面板
Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(intent, 0x1);
//直接打開藍牙
adapter.enable();
//關閉藍牙
adapter.disable();
//打開本機的藍牙發現功能(默認打開120秒,能夠將時間最多延長至300秒)
Intent discoveryIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);//設置持續時間(最多300秒)
3.搜索藍牙設備
使用BluetoothAdapter的startDiscovery()方法來搜索藍牙設備
startDiscovery()方法是一個異步方法,調用後會當即返回。該方法會進行對其餘藍牙設備的搜索,該過程會持續12秒。該方法調用後,搜索過程其實是在一個System Service中進行的,因此能夠調用cancelDiscovery()方法來中止搜索(該方法能夠在未執行discovery請求時調用)。
請求Discovery後,系統開始搜索藍牙設備,在這個過程當中,系統會發送如下三個廣播:
ACTION_DISCOVERY_START:開始搜索
ACTION_DISCOVERY_FINISHED:搜索結束
ACTION_FOUND:找到設備,這個Intent中包含兩個extra fields:EXTRA_DEVICE和EXTRA_CLASS,分別包含BluetooDevice和BluetoothClass。
咱們能夠本身註冊相應的BroadcastReceiver來接收響應的廣播,以便實現某些功能
複製代碼代碼以下:
// 建立一個接收ACTION_FOUND廣播的BroadcastReceiver
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
// 發現設備
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
// 從Intent中獲取設備對象
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
// 將設備名稱和地址放入array adapter,以便在ListView中顯示
mArrayAdapter.add(device.getName() + "\n" + device.getAddress());
}
}
};
// 註冊BroadcastReceiver
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver(mReceiver, filter); // 不要忘了以後解除綁定
4. 藍牙Socket通訊
若是打算建議兩個藍牙設備之間的鏈接,則必須實現服務器端與客戶端的機制。當兩個設備在同一個RFCOMM channel下分別擁有一個鏈接的BluetoothSocket,這兩個設備才能夠說是創建了鏈接。
服務器設備與客戶端設備獲取BluetoothSocket的途徑是不一樣的。服務器設備是經過accepted一個incoming connection來獲取的,而客戶端設備則是經過打開一個到服務器的RFCOMM channel來獲取的。
服務器端的實現
經過調用BluetoothAdapter的listenUsingRfcommWithServiceRecord(String, UUID)方法來獲取BluetoothServerSocket(UUID用於客戶端與服務器端之間的配對)
調用BluetoothServerSocket的accept()方法監聽鏈接請求,若是收到請求,則返回一個BluetoothSocket實例(此方法爲block方法,應置於新線程中)
若是不想在accept其餘的鏈接,則調用BluetoothServerSocket的close()方法釋放資源(調用該方法後,以前得到的BluetoothSocket實例並無close。但因爲RFCOMM一個時刻只容許在一條channel中有一個鏈接,則通常在accept一個鏈接後,便close掉BluetoothServerSocket)
複製代碼代碼以下:
private class AcceptThread extends Thread {
private final BluetoothServerSocket mmServerSocket;
public AcceptThread() {
// Use a temporary object that is later assigned to mmServerSocket,
// because mmServerSocket is final
BluetoothServerSocket tmp = null;
try {
// MY_UUID is the app's UUID string, also used by the client code
tmp = mBluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);
} catch (IOException e) { }
mmServerSocket = tmp;
}
public void run() {
BluetoothSocket socket = null;
// Keep listening until exception occurs or a socket is returned
while (true) {
try {
socket = mmServerSocket.accept();
} catch (IOException e) {
break;
}
// If a connection was accepted
if (socket != null) {
// Do work to manage the connection (in a separate thread)
manageConnectedSocket(socket);
mmServerSocket.close();
break;
}
}
}
/** Will cancel the listening socket, and cause the thread to finish */
public void cancel() {
try {
mmServerSocket.close();
} catch (IOException e) { }
}
}
客戶端的實現
經過搜索獲得服務器端的BluetoothService
調用BluetoothService的listenUsingRfcommWithServiceRecord(String, UUID)方法獲取BluetoothSocket(該UUID應該同於服務器端的UUID)
調用BluetoothSocket的connect()方法(該方法爲block方法),若是UUID同服務器端的UUID匹配,而且鏈接被服務器端accept,則connect()方法返回
注意:在調用connect()方法以前,應當肯定當前沒有搜索設備,不然鏈接會變得很是慢而且容易失敗
複製代碼代碼以下:
private class ConnectThread extends Thread {
private final BluetoothSocket mmSocket;
private final BluetoothDevice mmDevice;
public ConnectThread(BluetoothDevice device) {
// Use a temporary object that is later assigned to mmSocket,
// because mmSocket is final
BluetoothSocket tmp = null;
mmDevice = device;
// Get a BluetoothSocket to connect with the given BluetoothDevice
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() {
// Cancel discovery because it will slow down the connection
mBluetoothAdapter.cancelDiscovery();
try {
// Connect the device through the socket. This will block
// until it succeeds or throws an exception
mmSocket.connect();
} catch (IOException connectException) {
// Unable to connect; close the socket and get out
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) { }
}
}
鏈接管理(數據通訊)
分別經過BluetoothSocket的getInputStream()和getOutputStream()方法獲取InputStream和OutputStream
使用read(bytes[])和write(bytes[])方法分別進行讀寫操做
注意:read(bytes[])方法會一直block,知道從流中讀取到信息,而write(bytes[])方法並非常常的block(好比在另外一設備沒有及時read或者中間緩衝區已滿的狀況下,write方法會block)
複製代碼代碼以下:
private class ConnectedThread extends Thread {
private final BluetoothSocket mmSocket;
private final InputStream mmInStream;
private final OutputStream mmOutStream;
public ConnectedThread(BluetoothSocket socket) {
mmSocket = socket;
InputStream tmpIn = null;
OutputStream tmpOut = null;
// Get the input and output streams, using temp objects because
// member streams are final
try {
tmpIn = socket.getInputStream();
tmpOut = socket.getOutputStream();
} catch (IOException e) { }
mmInStream = tmpIn;
mmOutStream = tmpOut;
}
public void run() {
byte[] buffer = new byte[1024]; // buffer store for the stream
int bytes; // bytes returned from read()
// Keep listening to the InputStream until an exception occurs
while (true) {
try {
// Read from the InputStream
bytes = mmInStream.read(buffer);
// Send the obtained bytes to the UI Activity
mHandler.obtainMessage(MESSAGE_READ, bytes, -1, buffer)
.sendToTarget();
} catch (IOException e) {
break;
}
}
}
/* Call this from the main Activity to send data to the remote device */
public void write(byte[] bytes) {
try {
mmOutStream.write(bytes);
} catch (IOException e) { }
}
/* Call this from the main Activity to shutdown the connection */
public void cancel() {
try {
mmSocket.close();
} catch (IOException e) { }
}
}
引用資料:Android官方SDK、《Android/OPhone徹底開發講義》
android4.0藍牙使能的詳細解析 本文詳細分析了android4.0 中藍牙使能的過程,相比較android2.3,4.0中的藍牙最大的差異在於UI上on/off的僞開關。在android4.0中加入了 adapter的狀態機。所謂的狀態機就相似於狀態轉換圖,在一個狀態收到某個特定的命令會變成另一個狀態,不一樣的命令能夠跳轉到不一樣的狀態(固然也有 可能到同一狀態)。adapter的初始狀態爲poweroff,在android系統啓動的時候會進入warmup狀態,同時會進行UUID的add, 該操做會引發propertychanged的UUID signal,該signal會使得狀態從warmup變換到hotoff狀態。所以在UI端off時其實adapter已經處於hotoff狀態而不是 poweroff狀態。這一點是很關鍵的。在正文中,我會從假如我不知道這些開始來描繪整個使能的過程。
正文:
毫無疑問,bluetooth的打開是在Settings中進行的操做。所以,冤有頭,債有主,咱們來到了Settings.java中,果真發現了相關的代碼以下:
mBluetoothEnabler =new BluetoothEnabler(context, new Switch(context));
因而,咱們得以進入真正的藍牙操做的殿堂,好好進去看看吧。
一、BluetoothEnabler的構造函數
public BluetoothEnabler(Context context,Switch switch_) {
mContext = context;
mSwitch = switch_;
//很簡單了,去調用一個LocalBluetoothManager類的getInstance,其實會構造該類的
LocalBluetoothManager manager =LocalBluetoothManager.getInstance(context);
if (manager == null) {
// Bluetooth is not supported
mLocalAdapter = null;
mSwitch.setEnabled(false);
} else {
//構形成功後,經過manager獲得bluetooth的adapter
mLocalAdapter =manager.getBluetoothAdapter();
}
//同時新建一個intent,用於接收ACTION_STATE_CHANGED
mIntentFilter = newIntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
}
二、LocalBluetoothManager類的getInstance
public static synchronizedLocalBluetoothManager getInstance(Context context) {
if (sInstance == null) {
//2.1一樣的,這個會去調用LocalBluetoothAdapter的getInstance,也會構造該類
LocalBluetoothAdapter adapter =LocalBluetoothAdapter.getInstance();
if (adapter == null) {
return null;
}
// This will be around as long asthis process is
Context appContext =context.getApplicationContext();
//2.2構造LocalBluetoothManager類
sInstance = newLocalBluetoothManager(adapter, appContext);
}
return sInstance;
}
2.1LocalBluetoothAdapter的getInstance
static synchronized LocalBluetoothAdaptergetInstance() {
if (sInstance == null) {
//2.1.1經過BluetoothAdapter獲得DefaultAdapter
BluetoothAdapter adapter =BluetoothAdapter.getDefaultAdapter();
if (adapter != null) {
//2.1.2如有該DefaultAdapter,則構造LocalBluetoothAdapter
sInstance = newLocalBluetoothAdapter(adapter);
}
}
return sInstance;
}
2.1.1BluetoothAdapter獲得DefaultAdapter
public static synchronized BluetoothAdaptergetDefaultAdapter() {
if (sAdapter == null) {
IBinder b =ServiceManager.getService(BluetoothAdapter.BLUETOOTH_SERVICE);
if (b != null) {
IBluetooth service =IBluetooth.Stub.asInterface(b);
sAdapter = newBluetoothAdapter(service);
}
}
return sAdapter;
}
2.1.2構造LocalBluetoothAdapter
//其實就是 mAdapter的初始化而已
privateLocalBluetoothAdapter(BluetoothAdapter adapter) {
mAdapter = adapter;
}
2.2構造LocalBluetoothManager類
//管理本地藍牙類,用來在藍牙API子類上面再封裝一個接口
privateLocalBluetoothManager(LocalBluetoothAdapter adapter, Context context) {
mContext = context;
//mLocalAdapter初始化爲DefaultAdapter中獲得的值
mLocalAdapter= adapter;
//構造CachedBluetoothDeviceManager,用來管理遠程藍牙設備
mCachedDeviceManager = newCachedBluetoothDeviceManager(context);
//2.2.1構建BluetoothEventManager,該類是用來管理廣播消息和回調函數的,即分發不一樣的消息去對UI進行處理
mEventManager = newBluetoothEventManager(mLocalAdapter,
mCachedDeviceManager, context);
//2.2.2該類提供對不一樣LocalBluetoothProfile object的訪問
mProfileManager = newLocalBluetoothProfileManager(context,
mLocalAdapter,mCachedDeviceManager, mEventManager);
}
2.2.1構建BluetoothEventManager
BluetoothEventManager(LocalBluetoothAdapteradapter,
CachedBluetoothDeviceManagerdeviceManager, Context context) {
mLocalAdapter = adapter;
mDeviceManager = deviceManager;
//建立兩個IntentFilter
mAdapterIntentFilter = newIntentFilter();
//這裏沒有對mProfileIntentFilter進行初始化,這個在LocalBluetoothProfileManager的addProfile中實現
mProfileIntentFilter = newIntentFilter();
//建立一個Handler的Hash表
mHandlerMap = new HashMap<String,Handler>();
mContext = context;
//註冊對adapter和Device的幾個廣播消息的處理回調函數
//add action到mAdapterIntentFilter
// Bluetooth on/off broadcasts
addHandler(BluetoothAdapter.ACTION_STATE_CHANGED, newAdapterStateChangedHandler());
// Discovery broadcasts
addHandler(BluetoothAdapter.ACTION_DISCOVERY_STARTED, newScanningStateChangedHandler(true));
addHandler(BluetoothAdapter.ACTION_DISCOVERY_FINISHED, newScanningStateChangedHandler(false));
addHandler(BluetoothDevice.ACTION_FOUND, new DeviceFoundHandler());
addHandler(BluetoothDevice.ACTION_DISAPPEARED, newDeviceDisappearedHandler());
addHandler(BluetoothDevice.ACTION_NAME_CHANGED, newNameChangedHandler());
// Pairing broadcasts
addHandler(BluetoothDevice.ACTION_BOND_STATE_CHANGED, newBondStateChangedHandler());
addHandler(BluetoothDevice.ACTION_PAIRING_CANCEL, newPairingCancelHandler());
// Fine-grained state broadcasts
addHandler(BluetoothDevice.ACTION_CLASS_CHANGED, newClassChangedHandler());
addHandler(BluetoothDevice.ACTION_UUID,new UuidChangedHandler());
// Dock event broadcasts
addHandler(Intent.ACTION_DOCK_EVENT,new DockEventHandler());
//mAdapterIntentFilter的接收處理函數
mContext.registerReceiver(mBroadcastReceiver, mAdapterIntentFilter);
}
2.2.2構造LocalBluetoothProfileManager類
LocalBluetoothProfileManager(Contextcontext,
LocalBluetoothAdapter adapter,
CachedBluetoothDeviceManagerdeviceManager,
BluetoothEventManager eventManager){
mContext = context;
//各個類之間進行關聯
mLocalAdapter = adapter;
mDeviceManager = deviceManager;
mEventManager = eventManager;
// pass this reference to adapter andevent manager (circular dependency)
mLocalAdapter.setProfileManager(this);
mEventManager.setProfileManager(this);
ParcelUuid[] uuids =adapter.getUuids();
// uuids may be null if Bluetooth isturned off
if (uuids != null) {
//假如已經有了uuid,根據uuid來add並new對應的profile,只針對A2DP,HFP,HSP,OPP四個profile,HID和PAN在下面,每次都add
updateLocalProfiles(uuids);
}
// Always add HID and PAN profiles
//加入HID和PAN兩個profile
mHidProfile = new HidProfile(context,mLocalAdapter);
addProfile(mHidProfile,HidProfile.NAME,
BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED);
mPanProfile = new PanProfile(context);
addPanProfile(mPanProfile,PanProfile.NAME,
BluetoothPan.ACTION_CONNECTION_STATE_CHANGED);
Log.d(TAG,"LocalBluetoothProfileManager construction complete");
}
好吧,其實咱們被騙了,剛剛只是一個路引,不是真正的操做,真正的操做向來都是從你滑動界面那個on/off鍵開始的,所以咱們決定把這個鍵的處理給揪出來。在Settings界面上一共就只有兩個on/off鍵,一個是wifi,另外一個就是藍牙了,咱們從這個代碼入手:
case HEADER_TYPE_SWITCH:
//其實寫這個代碼的人也比較心虛,假如switch多一點,下面就要重寫了
// Would need a differenttreatment if the main menu had more switches
if (header.id ==R.id.wifi_settings) {
mWifiEnabler.setSwitch(holder.switch_);
} else {
//這個就是處理了,上面的路引沒有白作啊
mBluetoothEnabler.setSwitch(holder.switch_);
}
三、mBluetoothEnabler.setSwitch分析
public void setSwitch(Switch switch_) {
//如果和上次相同,則不作任何事情,能夠理解,代碼也懶嘛
if (mSwitch == switch_) return;
//把上次的switch的changelistener清空
mSwitch.setOnCheckedChangeListener(null);
mSwitch = switch_;
//重設此次的switch的changelistener
mSwitch.setOnCheckedChangeListener(this);
int bluetoothState =BluetoothAdapter.STATE_OFF;
//獲取getBluetoothState,這個過程也會同步一下state,防止改變
if (mLocalAdapter != null)bluetoothState = mLocalAdapter.getBluetoothState();
//根據狀態設置一下兩個標誌位
boolean isOn = bluetoothState ==BluetoothAdapter.STATE_ON;
boolean isOff = bluetoothState ==BluetoothAdapter.STATE_OFF;
//設置checked的狀態位。注意,假如這裏狀態發生了改變,則會調用this.onCheckedChanged來進行處理
mSwitch.setChecked(isOn);
if(WirelessSettings.isRadioAllowed(mContext, Settings.System.RADIO_BLUETOOTH)) {
//有bluetooth或者不是airplane,則該switch不變灰,不然,灰的。
mSwitch.setEnabled(isOn || isOff);
} else {
mSwitch.setEnabled(false);
}
}
四、onCheckedChanged
在switch狀態發生改變後,會調用這個地方的回調函數進行處理。
public void onCheckedChanged(CompoundButtonbuttonView, boolean isChecked) {
// Show toast message if Bluetooth isnot allowed in airplane mode
//如果打開的話,就須要檢查一下是否allow Bluetooth(radio,airplane的check)
if (isChecked &&
!WirelessSettings.isRadioAllowed(mContext,Settings.System.RADIO_BLUETOOTH)) {
Toast.makeText(mContext,R.string.wifi_in_airplane_mode, Toast.LENGTH_SHORT).show();
// Reset switch to off
//如果不對的話,reset爲off
buttonView.setChecked(false);
}
if (mLocalAdapter != null) {
//4.1設置scanmode,放心,它會判斷state的,不是STATE_ON,會直接返回false的
mLocalAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE);
//4.2使能或不使能Bluetooth了
mLocalAdapter.setBluetoothEnabled(isChecked);
}
//過程當中仍是會反灰,直到setBluetoothEnabled的結果返回會改變switch的狀態
mSwitch.setEnabled(false);
}
4.1設置scanmod
會調用adapter中的setScanMode,直接去看就能夠了,事實上就是設置了兩個property標誌,沒什麼
public boolean setScanMode(int mode) {
//這裏把這個代碼寫出來就是證實一下,STATE_ON纔會真正作下去,不然免談
if (getState() != STATE_ON) returnfalse;
//這裏會調用對應server中的setScanMode
return setScanMode(mode, 120);
}
public synchronized boolean setScanMode(intmode, int duration) {
//這裏有個permission,好像和2.3中不同,注意一下 mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS,
"NeedWRITE_SECURE_SETTINGS permission");
boolean pairable;
boolean discoverable;
switch (mode) {
case BluetoothAdapter.SCAN_MODE_NONE:
pairable = false;
discoverable = false;
break;
caseBluetoothAdapter.SCAN_MODE_CONNECTABLE:
//開始就是這裏了,可pairable,可是不可discoverable
pairable = true;
discoverable = false;
break;
caseBluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE:
pairable = true;
discoverable = true;
if (DBG) Log.d(TAG, "BTDiscoverable for " + duration + " seconds");
break;
default:
Log.w(TAG, "Requested invalidscan mode " + mode);
return false;
}
//設置這兩個property標誌
setPropertyBoolean("Discoverable", discoverable);
setPropertyBoolean("Pairable", pairable);
return true;
}
4.2setBluetoothEnabled分析
public void setBluetoothEnabled(booleanenabled) {
//根據enabled的標誌設置是enable仍是disable,在2.3中,這個地方就是bt_enable哦,這裏還不知道,咱們在第5步進行詳細的分析
boolean success = enabled
? mAdapter.enable()
: mAdapter.disable();
//成功了,設置對應的狀態位
if (success) {
setBluetoothStateInt(enabled
?BluetoothAdapter.STATE_TURNING_ON
:BluetoothAdapter.STATE_TURNING_OFF);
} else {
if (Utils.V) {
Log.v(TAG,"setBluetoothEnabled call, manager didn't return " +
"success forenabled: " + enabled);
}
//同步一下設置的狀態
syncBluetoothState();
}
}
}
五、mAdapter.enable或者mAdapter.disable
就先分析enable吧,它會調用對應server端的enable(ture),咱們來看看源碼
public synchronized boolean enable(booleansaveSetting) {
mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
"Need BLUETOOTH_ADMIN permission");
// Airplane mode can prevent Bluetoothradio from being turned on.
//檢查是不是飛行模式
if (mIsAirplaneSensitive &&isAirplaneModeOn() && !mIsAirplaneToggleable) {
return false;
}
//5.1注意與2.3的不一樣,在2.3中,這裏會調用enablethread去調用native的bt_enable,而4.0沒有這麼作。沒事,咱們來分析4.0怎麼作的。
mBluetoothState.sendMessage(BluetoothAdapterStateMachine.USER_TURN_ON,saveSetting);
return true;
}
5.1mBluetoothState.sendMessage
簡單理解一下,mBluetoothState是BluetoothAdapterStateMachine類。所以,在分析的以前,簡單說一下,它其實就是相似一個狀態轉換圖,根據你所處於的狀態,而後再判斷收到的操做,進行不一樣的處理。根據構造函數中的setInitialState(mPowerOff);能夠知道初始狀態是PowerOff。可是從它給出的狀態機能夠看出,在PowerOff的狀態時,它是經過TURN_HOT/TURN_ON來改變到HotOff狀態的,而後纔會收到USER_TURN_ON,去該變到BluetootOn的狀態。所以,能夠確定的是咱們這裏的USER_TURN_ON不是它收到的第一個message,所以咱們去糾結一下它是從哪裏開始改變PowerOff的狀態:extra1,而後再來看這裏的處理吧:5.2。
extra一、mAdapter.enable以前的狀態機轉變
衆所周知,android在啓動以後會啓動一個serverThread的線程,經過這個線程會啓動一系列的服務。咱們的藍牙服務也是在這裏啓動的,android4.0其實在這個地方對狀態機進行了修改,咱們來看一下源碼:
該代碼位於framworks/base/services/java/com/android/server/systemserver.java
BluetoothServicebluetooth = null;
BluetoothA2dpServicebluetoothA2dp = null;
//模擬器上是不支持Bluetooth的,工廠測試模式也沒有Bluetooth(這個不瞭解)
// Skip Bluetooth if we have anemulator kernel
// TODO: Use a more reliable checkto see if this product should
// support Bluetooth - see bug988521
if(SystemProperties.get("ro.kernel.qemu").equals("1")) {
Slog.i(TAG, "No BluetoohService (emulator)");
} else if (factoryTest ==SystemServer.FACTORY_TEST_LOW_LEVEL) {
Slog.i(TAG, "No BluetoothService (factory test)");
} else {
Slog.i(TAG, "BluetoothService");
//新建Bluetoothservice,並把他加入到ServiceManager中
bluetooth = newBluetoothService(context);
ServiceManager.addService(BluetoothAdapter.BLUETOOTH_SERVICE,bluetooth);
//extra1.1在啓動Bluetooth服務後進行一些初始化,呵呵,這裏就對狀態機進行了改變
bluetooth.initAfterRegistration();
//新建了BluetoothA2dpService,並把之加入到了ServiceManager中
bluetoothA2dp= new BluetoothA2dpService(context, bluetooth);
ServiceManager.addService(BluetoothA2dpService.BLUETOOTH_A2DP_SERVICE,
bluetoothA2dp);
//extra1.2一樣的要在以後作些init的工做
bluetooth.initAfterA2dpRegistration();
//獲得是否飛行
int airplaneModeOn =Settings.System.getInt(mContentResolver,
Settings.System.AIRPLANE_MODE_ON, 0);
//看Bluetooth是否on,如果打開的狀態(沒有飛行),則這裏會調用enable去打開
int bluetoothOn =Settings.Secure.getInt(mContentResolver,
Settings.Secure.BLUETOOTH_ON, 0);
if (airplaneModeOn == 0&& bluetoothOn != 0) {
bluetooth.enable();
}
}
extra1.1initAfterRegistration分析
public synchronized voidinitAfterRegistration() {
//獲得default的adapter
mAdapter =BluetoothAdapter.getDefaultAdapter();
//建立BluetoothAdapterStateMachine,初始化幾個狀態,並設初始狀態位POWEROFF,這裏同時新建了一個EventLoop
mBluetoothState = newBluetoothAdapterStateMachine(mContext, this, mAdapter);
mBluetoothState.start();
//根據這個xml的bool變量來決定是否先期TURN_HOT,該變量位於frameworks/base/core/res/res/values/config.xml中,默認爲true
if (mContext.getResources().getBoolean
(com.android.internal.R.bool.config_bluetooth_adapter_quick_switch)) {
//extra1.2發送TURN_HOT的狀態變化message
mBluetoothState.sendMessage(BluetoothAdapterStateMachine.TURN_HOT);
}
//獲得對應的EventLoop
mEventLoop =mBluetoothState.getBluetoothEventLoop();
}
extra1.2 TURN_HOT message的處理
/**
* Bluetooth module's power is off,firmware is not loaded.
*/
private class PowerOff extends State {
@Override
public void enter() {
if (DBG) log("Enter PowerOff:" + getCurrentMessage().what);
}
@Override
public boolean processMessage(Messagemessage) {
log("PowerOff process message:" + message.what);
boolean retValue = HANDLED;
switch(message.what) {
……
case TURN_HOT:
//extra1.3這裏就是咱們尋找了千年的bt_enable所在的地方。咱們去看看
if (prepareBluetooth()) {
//extra1.5轉變狀態到warmup,在prepareBluetooth真正完成後,這個狀態還會發生改變
transitionTo(mWarmUp);
}
break;
……
extra1.3prepareBluetooth分析
看英文註釋就知道了,不解釋
/**
* Turn on Bluetooth Module, Loadfirmware, and do all the preparation
* needed to get the Bluetooth Moduleready but keep it not discoverable
* and not connectable.
* The last step of this method sets upthe local service record DB.
* There will be a event reporting thestatus of the SDP setup.
*/
private boolean prepareBluetooth() {
//extra1.4首先仍是調用了enableNative的本地方法,到這裏你會發現終於和2.3類似了(不過請注意調用的時機不一樣了,這個在初始化,而2.3在界面的on/off滑動的時候),它仍是會調用bt_enable,這個就會調用對應的set_bluetooth_power了
if(mBluetoothService.enableNative() != 0) {
return false;
}
// try to start event loop, give 2attempts
//嘗試兩次去start event loop
int retryCount = 2;
boolean eventLoopStarted = false;
while ((retryCount-- > 0)&& !eventLoopStarted) {
mEventLoop.start();
// it may take a moment for theother thread to do its
// thing. Check periodically for a while.
int pollCount = 5;
while ((pollCount-- > 0)&& !eventLoopStarted) {
if(mEventLoop.isEventLoopRunning()) {
eventLoopStarted =true;
break;
}
try {
Thread.sleep(100);
} catch(InterruptedException e) {
log("prepareBluetooth sleep interrupted: " + pollCount);
break;
}
}
}
//出錯處理
if (!eventLoopStarted) {
mBluetoothService.disableNative();
return false;
}
// get BluetoothService ready
//創建native data以及SDP相關的一些操做,這裏將會產生PropertyChanged的UUIDs的signal,對該信號的處理會對狀態發生改變,詳細分析見extra1.5
if(!mBluetoothService.prepareBluetooth()) {
mEventLoop.stop();
mBluetoothService.disableNative();
return false;
}
//設置一個prepare的超時處理,在該時間內沒有收到UUID changed的signal將會進行錯誤處理
sendMessageDelayed(PREPARE_BLUETOOTH_TIMEOUT,PREPARE_BLUETOOTH_TIMEOUT_TIME);
return true;
}
}
extra1.4bt_enable分析
intbt_enable() {
LOGV(__FUNCTION__);
int ret = -1;
int hci_sock = -1;
int attempt;
//power的設置,on。不解釋,可加入對應板子的gpio口的處理,默認就只用了rfkill的處理
if (set_bluetooth_power(1) < 0) gotoout;
//開始hciattach服務,這個咱們也作了修改,加入了rtk_h5
LOGI("Starting hciattachdaemon");
if (property_set("ctl.start","hciattach") < 0) {
LOGE("Failed to starthciattach");
set_bluetooth_power(0);
goto out;
}
// Try for 10 seconds, this can onlysucceed once hciattach has sent the
// firmware and then turned on hci devicevia HCIUARTSETPROTO ioctl
for (attempt = 1000; attempt > 0; attempt--) {
//建立hci_sock
hci_sock = create_hci_sock();
if (hci_sock < 0) goto out;
//調用ioctl的HCIDEVUP,來判斷hciattach是否已經ok了。
ret = ioctl(hci_sock, HCIDEVUP,HCI_DEV_ID);
LOGI("bt_enable: ret: %d, errno:%d", ret, errno);
if (!ret) {
break;
} else if (errno == EALREADY) {
LOGW("Bluetoothd alreadystarted, unexpectedly!");
break;
}
close(hci_sock);
//等待10 ms後再試一次
usleep(100000); // 100 ms retry delay
}
//10s都沒有搞定,須要作個失敗的處理
if (attempt == 0) {
LOGE("%s: Timeout waiting for HCIdevice to come up, error- %d, ",
__FUNCTION__, ret);
if (property_set("ctl.stop","hciattach") < 0) {
LOGE("Error stoppinghciattach");
}
set_bluetooth_power(0);
goto out;
}
//啓動bluetoothd服務
LOGI("Starting bluetoothddeamon");
if (property_set("ctl.start","bluetoothd") < 0) {
LOGE("Failed to startbluetoothd");
set_bluetooth_power(0);
goto out;
}
ret = 0;
out:
//關閉hci_sock
if (hci_sock >= 0) close(hci_sock);
return ret;
}
extra 1.5 PropetyChanged的UUIDs的處理
event_filter是用來對bluez的dbus的signal進行監聽的,有signal產生後,會在這裏進行處理。所以,咱們直接到這裏看看該怎麼處理。
//Called by dbus during WaitForAndDispatchEventNative()
staticDBusHandlerResult event_filter(DBusConnection *conn, DBusMessage *msg,
void*data) {
native_data_t *nat;
JNIEnv *env;
DBusError err;
DBusHandlerResult ret;
//err的一個初始化
dbus_error_init(&err);
//獲得參數
nat = (native_data_t *)data;
nat->vm->GetEnv((void**)&env,nat->envVer);
if (dbus_message_get_type(msg) !=DBUS_MESSAGE_TYPE_SIGNAL) {
LOGV("%s: not interested (not asignal).", __FUNCTION__);
returnDBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
LOGV("%s: Received signal %s:%s from%s", __FUNCTION__,
dbus_message_get_interface(msg),dbus_message_get_member(msg),
dbus_message_get_path(msg));
env->PushLocalFrame(EVENT_LOOP_REFS);
……
//PropertyChanged這個signal的處理
} else if (dbus_message_is_signal(msg,
"org.bluez.Adapter",
"PropertyChanged")) {
//由msg解析參數
jobjectArray str_array =parse_adapter_property_change(env, msg);
if (str_array != NULL) {
/* Check if bluetoothd has(re)started, if so update the path. */
jstring property =(jstring)env->GetObjectArrayElement(str_array, 0);
const char *c_property =env->GetStringUTFChars(property, NULL);
//檢查Property是否started
if (!strncmp(c_property,"Powered", strlen("Powered"))) {
//如果powered,則看value是不是true,是ture就獲得對應的path
jstring value =
(jstring)env->GetObjectArrayElement(str_array, 1);
const char *c_value =env->GetStringUTFChars(value, NULL);
if (!strncmp(c_value,"true", strlen("true")))
nat->adapter =get_adapter_path(nat->conn);
env->ReleaseStringUTFChars(value, c_value);
}
env->ReleaseStringUTFChars(property, c_property);
//extra1.6調用對應的method_onPropertyChanged函數,該method對應的onPropertyChanged函數
env->CallVoidMethod(nat->me,
method_onPropertyChanged,
str_array);
} elseLOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, msg);
goto success;
……
extra1.6真正的處理函數onPropertyChanged分析
/**
* Called by native code on aPropertyChanged signal from
* org.bluez.Adapter. This method is alsocalled from
* {@link BluetoothAdapterStateMachine} toset the "Pairable"
* property when Bluetooth is enabled.
*
* @param propValues a string arraycontaining the key and one or more
* values.
*/
/*package*/ void onPropertyChanged(String[]propValues) {
BluetoothAdapterPropertiesadapterProperties =
mBluetoothService.getAdapterProperties();
//先fill up cache
if (adapterProperties.isEmpty()) {
// We have got a property changebefore
// we filled up our cache.
adapterProperties.getAllProperties();
}
log("Property Changed: " +propValues[0] + " : " + propValues[1]);
String name = propValues[0];
……
//對UUIDs的處理
} else if(name.equals("Devices") || name.equals("UUIDs")) {
String value = null;
int len =Integer.valueOf(propValues[1]);
if (len > 0) {
StringBuilder str = newStringBuilder();
for (int i = 2; i <propValues.length; i++) {
str.append(propValues[i]);
str.append(",");
}
value = str.toString();
}
//把name和value值加入到property的map中
adapterProperties.setProperty(name,value);
//extra1.7有UUIDs的change signal會刷新Bluetooth的State
if (name.equals("UUIDs")){
mBluetoothService.updateBluetoothState(value);
}
//對Pairable和Discoverable的處理
} else if(name.equals("Pairable") || name.equals("Discoverable")) {
adapterProperties.setProperty(name,propValues[1]);
if(name.equals("Discoverable")) {
//5.6發送SCAN_MODE_CHANGED的msg,去改變狀態機 mBluetoothState.sendMessage(BluetoothAdapterStateMachine.SCAN_MODE_CHANGED);
}
//設置對應的property
String pairable =name.equals("Pairable") ? propValues[1] :
adapterProperties.getProperty("Pairable");
String discoverable =name.equals("Discoverable") ? propValues[1] :
adapterProperties.getProperty("Discoverable");
// This shouldn't happen, unlessAdapter Properties are null.
if (pairable == null ||discoverable == null)
return;
int mode =BluetoothService.bluezStringToScanMode(
pairable.equals("true"),
discoverable.equals("true"));
if (mode >= 0) {
//當pairable和discoverable均爲true的時候,會發送一個ACTION_SCAN_MODE_CHANGED的廣播消息
Intent intent = newIntent(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED);
intent.putExtra(BluetoothAdapter.EXTRA_SCAN_MODE, mode);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
mContext.sendBroadcast(intent,BLUETOOTH_PERM);
}
}
……
extra1.7 UUIDs改變帶來的State的刷新
/**
* This function is called from BluetoothEvent Loop when onPropertyChanged
* for adapter comes in with UUID property.
* @param uuidsThe uuids of adapter asreported by Bluez.
*/
/*package*/ synchronized voidupdateBluetoothState(String uuids) {
ParcelUuid[] adapterUuids =convertStringToParcelUuid(uuids);
//爲何必須包含全部已經有的uuid??感受有點反了,再看看
if (mAdapterUuids != null &&
BluetoothUuid.containsAllUuids(adapterUuids, mAdapterUuids)) {
//放SERVICE_RECORD_LOADED的信息,此時,處於warm up狀態,看extra1.8分析狀態如何繼續改變 mBluetoothState.sendMessage(BluetoothAdapterStateMachine.SERVICE_RECORD_LOADED);
}
}
extra1.8 UUIDs對狀態機改變
/**
* Turning on Bluetooth module's power,loading firmware, starting
* event loop thread to listen on Bluetoothmodule event changes.
*/
private class WarmUp extends State {
@Override
public void enter() {
if (DBG) log("Enter WarmUp:" + getCurrentMessage().what);
}
@Override
public boolean processMessage(Messagemessage) {
log("WarmUp process message:" + message.what);
boolean retValue = HANDLED;
switch(message.what) {
case SERVICE_RECORD_LOADED:
//能夠看到,首先會把當時從poweroff過來的一個超時message拿remove了。
removeMessages(PREPARE_BLUETOOTH_TIMEOUT);
//轉到hotoff狀態,在hotoff狀態仍會接收到多個SERVICE_RECORD_LOADED的msg,可是那個狀態下該msg將沒有任何handled,所以會一直處於hotoff狀態
transitionTo(mHotOff);
break;
……
5.2mAdapter.enable中mBluetoothState.sendMessage後的狀態機處理
由extra的分析可知,此時,Bluetooth的State已經處於HotOff狀態了,因此,從這裏開始處理State的變換。
/**
* Bluetooth Module has powered, firmwareloaded, event loop started,
* SDP loaded, but the modules staysnon-discoverable and
* non-connectable.
*/
private class HotOff extends State {
@Override
public void enter() {
if (DBG) log("Enter HotOff:" + getCurrentMessage().what);
}
@Override
public boolean processMessage(Messagemessage) {
log("HotOff process message:" + message.what);
boolean retValue = HANDLED;
switch(message.what) {
case USER_TURN_ON:
//發出BluetoothAdapter.STATE_TURNING_ON的廣播消息
broadcastState(BluetoothAdapter.STATE_TURNING_ON);
if ((Boolean) message.obj){
//就是把Settings.Secure.BLUETOOTH_ON設爲1。用於標誌Bluetooth enable了
persistSwitchSetting(true);
}
// let it fall toTURN_ON_CONTINUE:
//$FALL-THROUGH$
//注意上面沒有break哦
case TURN_ON_CONTINUE:
//這裏就是把Bluetooth設爲connectable就是Powered=1,這裏就把prepareBluetooth中設置的不可鏈接從新設置回來了。這個重連會產生一些新的變化,它會發送WRITE_SCAN_ENABLE的cmd,所以在該cmd_complete時會有一些新的處理:5.3,它會再次引發狀態機的改變:5.6
mBluetoothService.switchConnectable(true);
//進入到Switching狀態
transitionTo(mSwitching);
break;
……
5.3 WRITE_SCAN_ENABLE在cmd_complete後的處理
在bluez中是用cmd_complete函數來監視發出cmd完成後的處理的。該函數具體以下:
staticinline void cmd_complete(int index, void *ptr)
{
structdev_info *dev = &devs[index];
evt_cmd_complete*evt = ptr;
uint16_topcode = btohs(evt->opcode);
uint8_tstatus = *((uint8_t *) ptr + EVT_CMD_COMPLETE_SIZE);
switch(opcode) {
……
//WRITE_SCAN_ENABLE命令完成的處理函數,會再發一個READ_SCAN_ENABLE的命令
casecmd_opcode_pack(OGF_HOST_CTL, OCF_WRITE_SCAN_ENABLE):
hci_send_cmd(dev->sk,OGF_HOST_CTL, OCF_READ_SCAN_ENABLE,
0,NULL);
break;
//5.4緊接着就是對READ_SCAN_ENABLE命令完成的處理,它是經過read_scan_complete來實現的
casecmd_opcode_pack(OGF_HOST_CTL, OCF_READ_SCAN_ENABLE):
ptr+= sizeof(evt_cmd_complete);
read_scan_complete(index,status, ptr);
break;
……
}
5.4 read_scan命令完成的處理
staticvoid read_scan_complete(int index, uint8_t status, void *ptr)
{
structbtd_adapter *adapter;
read_scan_enable_rp*rp = ptr;
DBG("hci%dstatus %u", index, status);
//由index獲得對應的adapter
adapter= manager_find_adapter_by_id(index);
if(!adapter) {
error("Unableto find matching adapter");
return;
}
//5.5這裏算是一個通知adapter,mode改變了。
adapter_mode_changed(adapter,rp->enable);
}
5.5通知adapter,mode發生了改變
voidadapter_mode_changed(struct btd_adapter *adapter, uint8_t scan_mode)
{
constgchar *path = adapter_get_path(adapter);
gbooleandiscoverable, pairable;
DBG("old0x%02x new 0x%02x", adapter->scan_mode, scan_mode);
//若相同,則nothing todo
if(adapter->scan_mode == scan_mode){
#ifdefBOARD_HAVE_BLUETOOTH_BCM
/*we may reset scan_mode already inbtd_adapter_stop(), so comes to here*/
set_mode_complete(adapter);
#endif
return;
}
//把discoverable的timeout清空
adapter_remove_discov_timeout(adapter);
//這裏開始,是設爲SCAN_PAGE| SCAN_INQUIRY
switch(scan_mode) {
caseSCAN_DISABLED:
adapter->mode= MODE_OFF;
discoverable= FALSE;
pairable= FALSE;
break;
caseSCAN_PAGE:
adapter->mode= MODE_CONNECTABLE;
discoverable= FALSE;
pairable= adapter->pairable;
break;
case(SCAN_PAGE | SCAN_INQUIRY):
//設一下模式,在有reply要求的狀況下,該步驟仍是很重要的
adapter->mode= MODE_DISCOVERABLE;
discoverable= TRUE;
pairable= adapter->pairable;
//還要設一個discoverable的時間
if(adapter->discov_timeout != 0)
adapter_set_discov_timeout(adapter,
adapter->discov_timeout);
break;
caseSCAN_INQUIRY:
/*Address the scenario where a low-level application like
* hciconfig changed the scan mode */
if(adapter->discov_timeout != 0)
adapter_set_discov_timeout(adapter,
adapter->discov_timeout);
/*ignore, this event should not be sent */
default:
/*ignore, reserved */
return;
}
/*If page scanning gets toggled emit the Pairable property */
//這裏會發一個property_changed的pairable的signal
if((adapter->scan_mode & SCAN_PAGE) != (scan_mode & SCAN_PAGE))
emit_property_changed(connection,adapter->path,
ADAPTER_INTERFACE,"Pairable",
DBUS_TYPE_BOOLEAN,&pairable);
if(!discoverable)
adapter_set_limited_discoverable(adapter,FALSE);
//這裏會發一個property_changed的discoverable的signal
emit_property_changed(connection,path,
ADAPTER_INTERFACE,"Discoverable",
DBUS_TYPE_BOOLEAN,&discoverable);
adapter->scan_mode= scan_mode;
set_mode_complete(adapter);
}
5.6 WRTIE_SCAN_ENABLE最終引發的狀態機的變化
在此以前,狀態機處於switching的狀態,收到了SCAN_MODE_CHANGED的msg。
private class Switching extends State {
@Override
public void enter() {
if (DBG) log("Enter Switching:" + getCurrentMessage().what);
}
@Override
public boolean processMessage(Messagemessage) {
log("Switching processmessage: " + message.what);
boolean retValue = HANDLED;
switch(message.what) {
case SCAN_MODE_CHANGED:
// This event matchesmBluetoothService.switchConnectable action
//mPublicState在hotoff到swtiching狀態變化時已經被設爲STATE_TURNING_ON了,因此這裏if沒有問題
if (mPublicState ==BluetoothAdapter.STATE_TURNING_ON) {
// set pairable if it'snot
//設置爲pairable假如尚未設置的話,這個會先在bluez中檢查一下當前是否pairable,咱們在前面已經設置好了,因此,這裏只是一個檢查而已,沒有什麼實際性的工做
mBluetoothService.setPairable();
//初始化bond state和profile state,這個會在adapter pairable以後,bluetooth turn on以前發生
mBluetoothService.initBluetoothAfterTurningOn();
//這邊正式進入到bluetoothon的狀態,終於進了這裏,哎。。。
transitionTo(mBluetoothOn);
//發送STATE_ON的broadcast
broadcastState(BluetoothAdapter.STATE_ON);
// run bluetooth nowthat it's turned on
// Note runBluetoothshould be called only in adapter STATE_ON
//鏈接那些能夠自動鏈接的設備,通知battery,藍牙打開了 mBluetoothService.runBluetooth(); } break; …… 至此,藍牙的使能主要過程已經所有搞定。