- OpenGL ES 3.x游戲開(kāi)發(fā)(上卷)
- 吳亞峰
- 8069字
- 2019-01-05 00:53:40
2.6 藍(lán)牙通信
隨著硬件設(shè)備價(jià)格的不斷降低,大部分智能手機(jī)上都已配備了藍(lán)牙網(wǎng)絡(luò)模塊,Android設(shè)備也是如此。如果能為一些小型的休閑娛樂(lè)游戲增加藍(lán)牙聯(lián)網(wǎng)對(duì)戰(zhàn)的功能,將會(huì)大大增加游戲的可玩性。本節(jié)將向讀者詳細(xì)介紹如何在Android平臺(tái)下開(kāi)發(fā)具有藍(lán)牙互聯(lián)功能的應(yīng)用程序。
2.6.1 藍(lán)牙通信的基本知識(shí)
藍(lán)牙是一種支持設(shè)備短距離通信(一般是10m以內(nèi))的無(wú)線技術(shù),其數(shù)據(jù)傳輸時(shí)不僅不需要連線,而且傳輸速率也比傳統(tǒng)手持設(shè)備的紅外模式更加迅速、高效,主要優(yōu)勢(shì)如下所列。
? 免費(fèi)。
藍(lán)牙無(wú)線技術(shù)規(guī)格供全球的成員公司免費(fèi)使用。除了設(shè)備費(fèi)用外,制造商不需要為使用藍(lán)牙技術(shù)再支付任何知識(shí)產(chǎn)權(quán)費(fèi)用,這大大降低了藍(lán)牙技術(shù)的普及門(mén)檻。
? 應(yīng)用范圍廣。
藍(lán)牙技術(shù)得到了廣泛的應(yīng)用,集成該技術(shù)的產(chǎn)品從手機(jī)、汽車到醫(yī)療設(shè)備等應(yīng)有盡有,使用該技術(shù)的用戶從消費(fèi)者、工業(yè)市場(chǎng)到企業(yè)等,不一而足。
? 易于使用。
藍(lán)牙是一項(xiàng)即時(shí)技術(shù),其不要求固定的基礎(chǔ)設(shè)施,且易于安裝和設(shè)置,而且無(wú)須電纜即可實(shí)現(xiàn)連接,使用起來(lái)非常方便。
提示
上一小節(jié)介紹的Socket技術(shù)雖然開(kāi)發(fā)很容易,但對(duì)用戶而言要么需要使用3G網(wǎng)絡(luò),要么需要通過(guò)Wi-Fi網(wǎng)絡(luò)。3G網(wǎng)絡(luò)雖然沒(méi)有位置限制,但費(fèi)用不低;Wi-Fi雖然免費(fèi),但必須在AP附近或自己架設(shè)AP??梢钥闯觯瑢?duì)于短距離即時(shí)互聯(lián)而言,采用藍(lán)牙技術(shù)更為合理、便捷。
? 全球通用的規(guī)格。
藍(lán)牙無(wú)線技術(shù)是當(dāng)今市場(chǎng)上支持范圍最廣泛,功能最豐富且安全的無(wú)線標(biāo)準(zhǔn)之一,全球范圍內(nèi)的資格認(rèn)證程序可以測(cè)試成員的產(chǎn)品是否符合標(biāo)準(zhǔn)。
介紹完了藍(lán)牙技術(shù)的特點(diǎn)與優(yōu)勢(shì)后,下面簡(jiǎn)單介紹一下藍(lán)牙設(shè)備的使用步驟,具體如下所列。
(1)開(kāi)啟要搜索的設(shè)備的藍(lán)牙功能,并設(shè)置為可見(jiàn)。
(2)在一個(gè)設(shè)備中開(kāi)啟搜索設(shè)備的功能,開(kāi)始搜索設(shè)備。
(3)當(dāng)搜索到其他設(shè)備后,會(huì)將搜索的設(shè)備顯示在本設(shè)備的列表中。
(4)選擇列表中的某一設(shè)備,請(qǐng)求匹配。
(5)被選中的設(shè)備收到匹配請(qǐng)求后,經(jīng)雙方驗(yàn)證同意,設(shè)備匹配成功。
(6)設(shè)備匹配成功后就可以建立連接,并收發(fā)數(shù)據(jù)了。
提示
藍(lán)牙通信與Socket網(wǎng)絡(luò)通信的基本思想非常類似,都是連接成功后建立雙向數(shù)據(jù)流收發(fā)數(shù)據(jù),但開(kāi)發(fā)起來(lái)要比Socket復(fù)雜一些。這主要是因?yàn)樗{(lán)牙設(shè)備的搜索功能和配對(duì)列表的顯示需要開(kāi)發(fā)人員自行編寫(xiě)代碼實(shí)現(xiàn),下面幾個(gè)小節(jié)的案例將對(duì)此進(jìn)行詳細(xì)介紹。
2.6.2 聊天案例概覽
本小節(jié)將介紹一個(gè)用藍(lán)牙技術(shù)實(shí)現(xiàn)聊天功能的案例,通過(guò)對(duì)本案例的學(xué)習(xí),讀者可以掌握關(guān)于藍(lán)牙通信的整個(gè)開(kāi)發(fā)過(guò)程。在開(kāi)發(fā)具體代碼之前,首先了解一下本案例的運(yùn)行方法及運(yùn)行效果,具體情況如下所列。
(1)準(zhǔn)備兩部Android手機(jī),在兩部手機(jī)上都安裝本案例的apk(Sample2_9.k)。
提示
筆者使用的兩部手機(jī)分別為華為u8800和摩托羅拉ME525,其他支持藍(lán)牙功能的Android智能手機(jī)也可以使用。之所以要使用真機(jī)是因?yàn)槟M器幾乎無(wú)法進(jìn)行藍(lán)牙程序的模擬測(cè)試,真機(jī)則要方便得多。
(2)將兩部手機(jī)的藍(lán)牙功能都打開(kāi),并設(shè)置為可見(jiàn)。
(3)在兩部手機(jī)中同時(shí)運(yùn)行本案例。
(4)在其中的一部手機(jī)中單擊Menu鍵(手機(jī)上自帶的菜單鍵)彈出設(shè)備搜索列表,如圖2-28所示。然后單擊列表下方的“掃描設(shè)備”按鈕搜索設(shè)備,搜索完畢后列表中出現(xiàn)另一手機(jī)的設(shè)備名稱及硬件地址,如圖2-29所示。

