官术网_书友最值得收藏!

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文件中了。

主站蜘蛛池模板: 开远市| 陈巴尔虎旗| 山丹县| 翁源县| 遂平县| 郴州市| 莱西市| 朝阳区| 桦甸市| 铅山县| 鲁甸县| 武山县| 马鞍山市| 金塔县| 呼和浩特市| 丰台区| 资阳市| 安仁县| 潜山县| 苗栗市| 阿荣旗| 千阳县| 汉寿县| 苏尼特左旗| 义乌市| 台南市| 弥勒县| 长垣县| 来安县| 松滋市| 疏勒县| 博客| 浮梁县| 绿春县| 方山县| 江门市| 兰考县| 平昌县| 保亭| 丘北县| 钦州市|