日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

Ophone平臺藍(lán)牙編程之藍(lán)牙聊天分析(二)

 WindySky 2016-06-23
接著上一篇沒有完成的任務(wù),我們繼續(xù)分析這個(gè)藍(lán)牙聊天程序的實(shí)現(xiàn),本文主要包括以下兩個(gè)部分的內(nèi)容:其一,分析掃描設(shè)備部分DeviceListActivity,其二,分析具體的聊天過程的完整通信方案,包括端口監(jiān)聽、鏈接配對、消息發(fā)送和接收等,如果有對上一篇文章不太熟悉的,可以返回去在過一次,這樣會有利于本文的理解。


  設(shè)備掃描(DeviceListActivity)
  在上一篇文章的介紹中,當(dāng)用戶點(diǎn)擊了掃描按鈕之后,則會執(zhí)行如下代碼:

 
  1. // 啟動(dòng)DeviceListActivity查看設(shè)備并掃描   
  2.   Intent serverIntent = new Intent(this, DeviceListActivity.class);   
  3.   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則為圖中對話框部分,其界面的布局如下代碼所示。

  1. "http://schemas./apk/res/android"  
  2.   android:orientation="vertical"  
  3.   android:layout_width="match_parent"  
  4.   android:layout_height="match_parent"  
  5.   >   
  6.      
  7.   "@+id/title_paired_devices"  
  8.   android:layout_width="match_parent"  
  9.   android:layout_height="wrap_content"  
  10.   android:text="@string/title_paired_devices"  
  11.   android:visibility="gone"  
  12.   android:background="#666"  
  13.   android:textColor="#fff"  
  14.   android:paddingLeft="5dp"  
  15.   />   
  16.      
  17.   "@+id/paired_devices"  
  18.   android:layout_width="match_parent"  
  19.   android:layout_height="wrap_content"  
  20.   android:stackFromBottom="true"  
  21.   android:layout_weight="1"  
  22.   />   
  23.      
  24.   "@+id/title_new_devices"  
  25.   android:layout_width="match_parent"  
  26.   android:layout_height="wrap_content"  
  27.   android:text="@string/title_other_devices"  
  28.   android:visibility="gone"  
  29.   android:background="#666"  
  30.   android:textColor="#fff"  
  31.   android:paddingLeft="5dp"  
  32.   />   
  33.      
  34.   "@+id/new_devices"  
  35.   android:layout_width="match_parent"  
  36.   android:layout_height="wrap_content"  
  37.   android:stackFromBottom="true"  
  38.   android:layout_weight="2"  
  39.   />   
  40.      
  41.   "@+id/button_scan"  
  42.   android:layout_width="match_parent"  
  43.   android:layout_height="wrap_content"  
  44.   android:text="@string/button_scan"  
  45.   />   
  46.     


  該布局整體由一個(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)了。
  同樣開始之前,我們先確定該類中的變量的作用,定義如下:

  1. public class DeviceListActivity extends Activity {   
  2.   // Debugging   
  3.   private static final String TAG = "DeviceListActivity";   
  4.   private static final boolean D = true;   
  5.   
  6.   // Return Intent extra   
  7.   public static String EXTRA_DEVICE_ADDRESS = "device_address";   
  8.   
  9.   // 藍(lán)牙適配器   
  10.   private BluetoothAdapter mBtAdapter;   
  11.   //已經(jīng)配對的藍(lán)牙設(shè)備   
  12.   private ArrayAdapter mPairedDevicesArrayAdapter;   
  13.   //新的藍(lán)牙設(shè)備   
  14.   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è)置:

  1. // 設(shè)置窗口   
  2.   requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);   
  3.   setContentView(R.layout.device_list);   
  4.   setResult(Activity.RESULT_CANCELED);  


  這里我們設(shè)置了窗口需要帶一個(gè)進(jìn)度條,當(dāng)我們在掃描時(shí)就看有很容易的高速用戶掃描進(jìn)度。具體布局則設(shè)置為device_list.xml也是我們文本第一段代碼的內(nèi)容,接下來首先初始化掃描按鈕,代碼如下:

  1. // 初始化掃描按鈕   
  2.   Button scanButton = (Button) findViewById(R.id.button_scan);   
  3.   scanButton.setOnClickListener(new OnClickListener() {   
  4.   public void onClick(View v) {   
  5.   doDiscovery();   
  6.   v.setVisibility(View.GONE);   
  7.   }   
  8.   });  

  首先取得按鈕對象,然后為其設(shè)置一個(gè)事件監(jiān)聽,當(dāng)事件觸發(fā)時(shí)就通過doDiscovery函數(shù)來執(zhí)行掃描操作即可,具體掃描過程稍后分析。
  然后需要初始化用來顯示設(shè)備的列表和數(shù)據(jù)源,使用如下代碼即可:

  1. //初始化ArrayAdapter,一個(gè)是已經(jīng)配對的設(shè)備,一個(gè)是新發(fā)現(xiàn)的設(shè)備   
  2.   mPairedDevicesArrayAdapter = new ArrayAdapter(this, R.layout.device_name);   
  3.   mNewDevicesArrayAdapter = new ArrayAdapter(this, R.layout.device_name);   
  4.   
  5.   // 檢測并設(shè)置已配對的設(shè)備ListView   
  6.   ListView pairedListView = (ListView) findViewById(R.id.paired_devices);   
  7.   pairedListView.setAdapter(mPairedDevicesArrayAdapter);   
  8.   pairedListView.setOnItemClickListener(mDeviceClickListener);   
  9.   
  10.   // 檢查并設(shè)置行發(fā)現(xiàn)的藍(lán)牙設(shè)備ListView   
  11.   ListView newDevicesListView = (ListView) findViewById(R.id.new_devices);   
  12.   newDevicesListView.setAdapter(mNewDevicesArrayAdapter);   
  13.   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)行處理,代碼如下:

  1.  // 當(dāng)一個(gè)設(shè)備被發(fā)現(xiàn)時(shí),需要注冊一個(gè)廣播   
  2.   IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);   
  3.   this.registerReceiver(mReceiver, filter);   
  4.   
  5.   // 當(dāng)顯示檢查完畢的時(shí)候,需要注冊一個(gè)廣播   
  6.   filter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);   
  7.   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)行處理,代碼如下:

  1. // 得到本地的藍(lán)牙適配器   
  2.   mBtAdapter = BluetoothAdapter.getDefaultAdapter();   
  3.   
  4.   // 得到一個(gè)已經(jīng)匹配到本地適配器的BluetoothDevice類的對象集合   
  5.   Set pairedDevices = mBtAdapter.getBondedDevices();   
  6.   
  7.   // 如果有配對成功的設(shè)備則添加到ArrayAdapter   
  8.   if (pairedDevices.size() > 0) {   
  9.   findViewById(R.id.title_paired_devices).setVisibility(View.VISIBLE);   
  10.   for (BluetoothDevice device : pairedDevices) {   
  11.   mPairedDevicesArrayAdapter.add(device.getName() + "\n" + device.getAddress());   
  12.   }   
  13.   } else {   
  14.    //否則添加一個(gè)沒有被配對的字符串   
  15.   String noDevices = getResources().getText(R.string.none_paired).toString();   
  16.   mPairedDevicesArrayAdapter.add(noDevices);   
  17.   }  


  首先通過藍(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)牙適配器和廣播的注銷操作,代碼如下:

  1. @Override  
  2.   protected void onDestroy() {   
  3.   super.onDestroy();   
  4.   // 確保我們沒有發(fā)現(xiàn),檢測設(shè)備   
  5.   if (mBtAdapter != null) {   
  6.   mBtAdapter.cancelDiscovery();   
  7.   }   
  8.   // 卸載所注冊的廣播   
  9.   this.unregisterReceiver(mReceiver);   
  10.   }  

  對于藍(lán)牙適配器的取消方式則調(diào)用cancelDiscovery()函數(shù)即可,卸載mReceiver則需要調(diào)用unregisterReceiver即可。
  做好初始化工作之后,下面我們開始分析掃描函數(shù)doDiscovery(),其掃描過程的實(shí)現(xiàn)很就簡單,代碼如下:

  1. /**  
  2.   * 請求能被發(fā)現(xiàn)的設(shè)備  
  3.   */  
  4.   private void doDiscovery() {   
  5.   if (D) Log.d(TAG, "doDiscovery()");   
  6.   
  7.   // 設(shè)置顯示進(jìn)度條   
  8.   setProgressBarIndeterminateVisibility(true);   
  9.   // 設(shè)置title為掃描狀態(tài)   
  10.   setTitle(R.string.scanning);   
  11.   
  12.   // 顯示新設(shè)備的子標(biāo)題   
  13.   findViewById(R.id.title_new_devices).setVisibility(View.VISIBLE);   
  14.   
  15.   // 如果已經(jīng)在請求現(xiàn)實(shí)了,那么就先停止   
  16.   if (mBtAdapter.isDiscovering()) {   
  17.   mBtAdapter.cancelDiscovery();   
  18.   }   
  19.   
  20.   // 請求從藍(lán)牙適配器得到能夠被發(fā)現(xiàn)的設(shè)備   
  21.   mBtAdapter.startDiscovery();   
  22.   }  

  首先通過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)如下所示。

  1. //監(jiān)聽掃描藍(lán)牙設(shè)備   
  2.   private final BroadcastReceiver mReceiver = new BroadcastReceiver() {   
  3.   @Override  
  4.   public void onReceive(Context context, Intent intent) {   
  5.   String action = intent.getAction();   
  6.   // 當(dāng)發(fā)現(xiàn)一個(gè)設(shè)備時(shí)   
  7.   if (BluetoothDevice.ACTION_FOUND.equals(action)) {   
  8.   // 從Intent得到藍(lán)牙設(shè)備對象   
  9.   BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);   
  10.   // 如果已經(jīng)配對,則跳過,因?yàn)樗呀?jīng)在設(shè)備列表中了   
  11.   if (device.getBondState() != BluetoothDevice.BOND_BONDED) {   
  12.    //否則添加到設(shè)備列表   
  13.   mNewDevicesArrayAdapter.add(device.getName() + "\n" + device.getAddress());   
  14.   }   
  15.   // 當(dāng)掃描完成之后改變Activity的title   
  16.   } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {   
  17.    //設(shè)置進(jìn)度條不顯示   
  18.   setProgressBarIndeterminateVisibility(false);   
  19.   //設(shè)置title   
  20.   setTitle(R.string.select_device);   
  21.   //如果計(jì)數(shù)為0,則表示沒有發(fā)現(xiàn)藍(lán)牙   
  22.   if (mNewDevicesArrayAdapter.getCount() == 0) {   
  23.   String noDevices = getResources().getText(R.string.none_found).toString();   
  24.   mNewDevicesArrayAdapter.add(noDevices);   
  25.   }   
  26.   }   
  27.   }   
  28.   };  


  其中我們通過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)如下:

  1. // ListViews中所有設(shè)備的點(diǎn)擊事件監(jiān)聽   
  2.   private OnItemClickListener mDeviceClickListener = new OnItemClickListener() {   
  3.   public void onItemClick(AdapterView av, View v, int arg2, long arg3) {   
  4.   // 取消檢測掃描發(fā)現(xiàn)設(shè)備的過程,因?yàn)閮?nèi)非常耗費(fèi)資源   
  5.   mBtAdapter.cancelDiscovery();   
  6.   
  7.   // 得到mac地址   
  8.   String info = ((TextView) v).getText().toString();   
  9.   String address = info.substring(info.length() - 17);   
  10.   
  11.   // 創(chuàng)建一個(gè)包括Mac地址的Intent請求   
  12.   Intent intent = new Intent();   
  13.   intent.putExtra(EXTRA_DEVICE_ADDRESS, address);   
  14.   
  15.   // 設(shè)置result并結(jié)束Activity   
  16.   setResult(Activity.RESULT_OK, intent);   
  17.   finish();   
  18.   }   
  19.   };  

  當(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)。同樣我們先熟悉一下該類的成員變量的作用,定義如下:

  1. // Debugging   
  2.   private static final String TAG = "BluetoothChatService";   
  3.   private static final boolean D = true;   
  4.   
  5.   //當(dāng)創(chuàng)建socket服務(wù)時(shí)的SDP名稱   
  6.   private static final String NAME = "BluetoothChat";   
  7.   
  8.   // 應(yīng)用程序的唯一UUID   
  9.   private static final UUID MY_UUID = UUID.fromString("fa87c0d0-afac-11de-8a39-0800200c9a66");   
  10.   
  11.   // 本地藍(lán)牙適配器   
  12.   private final BluetoothAdapter mAdapter;   
  13.   //Handler   
  14.   private final Handler mHandler;   
  15.   //請求鏈接的監(jiān)聽線程   
  16.   private AcceptThread mAcceptThread;   
  17.   //鏈接一個(gè)設(shè)備的線程   
  18.   private ConnectThread mConnectThread;   
  19.   //已經(jīng)鏈接之后的管理線程   
  20.   private ConnectedThread mConnectedThread;   
  21.   //當(dāng)前的狀態(tài)   
  22.   private int mState;   
  23.   
  24.   // 各種狀態(tài)   
  25.   public static final int STATE_NONE = 0;     
  26.   public static final int STATE_LISTEN = 1;    
  27.   public static final int STATE_CONNECTING = 2;    
  28.   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ù),代碼如下:

  1. public BluetoothChatService(Context context, Handler handler) {   
  2.    //得到本地藍(lán)牙適配器   
  3.   mAdapter = BluetoothAdapter.getDefaultAdapter();   
  4.   //設(shè)置狀態(tài)   
  5.   mState = STATE_NONE;   
  6.   //設(shè)置Handler   
  7.   mHandler = handler;   
  8.   }  

  取得本地藍(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)部分,如下:

  1. private synchronized void setState(int state) {   
  2.   if (D) Log.d(TAG, "setState() " + mState + " -> " + state);   
  3.   mState = state;   
  4.   
  5.   // 狀態(tài)更新之后UI Activity也需要更新   
  6.   mHandler.obtainMessage(BluetoothChat.MESSAGE_STATE_CHANGE, state, -1).sendToTarget();   
  7.   }   
  8.   
  9.   public synchronized int getState() {   
  10.   return mState;   
  11.   }  

  得到狀態(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線程中如何處理這些消息的,代碼如下:

  1. case MESSAGE_STATE_CHANGE:   
  2.   if(D) Log.i(TAG, "MESSAGE_STATE_CHANGE: " + msg.arg1);   
  3.   switch (msg.arg1) {   
  4.   case BluetoothChatService.STATE_CONNECTED:   
  5.    //設(shè)置狀態(tài)為已經(jīng)鏈接   
  6.   mTitle.setText(R.string.title_connected_to);   
  7.   //添加設(shè)備名稱   
  8.   mTitle.append(mConnectedDeviceName);   
  9.   //清理聊天記錄   
  10.   mConversationArrayAdapter.clear();   
  11.   break;   
  12.   case BluetoothChatService.STATE_CONNECTING:   
  13.    //設(shè)置正在鏈接   
  14.   mTitle.setText(R.string.title_connecting);   
  15.   break;   
  16.   case BluetoothChatService.STATE_LISTEN:   
  17.   case BluetoothChatService.STATE_NONE:   
  18.    //處于監(jiān)聽狀態(tài)或者沒有準(zhǔn)備狀態(tài),則顯示沒有鏈接   
  19.   mTitle.setText(R.string.title_not_connected);   
  20.   break;   
  21.   }   
  22.   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)如下:

  1. public synchronized void start() {   
  2.   if (D) Log.d(TAG, "start");   
  3.   
  4.   // 取消任何線程視圖建立一個(gè)連接   
  5.   if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread = null;}   
  6.   
  7.   // 取消任何正在運(yùn)行的鏈接   
  8.   if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread = null;}   
  9.   
  10.   // 啟動(dòng)AcceptThread線程來監(jiān)聽BluetoothServerSocket   
  11.   if (mAcceptThread == null) {   
  12.   mAcceptThread = new AcceptThread();   
  13.   mAcceptThread.start();   
  14.   }   
  15.   //設(shè)置狀態(tài)為監(jiān)聽,,等待鏈接   
  16.   setState(STATE_LISTEN);   
  17.   }  

  操作過程很簡單,首先取消另外兩個(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)如下所示。

  1. private class AcceptThread extends Thread {   
  2.   // 本地socket服務(wù)   
  3.   private final BluetoothServerSocket mmServerSocket;   
  4.   
  5.   public AcceptThread() {   
  6.   BluetoothServerSocket tmp = null;   
  7.   
  8.   // 創(chuàng)建一個(gè)新的socket服務(wù)監(jiān)聽   
  9.   try {   
  10.   tmp = mAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);   
  11.   } catch (IOException e) {   
  12.   Log.e(TAG, "listen() failed", e);   
  13.   }   
  14.   mmServerSocket = tmp;   
  15.   }   
  16.   
  17.   public void run() {   
  18.   if (D) Log.d(TAG, "BEGIN mAcceptThread" + this);   
  19.   setName("AcceptThread");   
  20.   BluetoothSocket socket = null;   
  21.   
  22.   // 如果當(dāng)前沒有鏈接則一直監(jiān)聽socket服務(wù)   
  23.   while (mState != STATE_CONNECTED) {   
  24.   try {   
  25.    //如果有請求鏈接,則接受   
  26.    //這是一個(gè)阻塞調(diào)用,將之返回鏈接成功和一個(gè)異常   
  27.   socket = mmServerSocket.accept();   
  28.   } catch (IOException e) {   
  29.   Log.e(TAG, "accept() failed", e);   
  30.   break;   
  31.   }   
  32.   
  33.   // 如果接受了一個(gè)鏈接   
  34.   if (socket != null) {   
  35.   synchronized (BluetoothChatService.this) {   
  36.   switch (mState) {   
  37.   case STATE_LISTEN:   
  38.   case STATE_CONNECTING:   
  39.   // 如果狀態(tài)為監(jiān)聽或者正在鏈接中,,則調(diào)用connected來鏈接   
  40.   connected(socket, socket.getRemoteDevice());   
  41.   break;   
  42.   case STATE_NONE:   
  43.   case STATE_CONNECTED:   
  44.   // 如果為沒有準(zhǔn)備或者已經(jīng)鏈接,這終止該socket   
  45.   try {   
  46.   socket.close();   
  47.   } catch (IOException e) {   
  48.   Log.e(TAG, "Could not close unwanted socket", e);   
  49.   }   
  50.   break;   
  51.   }   
  52.   }   
  53.   }   
  54.   }   
  55.   if (D) Log.i(TAG, "END mAcceptThread");   
  56.   }   
  57.   //關(guān)閉BluetoothServerSocket   
  58.   public void cancel() {   
  59.   if (D) Log.d(TAG, "cancel " + this);   
  60.   try {   
  61.   mmServerSocket.close();   
  62.   } catch (IOException e) {   
  63.   Log.e(TAG, "close() of server failed", e);   
  64.   }   
  65.   }   
  66.   }  


  首先通過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),如下:

  1. public synchronized void connect(BluetoothDevice device) {   
  2.   if (D) Log.d(TAG, "connect to: " + device);   
  3.   
  4.   // 取消任何鏈接線程,視圖建立一個(gè)鏈接   
  5.   if (mState == STATE_CONNECTING) {   
  6.   if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread = null;}   
  7.   }   
  8.   
  9.   // 取消任何正在運(yùn)行的線程   
  10.   if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread = null;}   
  11.   
  12.   // 啟動(dòng)一個(gè)鏈接線程鏈接指定的設(shè)備   
  13.   mConnectThread = new ConnectThread(device);   
  14.   mConnectThread.start();   
  15.   setState(STATE_CONNECTING);   
  16.   }  


  同樣,首先關(guān)閉其他兩個(gè)進(jìn)程,然后新建一個(gè)ConnectThread進(jìn)程,并啟動(dòng),通知UI界面狀態(tài)更改為正在連接的狀態(tài)(STATE_CONNECTING)。具體的連接進(jìn)程由ConnectThread來實(shí)現(xiàn),如下:

  1. private class ConnectThread extends Thread {   
  2.    //藍(lán)牙Socket   
  3.   private final BluetoothSocket mmSocket;   
  4.   //藍(lán)牙設(shè)備   
  5.   private final BluetoothDevice mmDevice;   
  6.   
  7.   public ConnectThread(BluetoothDevice device) {   
  8.   mmDevice = device;   
  9.   BluetoothSocket tmp = null;   
  10.   
  11.   //得到一個(gè)給定的藍(lán)牙設(shè)備的BluetoothSocket   
  12.   try {   
  13.   tmp = device.createRfcommSocketToServiceRecord(MY_UUID);   
  14.   } catch (IOException e) {   
  15.   Log.e(TAG, "create() failed", e);   
  16.   }   
  17.   mmSocket = tmp;   
  18.   }   
  19.   
  20.   public void run() {   
  21.   Log.i(TAG, "BEGIN mConnectThread");   
  22.   setName("ConnectThread");   
  23.   
  24.   // 取消可見狀態(tài),將會進(jìn)行鏈接   
  25.   mAdapter.cancelDiscovery();   
  26.   
  27.   // 創(chuàng)建一個(gè)BluetoothSocket鏈接   
  28.   try {   
  29.   //同樣是一個(gè)阻塞調(diào)用,返回成功和異常   
  30.   mmSocket.connect();   
  31.   } catch (IOException e) {   
  32.    //鏈接失敗   
  33.   connectionFailed();   
  34.   // 如果異常則關(guān)閉socket   
  35.   try {   
  36.   mmSocket.close();   
  37.   } catch (IOException e2) {   
  38.   Log.e(TAG, "unable to close() socket during connection failure", e2);   
  39.   }   
  40.   // 重新啟動(dòng)監(jiān)聽服務(wù)狀態(tài)   
  41.   BluetoothChatService.this.start();   
  42.   return;   
  43.   }   
  44.   
  45.   // 完成則重置ConnectThread   
  46.   synchronized (BluetoothChatService.this) {   
  47.   mConnectThread = null;   
  48.   }   
  49.   
  50.   // 開啟ConnectedThread(正在運(yùn)行中...)線程   
  51.   connected(mmSocket, mmDevice);   
  52.   }   
  53.   //取消鏈接線程ConnectThread   
  54.   public void cancel() {   
  55.   try {   
  56.   mmSocket.close();   
  57.   } catch (IOException e) {   
  58.   Log.e(TAG, "close() of connect socket failed", e);   
  59.   }   
  60.   }   
  61.   }  

  在創(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)如下:

  1. private void connectionFailed() {   
  2.   setState(STATE_LISTEN);   
  3.   
  4.   // 發(fā)送鏈接失敗的消息到UI界面   
  5.   Message msg = mHandler.obtainMessage(BluetoothChat.MESSAGE_TOAST);   
  6.   Bundle bundle = new Bundle();   
  7.   bundle.putString(BluetoothChat.TOAST, "Unable to connect device");   
  8.   msg.setData(bundle);   
  9.   mHandler.sendMessage(msg);   
  10.   }  

  首先更改狀態(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)如下:

  1. public synchronized void connected(BluetoothSocket socket, BluetoothDevice device) {   
  2.   if (D) Log.d(TAG, "connected");   
  3.   
  4.   // 取消ConnectThread鏈接線程   
  5.   if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread = null;}   
  6.   
  7.   // 取消所有正在鏈接的線程   
  8.   if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread = null;}   
  9.   
  10.   // 取消所有的監(jiān)聽線程,因?yàn)槲覀円呀?jīng)鏈接了一個(gè)設(shè)備   
  11.   if (mAcceptThread != null) {mAcceptThread.cancel(); mAcceptThread = null;}   
  12.   
  13.   // 啟動(dòng)ConnectedThread線程來管理鏈接和執(zhí)行翻譯   
  14.   mConnectedThread = new ConnectedThread(socket);   
  15.   mConnectedThread.start();   
  16.   
  17.   // 發(fā)送鏈接的設(shè)備名稱到UI Activity界面   
  18.   Message msg = mHandler.obtainMessage(BluetoothChat.MESSAGE_DEVICE_NAME);   
  19.   Bundle bundle = new Bundle();   
  20.   bundle.putString(BluetoothChat.DEVICE_NAME, device.getName());   
  21.   msg.setData(bundle);   
  22.   mHandler.sendMessage(msg);   
  23.   //狀態(tài)變?yōu)橐呀?jīng)鏈接,即正在運(yùn)行中   
  24.   setState(STATE_CONNECTED);   
  25.   }  

  首先,關(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),代碼如下:

  1. private class ConnectedThread extends Thread {   
  2.    //BluetoothSocket   
  3.   private final BluetoothSocket mmSocket;   
  4.   //輸入輸出流   
  5.   private final InputStream mmInStream;   
  6.   private final OutputStream mmOutStream;   
  7.   
  8.   public ConnectedThread(BluetoothSocket socket) {   
  9.   Log.d(TAG, "create ConnectedThread");   
  10.   mmSocket = socket;   
  11.   InputStream tmpIn = null;   
  12.   OutputStream tmpOut = null;   
  13.   
  14.   // 得到BluetoothSocket的輸入輸出流   
  15.   try {   
  16.   tmpIn = socket.getInputStream();   
  17.   tmpOut = socket.getOutputStream();   
  18.   } catch (IOException e) {   
  19.   Log.e(TAG, "temp sockets not created", e);   
  20.   }   
  21.   
  22.   mmInStream = tmpIn;   
  23.   mmOutStream = tmpOut;   
  24.   }   
  25.   
  26.   public void run() {   
  27.   Log.i(TAG, "BEGIN mConnectedThread");   
  28.   byte[] buffer = new byte[1024];   
  29.   int bytes;   
  30.   
  31.   // 監(jiān)聽輸入流   
  32.   while (true) {   
  33.   try {   
  34.   // 從輸入流中讀取數(shù)據(jù)   
  35.   bytes = mmInStream.read(buffer);   
  36.   
  37.   // 發(fā)送一個(gè)消息到UI線程進(jìn)行更新   
  38.   mHandler.obtainMessage(BluetoothChat.MESSAGE_READ, bytes, -1, buffer)   
  39.   .sendToTarget();   
  40.   } catch (IOException e) {   
  41.    //出現(xiàn)異常,則鏈接丟失   
  42.   Log.e(TAG, "disconnected", e);   
  43.   connectionLost();   
  44.   break;   
  45.   }   
  46.   }   
  47.   }   
  48.   
  49.   /**  
  50.   * 寫入藥發(fā)送的消息  
  51.   * @param buffer  The bytes to write  
  52.   */  
  53.   public void write(byte[] buffer) {   
  54.   try {   
  55.   mmOutStream.write(buffer);   
  56.   
  57.   // 將寫的消息同時(shí)傳遞給UI界面   
  58.   mHandler.obtainMessage(BluetoothChat.MESSAGE_WRITE, -1, -1, buffer)   
  59.   .sendToTarget();   
  60.   } catch (IOException e) {   
  61.   Log.e(TAG, "Exception during write", e);   
  62.   }   
  63.   }   
  64.   //取消ConnectedThread鏈接管理線程   
  65.   public void cancel() {   
  66.   try {   
  67.   mmSocket.close();   
  68.   } catch (IOException e) {   
  69.   Log.e(TAG, "close() of connect socket failed", e);   
  70.   }   
  71.   }   
  72.   }  

  連接之后的主要操作就是發(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ù)來處理連接丟失,代碼如下:

  1. private void connectionLost() {   
  2.   setState(STATE_LISTEN);   
  3.   
  4.   // 發(fā)送失敗消息到UI界面   
  5.   Message msg = mHandler.obtainMessage(BluetoothChat.MESSAGE_TOAST);   
  6.   Bundle bundle = new Bundle();   
  7.   bundle.putString(BluetoothChat.TOAST, "Device connection was lost");   
  8.   msg.setData(bundle);   
  9.   mHandler.sendMessage(msg);   
  10.   }  

  操作同樣簡單,首先改變狀態(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)代碼如下:

  1. //寫入自己要發(fā)送出來的消息   
  2.   public void write(byte[] out) {   
  3.   // Create temporary object   
  4.   ConnectedThread r;   
  5.   // Synchronize a copy of the ConnectedThread   
  6.   synchronized (this) {   
  7.    //判斷是否處于已經(jīng)鏈接狀態(tài)   
  8.   if (mState != STATE_CONNECTED) return;   
  9.   r = mConnectedThread;   
  10.   }   
  11.   // 執(zhí)行寫   
  12.   r.write(out);   
  13.   }  

  其實(shí)就是檢測,當(dāng)前的狀態(tài)是否處于已經(jīng)鏈接狀態(tài)STATE_CONNECTED,然后調(diào)用ConnectedThread 進(jìn)程中的write操作,來完成消息的發(fā)送。因此這時(shí)我們可以回過頭來看BluetoothChat中的sendMessage的實(shí)現(xiàn)了,如下所示:

  1. private void sendMessage(String message) {   
  2.   // 檢查是否處于連接狀態(tài)   
  3.   if (mChatService.getState() != BluetoothChatService.STATE_CONNECTED) {   
  4.   Toast.makeText(this, R.string.not_connected, Toast.LENGTH_SHORT).show();   
  5.   return;   
  6.   }   
  7.   
  8.   // 如果輸入的消息不為空才發(fā)送,否則不發(fā)送   
  9.   if (message.length() > 0) {   
  10.   // Get the message bytes and tell the BluetoothChatService to write   
  11.   byte[] send = message.getBytes();   
  12.   mChatService.write(send);   
  13.   
  14.   // Reset out string buffer to zero and clear the edit text field   
  15.   mOutStringBuffer.setLength(0);   
  16.   mOutEditText.setText(mOutStringBuffer);   
  17.   }   
  18.   }  

  同樣首先檢測了當(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è)消息的處理:

  1. case MESSAGE_WRITE:   
  2.   byte[] writeBuf = (byte[]) msg.obj;   
  3.   // 將自己寫入的消息也顯示到會話列表中   
  4.   String writeMessage = new String(writeBuf);   
  5.   mConversationArrayAdapter.add("Me:  " + writeMessage);   
  6.   break;   
  7.   case MESSAGE_READ:   
  8.   byte[] readBuf = (byte[]) msg.obj;   
  9.   // 取得內(nèi)容并添加到聊天對話列表中   
  10.   String readMessage = new String(readBuf, 0, msg.arg1);   
  11.   mConversationArrayAdapter.add(mConnectedDeviceName+":  " + readMessage);   
  12.   break;   
  13.   case MESSAGE_DEVICE_NAME:   
  14.   // 保存鏈接的設(shè)備名稱,并顯示一個(gè)toast提示   
  15.   mConnectedDeviceName = msg.getData().getString(DEVICE_NAME);   
  16.   Toast.makeText(getApplicationContext(), "Connected to "  
  17.   + mConnectedDeviceName, Toast.LENGTH_SHORT).show();   
  18.   break;   
  19.   case MESSAGE_TOAST:   
  20.    //處理鏈接(發(fā)送)失敗的消息   
  21.   Toast.makeText(getApplicationContext(), msg.getData().getString(TOAST),   
  22.   Toast.LENGTH_SHORT).show();   
  23.   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)如下:

  1. //停止所有的線程   
  2.   public synchronized void stop() {   
  3.   if (D) Log.d(TAG, "stop");   
  4.   if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread = null;}   
  5.   if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread = null;}   
  6.   if (mAcceptThread != null) {mAcceptThread.cancel(); mAcceptThread = null;}   
  7.   //狀態(tài)設(shè)置為準(zhǔn)備狀態(tài)   
  8.   setState(STATE_NONE);   
  9.   }  

  分別檢測三個(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你有更多的用處。

    本站是提供個(gè)人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多