▲圖2-28 在u8800中彈出設(shè)備搜索列表

▲圖2-29 u8800搜索到其他設(shè)備
(5)單擊列表中搜索到的設(shè)備名稱,一段時(shí)間后兩部手機(jī)會(huì)同時(shí)提示連接成功,如圖2-30和圖2-31所示。

▲圖2-30 u8800提示已連接到ME 525

▲圖2-31 ME 525提示已連接到u8800
提示
本案例運(yùn)行時(shí)的界面還有很多,由于篇幅所限,這里只給出主要的運(yùn)行界面,其他界面請(qǐng)讀者自行運(yùn)行隨書(shū)中的本案例進(jìn)行測(cè)試。
2.6.3 聊天案例的開(kāi)發(fā)過(guò)程
介紹完本案例的運(yùn)行過(guò)程及效果后,本小節(jié)將詳細(xì)介紹本案例的開(kāi)發(fā)過(guò)程。由于本案例中涉及的類比較多,因此,在開(kāi)發(fā)代碼之前首先介紹一下本案例中各個(gè)類之間的關(guān)系及各自的用途,如圖2-32所示。

▲圖2-32 案例中各個(gè)類之間的關(guān)系及各自的用途
從圖2-32可以看出,本案例中包含兩個(gè)Activity、一個(gè)Service、3個(gè)線程,各自的用途如下所列。
? Sample2_9_Activity為本案例的主Activity,程序一啟動(dòng)就開(kāi)始運(yùn)行,主要功能用來(lái)顯示對(duì)話信息;MyDeviceListActivity用于顯示搜索到的可連接設(shè)備列表,通過(guò)菜單啟動(dòng);MyService則用來(lái)在后臺(tái)管理藍(lán)牙連接。
? AcceptThread用于在藍(lán)牙連接請(qǐng)求接收方監(jiān)聽(tīng)連接請(qǐng)求;ConnectThread用來(lái)向別的設(shè)備發(fā)出連接請(qǐng)求;ConnectedThread用來(lái)在藍(lán)牙連接建立后接收對(duì)方設(shè)備發(fā)送過(guò)來(lái)的消息。
了解完本案例中各個(gè)類之間的關(guān)系及各自的用途后,就可以進(jìn)行代碼的開(kāi)發(fā)了,具體步驟如下所列。
(1)首先開(kāi)發(fā)本案例的主控制類Sample2_9_Activity,該類在程序開(kāi)始時(shí)執(zhí)行,其中重寫(xiě)了onCreate方法、onStart方法以及onDestroy等方法,具體代碼如下。
代碼位置:見(jiàn)本書(shū)隨書(shū)中源代碼/第2章/Sample2_9/src/com/bn/pp9目錄下的Sample2_9_Activity.java。
1 package com.bn.pp9; //聲明包 2 import android.app.Activity; //引入相關(guān)類 3 ……//此處省略了部分類的引入代碼,讀者可自行查看隨書(shū)的源代碼 4 import android.widget.Toast; //引入相關(guān)類 5 public class Sample2_9_Activity extends Activity { 6 private EditText outEt; //布局中的控件引用 7 private Button sendBtn; 8 private String connectedNameStr = null; //已連接的設(shè)備名稱 9 private StringBuffer outSb; //發(fā)送的字符信息 10 private BluetoothAdapter btAdapter = null; //本地藍(lán)牙適配器 11 private MyService myService = null; // Service引用 12 public void onCreate(Bundle savedInstanceState) { 13 super.onCreate(savedInstanceState); 14 setContentView(R.layout.main); 15 btAdapter = BluetoothAdapter.getDefaultAdapter(); //獲取本地藍(lán)牙適配器 16 } 17 public void onStart() { 18 super.onStart(); 19 //如果藍(lán)牙沒(méi)有開(kāi)啟,提示開(kāi)啟藍(lán)牙,并退出Activity 20 if (! btAdapter.isEnabled()) { 21 Toast.makeText(this, "請(qǐng)先開(kāi)啟藍(lán)牙!", Toast.LENGTH_LONG).show(); 22 finish(); 23 } else { //否則初始化聊天的控件 24 if (myService == null) 25 initChat(); 26 }} 27 public synchronized void onResume() { 28 super.onResume(); 29 if (myService ! = null) { //創(chuàng)建并開(kāi)啟Service 30 if (myService.getState() == MyService.STATE_NONE) { //如果Service為空狀態(tài) 31 myService.start(); //開(kāi)啟Service 32 }}} 33 private void initChat() { 34 outEt = (EditText) findViewById(R.id.edit_text_out); //獲取編輯文本框的引用 35 sendBtn = (Button) findViewById(R.id.button_send); //獲取發(fā)送按鈕引用,并為其添加監(jiān)聽(tīng) 36 sendBtn.setOnClickListener(new OnClickListener() { 37 public void onClick(View v) { 38 //獲取編輯文本框中的文本內(nèi)容,并發(fā)送消息 39 TextView view = (TextView) findViewById(R.id.edit_text_out); 40 String message = view.getText().toString(); 41 sendMessage(message); 42 }}); 43 myService = new MyService(this, mHandler); //創(chuàng)建Service對(duì)象 44 outSb = new StringBuffer(""); //初始化存儲(chǔ)發(fā)送消息的StringBuffer 45 } 46 public void onDestroy() { 47 super.onDestroy(); 48 if (myService ! = null) { //停止Service 49 myService.stop(); 50 }} 51 private void sendMessage(String message) { //發(fā)送消息的方法 52 ……//此處省略了部分源代碼,將在后面步驟中給出 53 }} 54 //處理從Service發(fā)來(lái)消息的Handler 55 private final Handler mHandler = new Handler() { 56 ……//此處省略了部分源代碼,將在后面步驟中給出 57 }}}; 58 public void onActivityResult(int requestCode, int resultCode, Intent data) { 59 ……//此處省略了部分源代碼,將在后面步驟中給出 60 }} 61 public boolean onPrepareOptionsMenu(Menu menu) { 62 //啟動(dòng)設(shè)備列表Activity搜索設(shè)備 63 Intent serverIntent = new Intent(this, MyDeviceListActivity.class); 64 startActivityForResult(serverIntent, 1); 65 return true; 66 }}
? 第6-16行是該類成員變量的定義及重寫(xiě)的onCreate方法,其主要工作為跳轉(zhuǎn)到主界面并通過(guò)BluetoothAdapter類的靜態(tài)方法getDefaultAdapter獲取藍(lán)牙適配器對(duì)象的引用。
? 第17-26行重寫(xiě)了onStart方法。在該方法中查看藍(lán)牙是否開(kāi)啟,如果藍(lán)牙沒(méi)有開(kāi)啟,則提示開(kāi)啟藍(lán)牙,并退出Activity;否則調(diào)用initChat方法初始化聊天界面。
? 第27-32行重寫(xiě)了onResume方法。在該方法中檢測(cè)是否已經(jīng)啟動(dòng)了后臺(tái)服務(wù),若后臺(tái)服務(wù)為空,則調(diào)用其start方法,開(kāi)啟后臺(tái)服務(wù)。此后臺(tái)服務(wù)用于管理藍(lán)牙的連接及數(shù)據(jù)的收發(fā),具體開(kāi)發(fā)在后面進(jìn)行介紹。
? 第33-45行為初始化聊天界面的方法。其中首先為發(fā)送按鈕添加了監(jiān)聽(tīng)器,接著創(chuàng)建了后臺(tái)服務(wù)對(duì)象與字符串緩沖對(duì)象。
? 第51-60行省略了發(fā)送消息的sendMessage方法、onActivityResult方法及Handler內(nèi)部類的代碼,這些省略部分將在后面進(jìn)行詳細(xì)介紹。
? 第61-66行為每次按下Menu鍵調(diào)用的方法。在該方法中創(chuàng)建一個(gè)Intent消息,通過(guò)其啟動(dòng)搜索設(shè)備列表的Activity。其中的“1”與onActivityResult方法中的“1”相對(duì)應(yīng)。
(2)接下來(lái)介紹Sample2_9_Activity類中省略的sendMessage方法。該方法負(fù)責(zé)發(fā)送消息,其代碼如下。
代碼位置:見(jiàn)本書(shū)隨書(shū)中源代碼/第2章/Sample2_9/src/com/bn/pp9目錄下的Sample2_9_Activity.java。
1 private void sendMessage(String message) { //發(fā)送消息的方法 2 //先檢查是否已經(jīng)連接到設(shè)備 3 if (myService.getState() ! = MyService.STATE_CONNECTED) { 4 Toast.makeText(this, R.string.not_connected, Toast.LENGTH_SHORT) .show(); 5 return; 6 } 7 if (message.length() > 0) { //如果消息不為空再發(fā)送消息 8 byte[] send = message.getBytes(); //獲取發(fā)送消息的字節(jié)數(shù)組,并發(fā)送 9 myService.write(send); 10 outSb.setLength(0); //消除StringBuffer和文本框的內(nèi)容 11 outEt.setText(outSb); 12 }}
? 第1-6行檢查是否已成功連接到設(shè)備,如果沒(méi)有連接成功,則彈出提示信息。
? 第7-12行為在連接成功的情況下,判斷待發(fā)送的消息是否為空,若消息不為空,則再將信息以字節(jié)數(shù)組的形式,通過(guò)myService的write方法發(fā)送出去。
(3)然后介紹前面省略的處理從Service發(fā)來(lái)消息的Handler類的開(kāi)發(fā),用下列代碼替代前面Sample2_9_Activity類中的第55-57行代碼。
代碼位置:見(jiàn)本書(shū)隨書(shū)中源代碼/第2章/Sample2_9/src/com/bn/pp9目錄下的Sample2_9_Activity.java。
1 // 處理從Service發(fā)來(lái)消息的Handler 2 private final Handler mHandler = new Handler() { 3 public void handleMessage(Message msg) { 4 switch (msg.what) { 5 case Constant.MSG_READ: 6 byte[] readBuf = (byte[]) msg.obj; 7 String readMessage = new String(readBuf, 0, msg.arg1); //創(chuàng)建要發(fā)送信息的字符串 8 Toast.makeText(Sample2_9_Activity.this, 9 connectedNameStr + ": " + readMessage, 10 Toast.LENGTH_LONG).show(); 11 break; 12 case Constant.MSG_DEVICE_NAME: 13 //獲取已連接的設(shè)備名稱 14 connectedNameStr = msg.getData().getString( 15 Constant.DEVICE_NAME); 16 Toast.makeText(getApplicationContext(), //并彈出提示信息 17 "已連接到 " + connectedNameStr, Toast.LENGTH_SHORT) 18 .show(); 19 break; 20 }}};
? 第4-11行為收到待讀數(shù)據(jù)信息時(shí)的處理代碼,其功能為從msg中獲取字節(jié)數(shù)組形式的信息,將其轉(zhuǎn)化成字符串,并以Toast的形式顯示在屏幕上。
? 第12-19行為收到已連接設(shè)備名稱信息的處理代碼,其功能為獲取已連接設(shè)備的名稱,并彈出Toast提示信息。
(4)接下來(lái)開(kāi)發(fā)重寫(xiě)的onActivityResult方法,用下列代碼替代前面Sample2_9_Activity類中的第58-60行代碼。
代碼位置:見(jiàn)本書(shū)隨書(shū)中源代碼/第2章/Sample2_9/src/com/bn/pp9目錄下的Sample2_9_Activity.java。
1 public void onActivityResult(int requestCode, int resultCode, Intent data) { 2 switch (requestCode) { 3 case 1:// 如果設(shè)備列表Activity返回一個(gè)連接的設(shè)備 4 if (resultCode == Activity.RESULT_OK) { 5 String address = data.getExtras().getString(//獲取設(shè)備的MAC地址 6 MyDeviceListActivity.EXTRA_DEVICE_ADDR); 7 BluetoothDevice device = btAdapter 8 .getRemoteDevice(address); //獲取BLuetoothDevice對(duì)象 9 myService.connect(device); //連接該設(shè)備 10 } 11 break; 12 }};
? 第1行為該方法的簽名,其中requestCode參數(shù)用來(lái)標(biāo)識(shí)是從哪一個(gè)Activity跳轉(zhuǎn)到該Activity, resultCode參數(shù)用來(lái)表示返回值的狀態(tài),data參數(shù)里包含了返回的數(shù)據(jù)。
? 第2-12行功能為:若設(shè)備列表Activity成功返回了選中的目標(biāo)連接設(shè)備,則調(diào)用藍(lán)牙適配器對(duì)象的getRemoteDevice方法獲取遠(yuǎn)端設(shè)備對(duì)象,然后將遠(yuǎn)端設(shè)備對(duì)象傳遞給自定義的后臺(tái)服務(wù)連接該設(shè)備。
(5)下一步將開(kāi)發(fā)用于顯示可連接設(shè)備列表的Activity類—MyDeviceListActivity類,其代碼如下。
代碼位置:見(jiàn)本書(shū)隨書(shū)中源代碼/第2章/Sample2_9/src/com/bn/pp9目錄下的MyDeviceList Activity.java。
1 package com.bn.pp9; 2 import java.util.Set; //引入相關(guān)類 3 ……//此處省略了部分類的引入代碼,讀者可自行查看隨書(shū)的源代碼 4 import android.widget.AdapterView.OnItemClickListener; //引入相關(guān)類 5 public class MyDeviceListActivity extends Activity { 6 public static String EXTRA_DEVICE_ADDR = "device_address"; //extra信息名稱 7 private BluetoothAdapter myBtAdapter; //成員變量 8 private ArrayAdapter<String> myAdapterPaired; 9 private ArrayAdapter<String> myAdapterNew; 10 @Override 11 protected void onCreate(Bundle savedInstanceState) { 12 super.onCreate(savedInstanceState); 13 requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); //設(shè)置窗口 14 setContentView(R.layout.device_list); 15 //設(shè)置為當(dāng)結(jié)果是Activity.RESULT_CANCELED時(shí),返回到該Activity的調(diào)用者 16 setResult(Activity.RESULT_CANCELED); 17 Button scanBtn = (Button) findViewById(R.id.button_scan); //初始化搜索按鈕 18 scanBtn.setOnClickListener(new OnClickListener() { 19 public void onClick(View v) { 20 doDiscovery(); 21 v.setVisibility(View.GONE); //使按鈕不可見(jiàn) 22 }}); 23 //初始化適配器 24 myAdapterPaired = new ArrayAdapter<String>(this, 25 R.layout.device_name); //已配對(duì)的 26 myAdapterNew = new ArrayAdapter<String>(this, 27 R.layout.device_name); //新發(fā)現(xiàn)的 28 //將已配對(duì)的設(shè)備放入列表中 29 ListView lvPaired = (ListView) findViewById(R.id.paired_devices); 30 lvPaired.setAdapter(myAdapterPaired); 31 lvPaired.setOnItemClickListener(mDeviceClickListener); 32 //將新發(fā)現(xiàn)的設(shè)備放入列表中 33 ListView lvNewDevices = (ListView) findViewById(R.id.new_devices); 34 lvNewDevices.setAdapter(myAdapterNew); 35 lvNewDevices.setOnItemClickListener(mDeviceClickListener); 36 //注冊(cè)發(fā)現(xiàn)設(shè)備時(shí)的廣播 37 IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND); 38 this.registerReceiver(mReceiver, filter); 39 //注冊(cè)搜索完成時(shí)的廣播 40 filter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED); 41 this.registerReceiver(mReceiver, filter); 42 //獲取本地藍(lán)牙適配器 43 myBtAdapter = BluetoothAdapter.getDefaultAdapter(); 44 //獲取已配對(duì)的設(shè)備 45 Set<BluetoothDevice> pairedDevices = myBtAdapter.getBondedDevices(); 46 //將所有已配對(duì)設(shè)備信息放入列表中 47 if (pairedDevices.size() > 0) { 48 findViewById(R.id.title_paired_devices).setVisibility(View.VISIBLE); 49 for (BluetoothDevice device : pairedDevices) { 50 myAdapterPaired.add(device.getName() + "\n" 51 + device.getAddress()); 52 }} else { 53 String noDevices = getResources().getText(R.string.none_paired). toString(); 54 myAdapterPaired.add(noDevices); 55 }} 56 protected void onDestroy() { 57 ……//此處省略了部分源代碼,該方法非常簡(jiǎn)單,請(qǐng)讀者自行查看隨書(shū)中的源代碼。 58 } 59 private void doDiscovery() { 60 ……//此處省略了用藍(lán)牙適配器搜索設(shè)備方法的部分源代碼,將在后面步驟中給出 61 } 62 private OnItemClickListener mDeviceClickListener = new OnItemClickListener() { 63 ……//此處省略了列表中設(shè)備按下時(shí)的監(jiān)聽(tīng)器的部分源代碼,將在后面步驟中給出 64 }; 65 private final BroadcastReceiver mReceiver = new BroadcastReceiver() { 66 ……//此處省略了監(jiān)聽(tīng)搜索到的設(shè)備的部分源代碼,將在后面步驟中給出 67 }; }
? 第6-9行為該類成員變量的定義。其中有extra信息的名稱、藍(lán)牙適配器對(duì)象的引用、已配對(duì)設(shè)備和新搜索到設(shè)備的列表對(duì)應(yīng)適配器的聲明。
? 第10-55行為重寫(xiě)的onCreate方法,其中首先初始化了成員變量及布局文件中的相關(guān)資源,然后注冊(cè)發(fā)現(xiàn)設(shè)備和搜索完成時(shí)的廣播接收器,并初始化了已配對(duì)設(shè)備列表。
? 第59-67行省略了doDiscovery方法、mDeviceClickListener監(jiān)聽(tīng)器及mReceiver內(nèi)部類的具體代碼,這些代碼將在后面進(jìn)行詳細(xì)介紹。
提示
由于篇幅所限,設(shè)備列表布局文件device_list.xml代碼的開(kāi)發(fā)在此沒(méi)有進(jìn)行介紹,需要的讀者請(qǐng)自行查看隨書(shū)中的源代碼。
(6)接下來(lái)將開(kāi)發(fā)前面省略的負(fù)責(zé)搜索設(shè)備的doDiscovery方法,其代碼如下。
代碼位置:見(jiàn)本書(shū)隨書(shū)中源代碼/第2章/Sample2_9/src/com/bn/pp9目錄下的MyDeviceList Activity.java。
1 private void doDiscovery() { 2 //在標(biāo)題上顯示正在搜索的標(biāo)志 3 setProgressBarIndeterminateVisibility(true); 4 setTitle(R.string.scanning); 5 //顯示搜索到的新設(shè)備的副標(biāo)題 6 findViewById(R.id.title_new_devices).setVisibility(View.VISIBLE); 7 if (myBtAdapter.isDiscovering()) { //如果正在搜索,取消本次搜索 8 myBtAdapter.cancelDiscovery(); 9 } 10 myBtAdapter.startDiscovery(); //開(kāi)始搜索 11 }
說(shuō)明
在該方法中首先更改UI界面(如在標(biāo)題上顯示正在搜索的標(biāo)志),完成相關(guān)操作后,調(diào)用藍(lán)牙適配器的startDiscovery方法進(jìn)行設(shè)備搜索。
(7)下一步將開(kāi)發(fā)前面省略的監(jiān)聽(tīng)器mDeviceClickListener的對(duì)應(yīng)代碼,具體內(nèi)容如下。
代碼位置:見(jiàn)本書(shū)隨書(shū)中源代碼/第2章/Sample2_9/src/com/bn/pp9目錄下的MyDeviceList Activity.java。
1 private OnItemClickListener mDeviceClickListener = new OnItemClickListener() { 2 public void onItemClick(AdapterView<? > av, View v, int arg2, long arg3) { 3 myBtAdapter.cancelDiscovery(); //取消搜索 4 String msg = ((TextView) v).getText().toString(); //獲取設(shè)備的MAC地址 5 String address = msg.substring(msg.length() - 17); 6 Intent intent = new Intent(); //創(chuàng)建帶有MAC地址的Intent 7 intent.putExtra(EXTRA_DEVICE_ADDR, address); 8 setResult(Activity.RESULT_OK, intent); //設(shè)置結(jié)果并退出Activity 9 finish(); 10 }};
? 上述實(shí)現(xiàn)監(jiān)聽(tīng)功能的onItemClick方法中,首先取消搜索,然后獲取并發(fā)送設(shè)備的MAC地址給主界面Activity。
? 第8行setResult方法中的參數(shù)Activity.RESULT_OK表示成功地獲取了要連接的設(shè)備的硬件地址。接收方的Activity根據(jù)Activity.RESULT_OK會(huì)做出相應(yīng)的處理,前面步驟4中已經(jīng)進(jìn)行了介紹。
(8)接下來(lái)將開(kāi)發(fā)前面省略的用于接收設(shè)備搜索到的系統(tǒng)廣播的廣播接收器mReceiver,其代碼如下。
代碼位置:見(jiàn)本書(shū)隨書(shū)中源代碼/第2章/Sample2_9/src/com/bn/pp9目錄下的MyDeviceList Activity.java。
1 //監(jiān)聽(tīng)搜索到的設(shè)備的BroadcastReceiver 2 private final BroadcastReceiver mReceiver = new BroadcastReceiver() { 3 @Override 4 public void onReceive(Context context, Intent intent) { 5 String action = intent.getAction(); 6 if (BluetoothDevice.ACTION_FOUND.equals(action)) { //如果找到設(shè)備 7 //從Intent中獲取BluetoothDevice對(duì)象 8 BluetoothDevice device = intent 9 .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 10 //如果沒(méi)有配對(duì),將設(shè)備加入新設(shè)備列表 11 if (device.getBondState() ! = BluetoothDevice.BOND_BONDED) { 12 myAdapterNew.add(device.getName() + "\n" 13 + device.getAddress()); 14 } 15 //當(dāng)搜索完成后,改變Activity的標(biāo)題 16 } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED 17 .equals(action)) { 18 setProgressBarIndeterminateVisibility(false); 19 setTitle(R.string.select_device); 20 if (myAdapterNew.getCount() == 0) { //沒(méi)有找到設(shè)備 21 String noDevices = getResources().getText( 22 R.string.none_found).toString(); 23 myAdapterNew.add(noDevices); 24 }}}};
? 第6-14行為此廣播接收器收到設(shè)備信息時(shí)對(duì)應(yīng)的處理代碼。首先從Intent中獲取BluetoothDevice對(duì)象,然后判斷該設(shè)備是否已經(jīng)配對(duì)。若沒(méi)有配對(duì),則將設(shè)備加入新設(shè)備列表,否則不進(jìn)行任何處理。
? 第16-24行為此廣播接收器收到完成設(shè)備搜索信息時(shí)對(duì)應(yīng)的處理代碼,主要是改變對(duì)話框的標(biāo)題等信息。如果沒(méi)有找到任何設(shè)備,則在界面中顯示沒(méi)有找到設(shè)備的信息。
(9)再接著開(kāi)發(fā)自定義的用于后臺(tái)服務(wù)的MyService類,其提供的后臺(tái)服務(wù)主要是關(guān)于藍(lán)牙設(shè)備的連接、數(shù)據(jù)的收發(fā)等方面,具體代碼如下。
代碼位置:見(jiàn)本書(shū)隨書(shū)中源代碼/第2章/Sample2_9/src/com/bn/pp9目錄下的MyService.java。
1 package com.bn.pp9; //聲明包 2 import java.io.IOException; //引入相關(guān)類 3 ……//此處省略了部分類的引入代碼,讀者可自行查看隨書(shū)的源代碼 4 import android.os.Message; //引入相關(guān)類 5 public class MyService { //用于管理連接的Service 6 //本應(yīng)用的唯一UUID 7 private static final UUID MY_UUID = 8 UUID.fromString("fa87c0d0-afac-11de-8a39-0800200c9a66"); 9 private final BluetoothAdapter btAdapter; //成員變量 10 private final Handler myHandler; 11 private AcceptThread myAcceptThread; 12 private ConnectThread myConnectThread; 13 private ConnectedThread myConnectedThread; 14 private int myState; 15 //表示當(dāng)前連接狀態(tài)的常量 16 public static final int STATE_NONE = 0; //什么也沒(méi)做 17 public static final int STATE_LISTEN = 1; //正在監(jiān)聽(tīng)連接 18 public static final int STATE_CONNECTING = 2; //正在連接 19 public static final int STATE_CONNECTED = 3; //已連接到設(shè)備 20 //構(gòu)造器 21 public MyService(Context context, Handler handler) { 22 btAdapter = BluetoothAdapter.getDefaultAdapter(); 23 myState = STATE_NONE; 24 myHandler = handler; 25 } 26 private synchronized void setState(int state) { //設(shè)置當(dāng)前連接狀態(tài)的方法 27 myState = state; 28 } 29 public synchronized int getState() { //獲取當(dāng)前連接狀態(tài)的方法 30 return myState; 31 } 32 public synchronized void start() { //開(kāi)啟Service的方法 33 //關(guān)閉不必要的線程 34 if (myConnectThread ! = null) {myConnectThread.cancel(); myConnectThread = null; } 35 if (myConnectedThread ! = null) {myConnectedThread.cancel(); myConnectedThread= null; } 36 if (myAcceptThread == null) { //開(kāi)啟線程監(jiān)聽(tīng)連接 37 myAcceptThread = new AcceptThread(); 38 myAcceptThread.start(); 39 } 40 setState(STATE_LISTEN); 41 } 42 public synchronized void stop() { //停止所有線程的方法 43 if(myConnectThread ! =null){myConnectThread.cancel(); myConnectThread=null; } 44 if (myConnectedThread ! = null) {myConnectedThread.cancel(); myConnectedThread= null; } 45 if (myAcceptThread ! = null) {myAcceptThread.cancel(); myAcceptThread = null; } 46 setState(STATE_NONE); 47 } 48 public void write(byte[] out) { //向ConnectedThread寫(xiě)入數(shù)據(jù)的方法 49 ConnectedThread tmpCt; //創(chuàng)建臨時(shí)對(duì)象引用 50 synchronized (this) { //鎖定ConnectedThread 51 if (myState ! = STATE_CONNECTED) return; 52 tmpCt = myConnectedThread; 53 } 54 tmpCt.write(out); //寫(xiě)入數(shù)據(jù) 55 } 56 public synchronized void connect(BluetoothDevice device) {//連接設(shè)備的方法 57 ……//此處省略了部分源代碼,將在后面步驟中給出 58 } 59 //開(kāi)啟管理和已連接的設(shè)備間通話的線程的方法 60 public synchronized void connected(BluetoothSocket socket, BluetoothDevice device) { 61 ……//此處省略了部分源代碼,將在后面步驟中給出 62 } 63 ……//以下省略了3個(gè)主要線程類的代碼,將在后面介紹 64 }
? 第6-19行為成員變量的定義,其中第7行定義了本應(yīng)用的UUID(全局唯一標(biāo)識(shí))。第16-19行定義了表示當(dāng)前連接狀態(tài)的一些常量,各常量代表的含義詳見(jiàn)代碼注釋。
? 第26-31行為設(shè)置和獲取當(dāng)前連接狀態(tài)的方法,這兩個(gè)方法都是用synchronized修飾符修飾的,這是為了避免異步線程可能同時(shí)讀取myState時(shí)產(chǎn)生的問(wèn)題。
? 第32-41行為開(kāi)啟后臺(tái)服務(wù)的方法。在該方法中首先關(guān)閉不必要的線程,然后開(kāi)啟線程監(jiān)聽(tīng)連接,并設(shè)置當(dāng)前狀態(tài)為正在監(jiān)聽(tīng)。
? 第42-47行為停止所有線程的stop方法,主要工作是調(diào)用各線程的cancel方法。
? 第48-55行為向連接線程ConnectedThread寫(xiě)入數(shù)據(jù)的write方法,這里只調(diào)用ConnectedThread對(duì)象的write方法即可。
提示
第56-63行省略了connect方法、connected方法及3個(gè)主要線程類的代碼,這些代碼的開(kāi)發(fā)將在下面進(jìn)行詳細(xì)講解。
(10)接下來(lái)將開(kāi)發(fā)負(fù)責(zé)連接設(shè)備的connect方法,其代碼如下。
代碼位置:見(jiàn)本書(shū)隨書(shū)中源代碼/第2章/Sample2_9/src/com/bn/pp9目錄下的MyService.java。
1 public synchronized void connect(BluetoothDevice device) { //連接設(shè)備的方法 2 //關(guān)閉不必要的線程 3 if (myState == STATE_CONNECTING) { 4 if (myConnectThread ! = null) {myConnectThread.cancel(); myConnectThread = null; } 5 } 6 if (myConnectedThread ! = null) {myConnectedThread.cancel(); myConnectedThread = null; } 7 myConnectThread = new ConnectThread(device); //開(kāi)啟線程連接設(shè)備 8 myConnectThread.start(); 9 setState(STATE_CONNECTING); 10 }
說(shuō)明
該方法的實(shí)現(xiàn)比較簡(jiǎn)單,首先關(guān)閉不必要的線程,然后開(kāi)啟線程連接設(shè)備,并設(shè)置當(dāng)前狀態(tài)為正在連接。
(11)接著開(kāi)發(fā)負(fù)責(zé)開(kāi)啟連接線程(ConnectedThread)并向myHandler發(fā)送設(shè)備名稱消息的connected方法,其代碼如下。
代碼位置:見(jiàn)本書(shū)隨書(shū)中源代碼/第2章/Sample2_9/src/com/bn/pp9目錄下的MyService.java。
1 public synchronized void connected(BluetoothSocket socket, BluetoothDevice device) { 2 //關(guān)閉不必要的線程 3 if (myConnectThread ! = null) {myConnectThread.cancel(); myConnectThread = null; } 4 if (myConnectedThread ! = null) {myConnectedThread.cancel(); myConnectedThread = null; } 5 if (myAcceptThread ! = null) {myAcceptThread.cancel(); myAcceptThread = null; } 6 myConnectedThread = new ConnectedThread(socket); //創(chuàng)建并啟動(dòng)ConnectedThread 7 myConnectedThread.start(); 8 //發(fā)送已連接的設(shè)備名稱到主界面Activity 9 Message msg = myHandler.obtainMessage(Constant.MSG_DEVICE_NAME); 10 Bundle bundle = new Bundle(); 11 bundle.putString(Constant.DEVICE_NAME, device.getName()); 12 msg.setData(bundle); 13 myHandler.sendMessage(msg); 14 setState(STATE_CONNECTED); 15 }
提示
在該方法中首先要關(guān)閉不必要的線程,然后創(chuàng)建并開(kāi)啟ConnectedThread線程,同時(shí)發(fā)送已連接的設(shè)備名稱到主界面Activity,最后設(shè)置當(dāng)前狀態(tài)為已連接。
(12)接下來(lái)將開(kāi)發(fā)前面省略的MyService類中的3個(gè)主要線程,首先開(kāi)發(fā)負(fù)責(zé)監(jiān)聽(tīng)連接請(qǐng)求的AcceptThread,其代碼如下。
代碼位置:見(jiàn)本書(shū)隨書(shū)中源代碼/第2章/Sample2_9/src/com/bn/pp9目錄下的MyService.java。
1 private class AcceptThread extends Thread { //用于監(jiān)聽(tīng)連接的線程 2 //本地服務(wù)器端ServerSocket 3 private final BluetoothServerSocket mmServerSocket; 4 public AcceptThread() { 5 BluetoothServerSocket tmpSS = null; 6 try { //創(chuàng)建用于監(jiān)聽(tīng)的服務(wù)器端ServerSocket 7 tmpSS = btAdapter.listenUsingRfcommWithServiceRecord("BluetoothChat", MY_UUID); 8 }catch (IOException e) {e.printStackTrace(); } 9 mmServerSocket = tmpSS; 10 } 11 public void run() { 12 setName("AcceptThread"); //設(shè)置線程名稱 13 BluetoothSocket socket = null; 14 while (myState ! = STATE_CONNECTED) { //如果沒(méi)有連接到設(shè)備 15 try { 16 socket = mmServerSocket.accept(); //獲取連接的Socket 17 }catch (IOException e) {e.printStackTrace(); break; } 18 if (socket ! = null) { //如果連接成功 19 synchronized (MyService.this) { 20 switch (myState) { 21 case STATE_LISTEN: 22 case STATE_CONNECTING: 23 //開(kāi)啟管理連接后數(shù)據(jù)交流的線程 24 connected(socket, socket.getRemoteDevice()); 25 break; 26 case STATE_NONE: 27 case STATE_CONNECTED: 28 try { //關(guān)閉新Socket 29 socket.close(); 30 } catch (IOException e) { 31 e.printStackTrace(); 32 } 33 break; 34 }}}}} 35 public void cancel() { //關(guān)閉本地服務(wù)器端ServerSocket的方法 36 try { 37 mmServerSocket.close(); //調(diào)用close方法關(guān)閉ServerSocket 38 }catch (IOException e) {e.printStackTrace(); } 39 }}
? 第3-10行為該線程類的成員變量及構(gòu)造器聲明,在構(gòu)造器中創(chuàng)建了用于在服務(wù)端監(jiān)聽(tīng)并接收連接請(qǐng)求的BluetoothServerSocket對(duì)象。
? 第11-34行為重寫(xiě)的用于描述線程任務(wù)的run方法。在該方法的while循環(huán)中,如果沒(méi)有連接到設(shè)備則一直調(diào)用BluetoothServerSocket的accept方法進(jìn)入阻塞狀態(tài),等待連接請(qǐng)求的到來(lái)。
(13)下面開(kāi)發(fā)負(fù)責(zé)用于嘗試連接其他設(shè)備的ConnectThread,其代碼如下。
代碼位置:見(jiàn)本書(shū)隨書(shū)中源代碼/第2章/Sample2_9/src/com/bn/pp9目錄下的MyService.java。
1 private class ConnectThread extends Thread { //用于嘗試連接其他設(shè)備的線程 2 private final BluetoothSocket myBtSocket; 3 private final BluetoothDevice mmDevice; 4 public ConnectThread(BluetoothDevice device) { 5 mmDevice = device; 6 BluetoothSocket tmp = null; 7 //通過(guò)正在連接的設(shè)備獲取BluetoothSocket 8 try { 9 tmp = device.createRfcommSocketToServiceRecord(MY_UUID); 10 }catch (IOException e) {e.printStackTrace(); } 11 myBtSocket = tmp; 12 } 13 public void run() { 14 setName("ConnectThread"); 15 btAdapter.cancelDiscovery(); //取消搜索設(shè)備 16 try { //連接到BluetoothSocket 17 myBtSocket.connect(); //嘗試連接 18 }catch (IOException e) { 19 setState(STATE_LISTEN); //連接斷開(kāi)后,設(shè)置狀態(tài)為正在監(jiān)聽(tīng) 20 try { //關(guān)閉Socket 21 myBtSocket.close(); 22 }catch (IOException e2) {e.printStackTrace(); } 23 MyService.this.start(); //如果連接不成功,重新開(kāi)啟Service 24 return; 25 } 26 synchronized (MyService.this) { //將ConnectThread線程置空 27 myConnectThread = null; 28 } 29 connected(myBtSocket, mmDevice); //開(kāi)啟管理連接后數(shù)據(jù)交流的線程 30 } 31 public void cancel() { 32 try { 33 myBtSocket.close(); 34 } catch (IOException e) {e.printStackTrace(); } 35 }}
? 第2-12行為該線程類的成員變量及構(gòu)造器的聲明。在構(gòu)造器中通過(guò)正在連接的設(shè)備獲取BluetoothSocket對(duì)象的引用。
? 第13-30行為重寫(xiě)的run方法,在該方法中主要調(diào)用BluetoothServerSocket類的connect方法嘗試連接。如果連接被斷開(kāi),改變相應(yīng)的狀態(tài),并釋放資源。
(14)接下來(lái)將開(kāi)發(fā)負(fù)責(zé)連接成功后信息收發(fā)的ConnectedThread,其代碼如下。
代碼位置:見(jiàn)本書(shū)隨書(shū)中源代碼/第2章/Sample2_9/src/com/bn/pp9目錄下的MyService.java。
1 private class ConnectedThread extends Thread { 2 private final BluetoothSocket myBtSocket; 3 private final InputStream mmInStream; 4 private final OutputStream myOs; 5 public ConnectedThread(BluetoothSocket socket) { 6 myBtSocket = socket; 7 InputStream tmpIn = null; 8 OutputStream tmpOut = null; 9 try { //獲取BluetoothSocket的輸入輸出流 10 tmpIn = socket.getInputStream(); 11 tmpOut = socket.getOutputStream(); 12 } catch (IOException e) {e.printStackTrace(); } //打印異常 13 mmInStream = tmpIn; 14 myOs = tmpOut; 15 } 16 public void run() { 17 byte[] buffer = new byte[1024]; 18 int bytes; 19 while (true) { //一直監(jiān)聽(tīng)輸入流 20 try { 21 bytes = mmInStream.read(buffer); //從輸入流中讀入數(shù)據(jù) 22 //將讀入的數(shù)據(jù)發(fā)送到主界面Activity 23 myHandler.obtainMessage(Constant.MSG_READ, bytes, -1, buffer) 24 .sendToTarget(); 25 } catch (IOException e) { 26 e.printStackTrace(); 27 setState(STATE_LISTEN); //連接斷開(kāi)后設(shè)置狀態(tài)為正在監(jiān)聽(tīng) 28 break; 29 }}} 30 public void write(byte[] buffer) { //向輸出流中寫(xiě)入數(shù)據(jù)的方法 31 try { 32 myOs.write(buffer); 33 } catch (IOException e) {e.printStackTrace(); } 34 } 35 public void cancel() { 36 try { 37 myBtSocket.close(); //關(guān)閉Socket 38 } catch (IOException e) {e.printStackTrace(); } 39 }}
? 第2-15行為該線程類的成員變量及構(gòu)造器的聲明。在構(gòu)造器中通過(guò)傳遞進(jìn)來(lái)的BluetoothSocket對(duì)象的引用獲取基于藍(lán)牙連接的輸入輸出流。
? 第16-29行為重寫(xiě)的run方法。在該方法中通過(guò)while循環(huán)一直監(jiān)聽(tīng)輸入流,一旦輸入流中有數(shù)據(jù),便從輸入流中讀出數(shù)據(jù),同時(shí)將數(shù)據(jù)發(fā)送到主界面Activity。
? 第30-34行為向輸出流中寫(xiě)入數(shù)據(jù)的方法,該方法只是對(duì)輸出流對(duì)象的write方法進(jìn)行了簡(jiǎn)單封裝。
(15)代碼開(kāi)發(fā)完成后還需要在AndroidManifest.xml中聲明BLUETOOTH權(quán)限,其代碼如下。
代碼位置:見(jiàn)本書(shū)隨書(shū)中源代碼/第2章/Sample2_9目錄下的AndroidManifest.xml。
1 <! --聲明BLUETOOTH權(quán)限--> 2 <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> 3 <uses-permission android:name="android.permission.BLUETOOTH" />
提示
上述代碼應(yīng)插入到AndroidManifest.xml中的“</manifest>”標(biāo)簽之前。
(16)在AndroidManifest.xml文件中聲明完BLUETOOTH權(quán)限后,接下來(lái)需要在AndroidManifest.xml文件中注冊(cè)自己開(kāi)發(fā)的MyDeviceListActivity,具體代碼如下。
代碼位置:見(jiàn)本書(shū)隨書(shū)中源代碼/第2章/Sample2_9目錄下的AndroidManifest.xml。
1 <! --注冊(cè)MyDeviceListActivity--> 2 <activity android:name=".MyDeviceListActivity" 3 android:label="@string/select_device" 4 android:theme="@android:style/Theme.Dialog" 5 android:configChanges="orientation|keyboardHidden" 6 />
提示
上述代碼應(yīng)插入到AndroidManifest.xml中的<application>與</application>標(biāo)簽之間。請(qǐng)讀者注意的是,在創(chuàng)建項(xiàng)目時(shí),指定的Activity其配置代碼系統(tǒng)會(huì)自動(dòng)加入到AndroidManifest.xml文件中,但自己在項(xiàng)目中再開(kāi)發(fā)其他的Activity時(shí)就需要自行添加相應(yīng)的配置代碼到AndroidManifest.xml文件中了。
- 微信小游戲開(kāi)發(fā):后端篇
- Android游戲開(kāi)發(fā)實(shí)踐指南
- 網(wǎng)絡(luò)游戲角色設(shè)計(jì)與制作實(shí)戰(zhàn)(第二版)
- Unity 5.X 3D游戲開(kāi)發(fā)技術(shù)詳解與典型案例
- 觸摸屏游戲設(shè)計(jì)
- 游戲開(kāi)發(fā)實(shí)戰(zhàn)寶典
- Cocos Creator 3.x 游戲開(kāi)發(fā)入門(mén)與實(shí)戰(zhàn)
- 傳奇 奇幻插畫(huà)創(chuàng)作藝術(shù)
- 妙趣橫生的游戲制作之旅
- 實(shí)例妙解Cocos2D-X游戲開(kāi)發(fā)
- 游戲數(shù)值百寶書(shū):成為優(yōu)秀的數(shù)值策劃
- 傳奇 游戲角色及場(chǎng)景設(shè)定藝術(shù)
- Unreal Engine 4 游戲開(kāi)發(fā)指南
- 電子游戲與多元智能培養(yǎng)
- 游戲藝術(shù):從傳統(tǒng)到現(xiàn)代的發(fā)展歷程