接著上一篇沒有完成的任務(wù),我們繼續(xù)分析這個(gè)藍(lán)牙聊天程序的實(shí)現(xiàn),本文主要包括以下兩個(gè)部分的內(nèi)容:其一,分析掃描設(shè)備部分DeviceListActivity,其二,分析具體的聊天過程的完整通信方案,包括端口監(jiān)聽、鏈接配對、消息發(fā)送和接收等,如果有對上一篇文章不太熟悉的,可以返回去在過一次,這樣會有利于本文的理解。
設(shè)備掃描(DeviceListActivity) 在上一篇文章的介紹中,當(dāng)用戶點(diǎn)擊了掃描按鈕之后,則會執(zhí)行如下代碼:
// 啟動(dòng)DeviceListActivity查看設(shè)備并掃描
Intent serverIntent = new Intent(this, DeviceListActivity.class);
startActivityForResult(serverIntent, REQUEST_CONNECT_DEVICE);
該代碼將跳轉(zhuǎn)到DeviceListActivity進(jìn)行設(shè)備的掃描,并且通過REQUEST_CONNECT_DEVICE來請求鏈接掃描到的設(shè)備。從AndroidManifest.xml文件中我們知道DeviceListActivity將為定義為一個(gè)對話框的風(fēng)格,下圖是該應(yīng)用程序中,掃描藍(lán)牙設(shè)備的截圖。
其中DeviceListActivity則為圖中對話框部分,其界面的布局如下代碼所示。
"http://schemas./apk/res/android"
android:orientation= "vertical"
android:layout_width= "match_parent"
android:layout_height= "match_parent"
>
"@+id/title_paired_devices"
android:layout_width= "match_parent"
android:layout_height= "wrap_content"
android:text= "@string/title_paired_devices"
android:visibility= "gone"
android:background= "#666"
android:textColor= "#fff"
android:paddingLeft= "5dp"
/>
"@+id/paired_devices"
android:layout_width= "match_parent"
android:layout_height= "wrap_content"
android:stackFromBottom= "true"
android:layout_weight= "1"
/>
"@+id/title_new_devices"
android:layout_width= "match_parent"
android:layout_height= "wrap_content"
android:text= "@string/title_other_devices"
android:visibility= "gone"
android:background= "#666"
android:textColor= "#fff"
android:paddingLeft= "5dp"
/>
"@+id/new_devices"
android:layout_width= "match_parent"
android:layout_height= "wrap_content"
android:stackFromBottom= "true"
android:layout_weight= "2"
/>
"@+id/button_scan"
android:layout_width= "match_parent"
android:layout_height= "wrap_content"
android:text= "@string/button_scan"
/>
該布局整體由一個(gè)線性布局LinearLayout組成,其中包含了兩個(gè)textview中來顯示已經(jīng)配對的設(shè)備和信掃描出來的設(shè)備(還沒有經(jīng)過配對)和兩個(gè)ListView分別用于顯示已經(jīng)配對和沒有配對的設(shè)備的相關(guān)信息。按鈕則用于執(zhí)行掃描過程用,整個(gè)結(jié)構(gòu)很簡單,下面我們開始分析如何編碼實(shí)現(xiàn)了。 同樣開始之前,我們先確定該類中的變量的作用,定義如下:
public class DeviceListActivity extends Activity {
// Debugging
private static final String TAG = "DeviceListActivity";
private static final boolean D = true;
// Return Intent extra
public static String EXTRA_DEVICE_ADDRESS = "device_address";
// 藍(lán)牙適配器
private BluetoothAdapter mBtAdapter;
//已經(jīng)配對的藍(lán)牙設(shè)備
private ArrayAdapter mPairedDevicesArrayAdapter;
//新的藍(lán)牙設(shè)備
private ArrayAdapter mNewDevicesArrayAdapter;
其中Debugging部分,同樣用于調(diào)試,這里定義了一個(gè)EXTRA_DEVICE_ADDRESS,用于在通過Intent傳遞數(shù)據(jù)時(shí)的附加信息,即設(shè)備的地址,當(dāng)掃描出來之后,返回到BluetoothChat中的onActivityResult函數(shù)的REQUEST_CONNECT_DEVICE命令,這是我們就需要通過DeviceListActivity.EXTRA_DEVICE_ADDRESS來取得該設(shè)備的Mac地址,因此當(dāng)我們掃描完成之后在反饋掃描結(jié)果時(shí)就需要綁定設(shè)備地址作為EXTRA_DEVICE_ADDRESS的附加值,這和我們上一篇介紹的并不矛盾。另外其他幾個(gè)變量則分別是本地藍(lán)牙適配器、已經(jīng)配對的藍(lán)牙列表和掃描出來還沒有配對的藍(lán)牙設(shè)備列表,稍后我們可以看到對他們的使用。 進(jìn)入DeviceListActivity之后我們首先分析onCreate,首先通過如下代碼對窗口進(jìn)行了設(shè)置:
// 設(shè)置窗口
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
setContentView(R.layout.device_list);
setResult(Activity.RESULT_CANCELED);
這里我們設(shè)置了窗口需要帶一個(gè)進(jìn)度條,當(dāng)我們在掃描時(shí)就看有很容易的高速用戶掃描進(jìn)度。具體布局則設(shè)置為device_list.xml也是我們文本第一段代碼的內(nèi)容,接下來首先初始化掃描按鈕,代碼如下:
// 初始化掃描按鈕
Button scanButton = (Button) findViewById(R.id.button_scan);
scanButton.setOnClickListener( new OnClickListener() {
public void onClick(View v) {
doDiscovery();
v.setVisibility(View.GONE);
}
});
首先取得按鈕對象,然后為其設(shè)置一個(gè)事件監(jiān)聽,當(dāng)事件觸發(fā)時(shí)就通過doDiscovery函數(shù)來執(zhí)行掃描操作即可,具體掃描過程稍后分析。 然后需要初始化用來顯示設(shè)備的列表和數(shù)據(jù)源,使用如下代碼即可:
//初始化ArrayAdapter,一個(gè)是已經(jīng)配對的設(shè)備,一個(gè)是新發(fā)現(xiàn)的設(shè)備
mPairedDevicesArrayAdapter = new ArrayAdapter( this, R.layout.device_name);
mNewDevicesArrayAdapter = new ArrayAdapter( this, R.layout.device_name);
// 檢測并設(shè)置已配對的設(shè)備ListView
ListView pairedListView = (ListView) findViewById(R.id.paired_devices);
pairedListView.setAdapter(mPairedDevicesArrayAdapter);
pairedListView.setOnItemClickListener(mDeviceClickListener);
// 檢查并設(shè)置行發(fā)現(xiàn)的藍(lán)牙設(shè)備ListView
ListView newDevicesListView = (ListView) findViewById(R.id.new_devices);
newDevicesListView.setAdapter(mNewDevicesArrayAdapter);
newDevicesListView.setOnItemClickListener(mDeviceClickListener);
并分別對這些列表中的選項(xiàng)設(shè)置了監(jiān)聽mDeviceClickListener,用來處理,當(dāng)選擇該選項(xiàng)時(shí),就進(jìn)行鏈接和配對操作。既然是掃描,我們就需要對掃描的結(jié)果進(jìn)行監(jiān)控,這里我們構(gòu)建了一個(gè)廣播BroadcastReceiver來對掃描的結(jié)果進(jìn)行處理,代碼如下:
// 當(dāng)一個(gè)設(shè)備被發(fā)現(xiàn)時(shí),需要注冊一個(gè)廣播
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
this.registerReceiver(mReceiver, filter);
// 當(dāng)顯示檢查完畢的時(shí)候,需要注冊一個(gè)廣播
filter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
this.registerReceiver(mReceiver, filter);
這里我們注冊到廣播mReceiver的IntentFilter主要包括了發(fā)現(xiàn)藍(lán)牙設(shè)備(BluetoothDevice.ACTION_FOUND)和掃描結(jié)束(BluetoothAdapter.ACTION_DISCOVERY_FINISHED),稍后我們分析如何在mReceiver中來處理這些事件。 最后我們需要取得本地藍(lán)牙適配器和一些初始的藍(lán)牙設(shè)備數(shù)據(jù)顯示列表進(jìn)行處理,代碼如下:
// 得到本地的藍(lán)牙適配器
mBtAdapter = BluetoothAdapter.getDefaultAdapter();
// 得到一個(gè)已經(jīng)匹配到本地適配器的BluetoothDevice類的對象集合
Set pairedDevices = mBtAdapter.getBondedDevices();
// 如果有配對成功的設(shè)備則添加到ArrayAdapter
if (pairedDevices.size() > 0 ) {
findViewById(R.id.title_paired_devices).setVisibility(View.VISIBLE);
for (BluetoothDevice device : pairedDevices) {
mPairedDevicesArrayAdapter.add(device.getName() + "\n" + device.getAddress());
}
} else {
//否則添加一個(gè)沒有被配對的字符串
String noDevices = getResources().getText(R.string.none_paired).toString();
mPairedDevicesArrayAdapter.add(noDevices);
}
首先通過藍(lán)牙適配器的getBondedDevices函數(shù)取得已經(jīng)配對的藍(lán)牙設(shè)備,并將其添加到mPairedDevicesArrayAdapter數(shù)據(jù)源中,會顯示到pairedListView列表視圖中,如果沒有已經(jīng)配對的藍(lán)牙設(shè)備,則顯示一個(gè)R.string.none_paired字符串表示目前沒有配對成功的設(shè)備。 onDestroy函數(shù)中會制定銷毀操作,主要包括藍(lán)牙適配器和廣播的注銷操作,代碼如下:
@Override
protected void onDestroy() {
super.onDestroy();
// 確保我們沒有發(fā)現(xiàn),檢測設(shè)備
if (mBtAdapter != null) {
mBtAdapter.cancelDiscovery();
}
// 卸載所注冊的廣播
this.unregisterReceiver(mReceiver);
}
對于藍(lán)牙適配器的取消方式則調(diào)用cancelDiscovery()函數(shù)即可,卸載mReceiver則需要調(diào)用unregisterReceiver即可。 做好初始化工作之后,下面我們開始分析掃描函數(shù)doDiscovery(),其掃描過程的實(shí)現(xiàn)很就簡單,代碼如下:
/**
* 請求能被發(fā)現(xiàn)的設(shè)備
*/
private void doDiscovery() {
if (D) Log.d(TAG, "doDiscovery()");
// 設(shè)置顯示進(jìn)度條
setProgressBarIndeterminateVisibility( true);
// 設(shè)置title為掃描狀態(tài)
setTitle(R.string.scanning);
// 顯示新設(shè)備的子標(biāo)題
findViewById(R.id.title_new_devices).setVisibility(View.VISIBLE);
// 如果已經(jīng)在請求現(xiàn)實(shí)了,那么就先停止
if (mBtAdapter.isDiscovering()) {
mBtAdapter.cancelDiscovery();
}
// 請求從藍(lán)牙適配器得到能夠被發(fā)現(xiàn)的設(shè)備
mBtAdapter.startDiscovery();
}
首先通過setProgressBarIndeterminateVisibility將進(jìn)度條設(shè)置為顯示狀態(tài),設(shè)置標(biāo)題title為R.string.scanning字符串,表示正在掃描中,代碼中所說的新設(shè)備的子標(biāo)題,其實(shí)就是上面我們所說的掃描到的沒有經(jīng)過配對的設(shè)備的title,對應(yīng)于R.id.title_new_devices。掃描之前我們首先通過isDiscovering函數(shù)檢測當(dāng)前是否正在掃描,如果正在掃描則調(diào)用cancelDiscovery函數(shù)來取消當(dāng)前的掃描,最后調(diào)用startDiscovery函數(shù)開始執(zhí)行掃描操作。 現(xiàn)在已經(jīng)開始掃描了,下面我們就需要對掃描過程進(jìn)行監(jiān)控和對掃描的結(jié)果進(jìn)行處理。即我們所定義的廣播mReceiver,其實(shí)現(xiàn)如下所示。
//監(jiān)聽掃描藍(lán)牙設(shè)備
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
// 當(dāng)發(fā)現(xiàn)一個(gè)設(shè)備時(shí)
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
// 從Intent得到藍(lán)牙設(shè)備對象
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
// 如果已經(jīng)配對,則跳過,因?yàn)樗呀?jīng)在設(shè)備列表中了
if (device.getBondState() != BluetoothDevice.BOND_BONDED) {
//否則添加到設(shè)備列表
mNewDevicesArrayAdapter.add(device.getName() + "\n" + device.getAddress());
}
// 當(dāng)掃描完成之后改變Activity的title
} else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {
//設(shè)置進(jìn)度條不顯示
setProgressBarIndeterminateVisibility( false);
//設(shè)置title
setTitle(R.string.select_device);
//如果計(jì)數(shù)為0,則表示沒有發(fā)現(xiàn)藍(lán)牙
if (mNewDevicesArrayAdapter.getCount() == 0 ) {
String noDevices = getResources().getText(R.string.none_found).toString();
mNewDevicesArrayAdapter.add(noDevices);
}
}
}
};
其中我們通過intent.getAction()可以取得一個(gè)動(dòng)作,然后判斷如果動(dòng)作為BluetoothDevice.ACTION_FOUND,則表示發(fā)現(xiàn)一個(gè)藍(lán)牙設(shè)備,然后通過BluetoothDevice.EXTRA_DEVICE常量可以取得Intent中的藍(lán)牙設(shè)備對象(BluetoothDevice),然后通過條件"device.getBondState() != BluetoothDevice.BOND_BONDED"來判斷設(shè)備是否配對,如果沒有配對則添加到行設(shè)備列表數(shù)據(jù)源mNewDevicesArrayAdapter中,另外,當(dāng)我們?nèi)〉玫膭?dòng)作為BluetoothAdapter.ACTION_DISCOVERY_FINISHED,則表示掃描過程完畢,這時(shí)首先需要設(shè)置進(jìn)度條不現(xiàn)實(shí),并且設(shè)置窗口的標(biāo)題為選擇一個(gè)設(shè)備(R.string.select_device)。當(dāng)然如果掃描完成之后沒有發(fā)現(xiàn)新的設(shè)備,則添加一個(gè)沒有發(fā)現(xiàn)新的設(shè)備字符串(R.string.none_found)到mNewDevicesArrayAdapter中。 最后,掃描界面上還有一個(gè)按鈕,其監(jiān)聽mDeviceClickListener的實(shí)現(xiàn)如下:
// ListViews中所有設(shè)備的點(diǎn)擊事件監(jiān)聽
private OnItemClickListener mDeviceClickListener = new OnItemClickListener() {
public void onItemClick(AdapterView av, View v, int arg2, long arg3) {
// 取消檢測掃描發(fā)現(xiàn)設(shè)備的過程,因?yàn)閮?nèi)非常耗費(fèi)資源
mBtAdapter.cancelDiscovery();
// 得到mac地址
String info = ((TextView) v).getText().toString();
String address = info.substring(info.length() - 17 );
// 創(chuàng)建一個(gè)包括Mac地址的Intent請求
Intent intent = new Intent();
intent.putExtra(EXTRA_DEVICE_ADDRESS, address);
// 設(shè)置result并結(jié)束Activity
setResult(Activity.RESULT_OK, intent);
finish();
}
};
當(dāng)用戶點(diǎn)擊該按鈕時(shí),首先取消掃描進(jìn)程,因?yàn)閽呙柽^程是一個(gè)非常耗費(fèi)資源的過程,然后去的設(shè)備的mac地址,構(gòu)建一個(gè)Intent 對象,通過附加數(shù)據(jù)EXTRA_DEVICE_ADDRESS將mac地址傳遞到BluetoothChat中,然后調(diào)用finish來結(jié)束該界面。這時(shí)就會回到上一篇文章我們介紹的BluetoothChat中的onActivityResult函數(shù)中去執(zhí)行請求代碼為REQUEST_CONNECT_DEVICE的片段,用來連接一個(gè)設(shè)備。
BluetoothChatService 對于設(shè)備的監(jiān)聽,連接管理都將由REQUEST_CONNECT_DEVICE來實(shí)現(xiàn),其中又包括三個(gè)主要部分,三個(gè)進(jìn)程分貝是:請求連接的監(jiān)聽線程(AcceptThread)、連接一個(gè)設(shè)備的進(jìn)程(ConnectThread)、連接之后的管理進(jìn)程(ConnectedThread)。同樣我們先熟悉一下該類的成員變量的作用,定義如下:
// Debugging
private static final String TAG = "BluetoothChatService";
private static final boolean D = true;
//當(dāng)創(chuàng)建socket服務(wù)時(shí)的SDP名稱
private static final String NAME = "BluetoothChat";
// 應(yīng)用程序的唯一UUID
private static final UUID MY_UUID = UUID.fromString("fa87c0d0-afac-11de-8a39-0800200c9a66");
// 本地藍(lán)牙適配器
private final BluetoothAdapter mAdapter;
//Handler
private final Handler mHandler;
//請求鏈接的監(jiān)聽線程
private AcceptThread mAcceptThread;
//鏈接一個(gè)設(shè)備的線程
private ConnectThread mConnectThread;
//已經(jīng)鏈接之后的管理線程
private ConnectedThread mConnectedThread;
//當(dāng)前的狀態(tài)
private int mState;
// 各種狀態(tài)
public static final int STATE_NONE = 0 ;
public static final int STATE_LISTEN = 1 ;
public static final int STATE_CONNECTING = 2 ;
public static final int STATE_CONNECTED = 3 ;
Debugging為調(diào)試相關(guān),NAME 是當(dāng)我們在創(chuàng)建一個(gè)socket監(jiān)聽服務(wù)時(shí)的一個(gè)SDP名稱,另外還包括一個(gè)狀態(tài)變量mState,其值則分別是下面的"各種狀態(tài)"部分,另外還有一個(gè)本地藍(lán)牙適配器和三個(gè)不同的進(jìn)程對象,由此可見,本地藍(lán)牙適配器的確是任何藍(lán)牙操作的基礎(chǔ)對象,下面我們會分別介紹這些進(jìn)程的實(shí)現(xiàn)。 首先是初始化操作,即構(gòu)造函數(shù),代碼如下:
public BluetoothChatService(Context context, Handler handler) {
//得到本地藍(lán)牙適配器
mAdapter = BluetoothAdapter.getDefaultAdapter();
//設(shè)置狀態(tài)
mState = STATE_NONE;
//設(shè)置Handler
mHandler = handler;
}
取得本地藍(lán)牙適配器、設(shè)置狀態(tài)為STATE_NONE,設(shè)置傳遞進(jìn)來的mHandler。接下來需要控制當(dāng)狀態(tài)改變之后,我們需要通知UI界面也同時(shí)更改狀態(tài),下面是得到狀態(tài)和設(shè)置狀態(tài)的實(shí)現(xiàn)部分,如下:
private synchronized void setState(int state) {
if (D) Log.d(TAG, "setState() " + mState + " -> " + state);
mState = state;
// 狀態(tài)更新之后UI Activity也需要更新
mHandler.obtainMessage(BluetoothChat.MESSAGE_STATE_CHANGE, state, - 1 ).sendToTarget();
}
public synchronized int getState() {
return mState;
}
得到狀態(tài)沒有什么特別的,關(guān)鍵在于設(shè)置狀態(tài)之后需要通過obtainMessage來發(fā)送一個(gè)消息到Handler,通知UI界面也同時(shí)更新其狀態(tài),對應(yīng)的Handler的實(shí)現(xiàn)則位于BluetoothChat中的private final Handler mHandler = new Handler()部分,從上面的代碼中,我們可以看到關(guān)于狀態(tài)更改的之后會發(fā)送一個(gè)BluetoothChat.MESSAGE_STATE_CHANGE消息到UI線程中,下面我們看一下UI線程中如何處理這些消息的,代碼如下:
case MESSAGE_STATE_CHANGE:
if(D) Log.i(TAG, "MESSAGE_STATE_CHANGE: " + msg.arg1);
switch (msg.arg1) {
case BluetoothChatService.STATE_CONNECTED:
//設(shè)置狀態(tài)為已經(jīng)鏈接
mTitle.setText(R.string.title_connected_to);
//添加設(shè)備名稱
mTitle.append(mConnectedDeviceName);
//清理聊天記錄
mConversationArrayAdapter.clear();
break;
case BluetoothChatService.STATE_CONNECTING:
//設(shè)置正在鏈接
mTitle.setText(R.string.title_connecting);
break;
case BluetoothChatService.STATE_LISTEN:
case BluetoothChatService.STATE_NONE:
//處于監(jiān)聽狀態(tài)或者沒有準(zhǔn)備狀態(tài),則顯示沒有鏈接
mTitle.setText(R.string.title_not_connected);
break;
}
break;
可以看出,當(dāng)不同的狀態(tài)在更改之后會進(jìn)行不同的設(shè)置,但是大多數(shù)都是根據(jù)不同的狀態(tài)設(shè)置顯示了不同的title,當(dāng)已經(jīng)鏈接(STATE_CONNECTED)之后,設(shè)置了標(biāo)題為鏈接的設(shè)備名,并同時(shí)還mConversationArrayAdapter進(jìn)行了清除操作,即清除聊天記錄。 現(xiàn)在,初始化操作已經(jīng)完成了,下面我們可以調(diào)用start函數(shù)來開啟一個(gè)服務(wù)進(jìn)程了,也即是在BluetoothChat中的onResume函數(shù)中所調(diào)用的start操作,其具體實(shí)現(xiàn)如下:
public synchronized void start() {
if (D) Log.d(TAG, "start");
// 取消任何線程視圖建立一個(gè)連接
if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread = null;}
// 取消任何正在運(yùn)行的鏈接
if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread = null;}
// 啟動(dòng)AcceptThread線程來監(jiān)聽BluetoothServerSocket
if (mAcceptThread == null) {
mAcceptThread = new AcceptThread();
mAcceptThread.start();
}
//設(shè)置狀態(tài)為監(jiān)聽,,等待鏈接
setState(STATE_LISTEN);
}
操作過程很簡單,首先取消另外兩個(gè)進(jìn)程,新建一個(gè)AcceptThread進(jìn)程,并啟動(dòng)AcceptThread進(jìn)程,最后設(shè)置狀態(tài)變?yōu)楸O(jiān)聽(STATE_LISTEN),這時(shí)UI界面的title也將更新為監(jiān)聽狀態(tài),即等待設(shè)備的連接。關(guān)于AcceptThread的具體實(shí)現(xiàn)如下所示。
private class AcceptThread extends Thread {
// 本地socket服務(wù)
private final BluetoothServerSocket mmServerSocket;
public AcceptThread() {
BluetoothServerSocket tmp = null;
// 創(chuàng)建一個(gè)新的socket服務(wù)監(jiān)聽
try {
tmp = mAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);
} catch (IOException e) {
Log.e(TAG, "listen() failed", e);
}
mmServerSocket = tmp;
}
public void run() {
if (D) Log.d(TAG, "BEGIN mAcceptThread" + this);
setName( "AcceptThread");
BluetoothSocket socket = null;
// 如果當(dāng)前沒有鏈接則一直監(jiān)聽socket服務(wù)
while (mState != STATE_CONNECTED) {
try {
//如果有請求鏈接,則接受
//這是一個(gè)阻塞調(diào)用,將之返回鏈接成功和一個(gè)異常
socket = mmServerSocket.accept();
} catch (IOException e) {
Log.e(TAG, "accept() failed", e);
break;
}
// 如果接受了一個(gè)鏈接
if (socket != null) {
synchronized (BluetoothChatService.this) {
switch (mState) {
case STATE_LISTEN:
case STATE_CONNECTING:
// 如果狀態(tài)為監(jiān)聽或者正在鏈接中,,則調(diào)用connected來鏈接
connected(socket, socket.getRemoteDevice());
break;
case STATE_NONE:
case STATE_CONNECTED:
// 如果為沒有準(zhǔn)備或者已經(jīng)鏈接,這終止該socket
try {
socket.close();
} catch (IOException e) {
Log.e(TAG, "Could not close unwanted socket", e);
}
break;
}
}
}
}
if (D) Log.i(TAG, "END mAcceptThread");
}
//關(guān)閉BluetoothServerSocket
public void cancel() {
if (D) Log.d(TAG, "cancel " + this);
try {
mmServerSocket.close();
} catch (IOException e) {
Log.e(TAG, "close() of server failed", e);
}
}
}
首先通過listenUsingRfcommWithServiceRecord創(chuàng)建一個(gè)socket服務(wù),用來監(jiān)聽設(shè)備的連接,當(dāng)進(jìn)程啟動(dòng)之后直到有設(shè)備連接時(shí),這段時(shí)間都將通過accept來監(jiān)聽和接收一個(gè)連接請求,如果連接無效則調(diào)用close來關(guān)閉即可,如果連接有效則調(diào)用connected進(jìn)入連接進(jìn)程,進(jìn)入連接進(jìn)程之后會取消當(dāng)前的監(jiān)聽進(jìn)程,取消過程則直接調(diào)用cancel通過mmServerSocket.close()來關(guān)閉即可。下面我們分析連接函數(shù)connect的實(shí)現(xiàn),如下:
public synchronized void connect(BluetoothDevice device) {
if (D) Log.d(TAG, "connect to: " + device);
// 取消任何鏈接線程,視圖建立一個(gè)鏈接
if (mState == STATE_CONNECTING) {
if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread = null;}
}
// 取消任何正在運(yùn)行的線程
if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread = null;}
// 啟動(dòng)一個(gè)鏈接線程鏈接指定的設(shè)備
mConnectThread = new ConnectThread(device);
mConnectThread.start();
setState(STATE_CONNECTING);
}
同樣,首先關(guān)閉其他兩個(gè)進(jìn)程,然后新建一個(gè)ConnectThread進(jìn)程,并啟動(dòng),通知UI界面狀態(tài)更改為正在連接的狀態(tài)(STATE_CONNECTING)。具體的連接進(jìn)程由ConnectThread來實(shí)現(xiàn),如下:
private class ConnectThread extends Thread {
//藍(lán)牙Socket
private final BluetoothSocket mmSocket;
//藍(lán)牙設(shè)備
private final BluetoothDevice mmDevice;
public ConnectThread(BluetoothDevice device) {
mmDevice = device;
BluetoothSocket tmp = null;
//得到一個(gè)給定的藍(lán)牙設(shè)備的BluetoothSocket
try {
tmp = device.createRfcommSocketToServiceRecord(MY_UUID);
} catch (IOException e) {
Log.e(TAG, "create() failed", e);
}
mmSocket = tmp;
}
public void run() {
Log.i(TAG, "BEGIN mConnectThread");
setName( "ConnectThread");
// 取消可見狀態(tài),將會進(jìn)行鏈接
mAdapter.cancelDiscovery();
// 創(chuàng)建一個(gè)BluetoothSocket鏈接
try {
//同樣是一個(gè)阻塞調(diào)用,返回成功和異常
mmSocket.connect();
} catch (IOException e) {
//鏈接失敗
connectionFailed();
// 如果異常則關(guān)閉socket
try {
mmSocket.close();
} catch (IOException e2) {
Log.e(TAG, "unable to close() socket during connection failure", e2);
}
// 重新啟動(dòng)監(jiān)聽服務(wù)狀態(tài)
BluetoothChatService. this.start();
return;
}
// 完成則重置ConnectThread
synchronized (BluetoothChatService.this) {
mConnectThread = null;
}
// 開啟ConnectedThread(正在運(yùn)行中...)線程
connected(mmSocket, mmDevice);
}
//取消鏈接線程ConnectThread
public void cancel() {
try {
mmSocket.close();
} catch (IOException e) {
Log.e(TAG, "close() of connect socket failed", e);
}
}
}
在創(chuàng)建該進(jìn)程時(shí),就已經(jīng)知道當(dāng)前需要被連接的藍(lán)牙設(shè)備,然后通過createRfcommSocketToServiceRecord可以構(gòu)建一個(gè)藍(lán)牙設(shè)備的BluetoothSocket對象,當(dāng)進(jìn)入連接狀態(tài)時(shí),就可以調(diào)用cancelDiscovery來取消藍(lán)牙的可見狀態(tài),然后通過調(diào)用connect函數(shù)進(jìn)行鏈接操作,如果出現(xiàn)異常則表示鏈接失敗,則調(diào)用connectionFailed函數(shù)通知UI進(jìn)程更新界面的顯示為鏈接失敗狀態(tài),然后關(guān)閉BluetoothSocket,調(diào)用start函數(shù)重新開啟一個(gè)監(jiān)聽服務(wù)AcceptThread,對于鏈接失敗的處理實(shí)現(xiàn)如下:
private void connectionFailed() {
setState(STATE_LISTEN);
// 發(fā)送鏈接失敗的消息到UI界面
Message msg = mHandler.obtainMessage(BluetoothChat.MESSAGE_TOAST);
Bundle bundle = new Bundle();
bundle.putString(BluetoothChat.TOAST, "Unable to connect device");
msg.setData(bundle);
mHandler.sendMessage(msg);
}
首先更改狀態(tài)為STATE_LISTEN,然后發(fā)送一個(gè)Message帶UI界面,通知UI更新,顯示一個(gè)Toast告知用戶,當(dāng)BluetoothChat中的mHandler接收到BluetoothChat.TOAST消息時(shí),就會直接更新UI界面的顯示,如果連接成功則將調(diào)用connected函數(shù)進(jìn)入連接管理進(jìn)程,其實(shí)現(xiàn)如下:
public synchronized void connected(BluetoothSocket socket, BluetoothDevice device) {
if (D) Log.d(TAG, "connected");
// 取消ConnectThread鏈接線程
if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread = null;}
// 取消所有正在鏈接的線程
if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread = null;}
// 取消所有的監(jiān)聽線程,因?yàn)槲覀円呀?jīng)鏈接了一個(gè)設(shè)備
if (mAcceptThread != null) {mAcceptThread.cancel(); mAcceptThread = null;}
// 啟動(dòng)ConnectedThread線程來管理鏈接和執(zhí)行翻譯
mConnectedThread = new ConnectedThread(socket);
mConnectedThread.start();
// 發(fā)送鏈接的設(shè)備名稱到UI Activity界面
Message msg = mHandler.obtainMessage(BluetoothChat.MESSAGE_DEVICE_NAME);
Bundle bundle = new Bundle();
bundle.putString(BluetoothChat.DEVICE_NAME, device.getName());
msg.setData(bundle);
mHandler.sendMessage(msg);
//狀態(tài)變?yōu)橐呀?jīng)鏈接,即正在運(yùn)行中
setState(STATE_CONNECTED);
}
首先,關(guān)閉所有的進(jìn)程,構(gòu)建一個(gè)ConnectedThread進(jìn)程,并準(zhǔn)備一個(gè)Message消息,就設(shè)備名稱(BluetoothChat.DEVICE_NAME)也發(fā)送到UI進(jìn)程,因?yàn)閁I進(jìn)程需要顯示當(dāng)前連接的設(shè)備名稱,當(dāng)UI進(jìn)程收到BluetoothChat.MESSAGE_DEVICE_NAME消息時(shí)就會更新相應(yīng)的UI界面,就是設(shè)置窗口的title,這里我們就不貼出代碼了,下面我們分析一下ConnectedThread的實(shí)現(xiàn),代碼如下:
private class ConnectedThread extends Thread {
//BluetoothSocket
private final BluetoothSocket mmSocket;
//輸入輸出流
private final InputStream mmInStream;
private final OutputStream mmOutStream;
public ConnectedThread(BluetoothSocket socket) {
Log.d(TAG, "create ConnectedThread");
mmSocket = socket;
InputStream tmpIn = null;
OutputStream tmpOut = null;
// 得到BluetoothSocket的輸入輸出流
try {
tmpIn = socket.getInputStream();
tmpOut = socket.getOutputStream();
} catch (IOException e) {
Log.e(TAG, "temp sockets not created", e);
}
mmInStream = tmpIn;
mmOutStream = tmpOut;
}
public void run() {
Log.i(TAG, "BEGIN mConnectedThread");
byte[] buffer = new byte[1024 ];
int bytes;
// 監(jiān)聽輸入流
while (true) {
try {
// 從輸入流中讀取數(shù)據(jù)
bytes = mmInStream.read(buffer);
// 發(fā)送一個(gè)消息到UI線程進(jìn)行更新
mHandler.obtainMessage(BluetoothChat.MESSAGE_READ, bytes, - 1 , buffer)
.sendToTarget();
} catch (IOException e) {
//出現(xiàn)異常,則鏈接丟失
Log.e(TAG, "disconnected", e);
connectionLost();
break;
}
}
}
/**
* 寫入藥發(fā)送的消息
* @param buffer The bytes to write
*/
public void write(byte[] buffer) {
try {
mmOutStream.write(buffer);
// 將寫的消息同時(shí)傳遞給UI界面
mHandler.obtainMessage(BluetoothChat.MESSAGE_WRITE, - 1 , -1 , buffer)
.sendToTarget();
} catch (IOException e) {
Log.e(TAG, "Exception during write", e);
}
}
//取消ConnectedThread鏈接管理線程
public void cancel() {
try {
mmSocket.close();
} catch (IOException e) {
Log.e(TAG, "close() of connect socket failed", e);
}
}
}
連接之后的主要操作就是發(fā)送和接收聊天消息了,因?yàn)樾枰ㄟ^其輸入(出)流來操作具體信息,進(jìn)程會一直從輸入流中讀取信息,并通過obtainMessage函數(shù)將讀取的信息以BluetoothChat.MESSAGE_READ命令發(fā)送到UI進(jìn)程,到UI進(jìn)程收到是,就需要將其顯示到消息列表之中,同時(shí)對于發(fā)送消息,需要實(shí)行寫操作write,其操作就是將要發(fā)送的消息寫入到輸出流mmOutStream中,并且以BluetoothChat.MESSAGE_WRITE命令的方式發(fā)送到UI進(jìn)程中,進(jìn)行同步更新,如果在讀取消息時(shí)失敗或者產(chǎn)生了異常,則表示連接丟失,這是就調(diào)用connectionLost函數(shù)來處理連接丟失,代碼如下:
private void connectionLost() {
setState(STATE_LISTEN);
// 發(fā)送失敗消息到UI界面
Message msg = mHandler.obtainMessage(BluetoothChat.MESSAGE_TOAST);
Bundle bundle = new Bundle();
bundle.putString(BluetoothChat.TOAST, "Device connection was lost");
msg.setData(bundle);
mHandler.sendMessage(msg);
}
操作同樣簡單,首先改變狀態(tài)為STATE_LISTEN,然后BluetoothChat.MESSAGE_TOAST命令發(fā)送一個(gè)消息Message到UI進(jìn)程,通知UI進(jìn)程更新顯示畫面即可。對于寫操作,是調(diào)用了BluetoothChatService.write來實(shí)現(xiàn),其實(shí)現(xiàn)代碼如下:
//寫入自己要發(fā)送出來的消息
public void write(byte[] out) {
// Create temporary object
ConnectedThread r;
// Synchronize a copy of the ConnectedThread
synchronized (this) {
//判斷是否處于已經(jīng)鏈接狀態(tài)
if (mState != STATE_CONNECTED) return;
r = mConnectedThread;
}
// 執(zhí)行寫
r.write(out);
}
其實(shí)就是檢測,當(dāng)前的狀態(tài)是否處于已經(jīng)鏈接狀態(tài)STATE_CONNECTED,然后調(diào)用ConnectedThread 進(jìn)程中的write操作,來完成消息的發(fā)送。因此這時(shí)我們可以回過頭來看BluetoothChat中的sendMessage的實(shí)現(xiàn)了,如下所示:
private void sendMessage(String message) {
// 檢查是否處于連接狀態(tài)
if (mChatService.getState() != BluetoothChatService.STATE_CONNECTED) {
Toast.makeText( this, R.string.not_connected, Toast.LENGTH_SHORT).show();
return;
}
// 如果輸入的消息不為空才發(fā)送,否則不發(fā)送
if (message.length() > 0 ) {
// Get the message bytes and tell the BluetoothChatService to write
byte[] send = message.getBytes();
mChatService.write(send);
// Reset out string buffer to zero and clear the edit text field
mOutStringBuffer.setLength( 0 );
mOutEditText.setText(mOutStringBuffer);
}
}
同樣首先檢測了當(dāng)前的狀態(tài)是否為已經(jīng)連接狀態(tài),然后對要發(fā)送的消息是否為null進(jìn)行了判斷,如果為空則不需要發(fā)送,否則調(diào)用mChatService.write(即上面所說的ConnectedThread 中的wirte操作)來發(fā)送消息。然后一個(gè)小的細(xì)節(jié)就是設(shè)置編輯框的內(nèi)容為null即可。最后我們可以看一下在BluetoothChat中如何處理這些接收到的消息,主要位于mHandler中的handleMessage函數(shù)中,對于狀態(tài)改變的消息我們已經(jīng)分析過了,下面是其他幾個(gè)消息的處理:
case MESSAGE_WRITE:
byte[] writeBuf = (byte[]) msg.obj;
// 將自己寫入的消息也顯示到會話列表中
String writeMessage = new String(writeBuf);
mConversationArrayAdapter.add( "Me: " + writeMessage);
break;
case MESSAGE_READ:
byte[] readBuf = (byte[]) msg.obj;
// 取得內(nèi)容并添加到聊天對話列表中
String readMessage = new String(readBuf, 0 , msg.arg1);
mConversationArrayAdapter.add(mConnectedDeviceName+ ": " + readMessage);
break;
case MESSAGE_DEVICE_NAME:
// 保存鏈接的設(shè)備名稱,并顯示一個(gè)toast提示
mConnectedDeviceName = msg.getData().getString(DEVICE_NAME);
Toast.makeText(getApplicationContext(), "Connected to "
+ mConnectedDeviceName, Toast.LENGTH_SHORT).show();
break;
case MESSAGE_TOAST:
//處理鏈接(發(fā)送)失敗的消息
Toast.makeText(getApplicationContext(), msg.getData().getString(TOAST),
Toast.LENGTH_SHORT).show();
break;
分別是讀取消息和寫消息(發(fā)送消息),對于一些信息提示消息MESSAGE_TOAST,則通過Toast顯示出來即可。如果消息是設(shè)備名稱MESSAGE_DEVICE_NAME,則提示用戶當(dāng)前連接的設(shè)備的名稱。對于寫消息(MESSAGE_WRITE)和讀消息(MESSAGE_READ)我們就不重復(fù)了,大家看看代碼都已經(jīng)加入了詳細(xì)的注釋了。 最后當(dāng)我們在需要停止這些進(jìn)程時(shí)就看有直接調(diào)用stop即可,具體實(shí)現(xiàn)如下:
//停止所有的線程
public synchronized void stop() {
if (D) Log.d(TAG, "stop");
if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread = null;}
if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread = null;}
if (mAcceptThread != null) {mAcceptThread.cancel(); mAcceptThread = null;}
//狀態(tài)設(shè)置為準(zhǔn)備狀態(tài)
setState(STATE_NONE);
}
分別檢測三個(gè)進(jìn)程是否為null,然后調(diào)用各自的cancel函數(shù)來取消進(jìn)程,最后不要忘記將狀態(tài)恢復(fù)到STATE_NONE即可。
總結(jié) 終于完成了對藍(lán)牙聊天程序的實(shí)現(xiàn)和分析,該示例程序比較全面,基本上包括了藍(lán)牙編程的各個(gè)方面,希望通過這幾篇文章的問題,能夠幫助大家理解在Ophone平臺上進(jìn)行藍(lán)牙編程,同時(shí)將藍(lán)牙技術(shù)運(yùn)用到其他應(yīng)用程序中實(shí)現(xiàn)應(yīng)用程序的網(wǎng)絡(luò)化,聯(lián)機(jī)性?;蛟S你有更多的用處。