安卓 藍芽通訊之聊天小程式
安卓 藍芽聊天小程式
一、簡述
記--簡單的藍芽聊天小程式。使用的是傳統藍芽開發。(某些手機由於Android版本原因需要新增新的許可權)
兩臺裝置開啟藍芽,一臺裝置設定藍芽可見性,另一臺裝置進行連線,然後互相收發資訊。
開發環境:win7-32bit, ADT,jdk1.7
測試手機:Android版本4.4.2
例子打包:連結: https://pan.baidu.com/s/1WXKD_Wan4tc9O86-1H4EQg 提取碼: g1ac
二、效果
三、工程結構
四、原始檔
MainActivity.java檔案
package com.liang.bluetooth; /* * 首先開啟藍芽並且開啟藍芽可見性是可以正常通訊的 * 問題1:靜默藍芽可見性問題 * 問題2:詢問式開啟藍芽問題 * 問題3:二次連線處理 * 問題4:資源釋放、退出處理 * 問題5:主執行緒不會等待AlertDialog返回,show出來後繼續執行。 解決:阻塞主執行緒,等待AlertDialog返回 * */ import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.UnsupportedEncodingException; import java.lang.reflect.Method; import java.util.UUID; import com.liang.bluetooth.DeviceListActivity; import com.liang.bluetooth.R; import android.annotation.SuppressLint; import android.app.Activity; import android.app.AlertDialog; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothServerSocket; import android.bluetooth.BluetoothSocket; import android.content.DialogInterface; import android.content.Intent; import android.os.Bundle; import android.os.Handler; import android.os.Message; //import android.view.Menu; //如使用選單加入此三包 //import android.view.MenuInflater; //import android.view.MenuItem; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.ScrollView; import android.widget.TextView; import android.widget.Toast; @SuppressLint({ "NewApi", "HandlerLeak" }) public class MainActivity extends Activity { private final static int REQUEST_CONNECT_DEVICE = 1; //用來表示 查詢裝置的請求碼 private final static int REQUEST_BT_ENABLE_CODE = 2; //用來表示 開啟藍芽的請求碼 private final static String MY_UUID = "00001101-0000-1000-8000-00805F9B34FB"; //SPP服務UUID號 private TextView tv_dev = null; //用來顯示本機藍芽裝置名稱 private TextView tv_reDev = null; //用來顯示連線的藍芽裝置名稱 private TextView tv_rmsg = null; //用來顯示接收到的資訊 private ScrollView sv = null; //訊息滾動條控制代碼,目的滾動到底部,顯示最新接收的訊息 private EditText edt_smsg = null; //傳送資訊輸入文字框 private Button btn_cnnt = null; //"連線"按鈕 private Button btn_send_file = null; //"傳送檔案"按鈕 (此功能還沒有新增) private Toast mToast = null; //提示框 private BluetoothAdapter mAdapter = null; //獲取本地藍芽介面卡 BluetoothDevice mRemoteDev = null; //用來表示其他藍芽裝置 BluetoothSocket mSocket = null; //藍芽通訊socket private BluetoothServerSocket mServerSocket = null;//服務socket AcceptThread accept_thread = null; //服務執行緒 等待別的藍芽裝置請求連線 ConnectThread connect_thread = null;//客戶端連線執行緒 主動連線其它藍芽裝置 PrintWriter writer = null;//傳送訊息給其它藍芽裝置 private int cs_flag = 0; //用來標識本機是作為1伺服器的還是2客戶端,在傳送訊息的時候要用到 /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); //設定主介面為 main.xml //1 初始化控制元件 init(); //2 請求開啟藍芽操作 if( mAdapter.isEnabled() )//已經開啟藍芽 { setDiscoverableTimeout(300, 2);//設定藍芽可見性 // 開啟服務執行緒 accept_thread = new AcceptThread(); accept_thread.start(); } else //還沒開啟藍芽則開啟 { openBlueTooth(MainActivity.this, 0, 2);//詢問開啟藍芽 } } /* * 初始化 * */ public void init() { mAdapter = BluetoothAdapter.getDefaultAdapter();//獲取藍芽介面卡 //判斷裝置是否支援藍芽 if(mAdapter == null) { //告訴使用者裝置不支援藍芽,然後退出 showAlertDialog("退出應用","抱歉!該裝置不支援藍芽。 ", "", "確定", 0); } //初始化控制元件 tv_dev = (TextView) findViewById(R.id.tv_dev); //得到本機藍芽裝置名稱顯示控制代碼 tv_dev.setText("本機藍芽名稱:"+mAdapter.getName()+"("+mAdapter.getAddress()+")");//顯示本機藍芽裝置名稱 tv_reDev = (TextView) findViewById(R.id.tv_reDev);//得到其他藍芽裝置名稱顯示控制代碼 tv_rmsg = (TextView) findViewById(R.id.in); //得到資料顯示控制代碼 sv = (ScrollView)findViewById(R.id.sv_list); //得到翻頁控制代碼(有滾動條) edt_smsg = (EditText)findViewById(R.id.edt_smsg); //得到輸入框控制代碼 btn_cnnt = (Button)findViewById(R.id.btn_cnnt);//得到"連線"按鈕 btn_send_file = (Button)findViewById(R.id.btn_send_file);//得到"傳送檔案"按鈕 } /* * 訊息處理佇列,對於accept、connect子執行緒一般不能直接更改UI, * 所以子執行緒通過傳送Handler訊息,讓Handler更改UI,(Handler類似獨立執行緒) */ private Handler mHandler = new Handler(){ public void handleMessage(Message msg){ super.handleMessage(msg); switch(msg.what) { case 1://彈出Toast提示框 showToast((String)msg.obj); break; case 2://確認連線 showAlertDialog("確認連線", "連線"+(String)msg.obj, "取消", "確定", 1); break; case 3://更改 所連線的藍芽裝置的資訊 tv_reDev.setText((String)msg.obj); break; case 4://更改"連線"按鈕的顯示文字 btn_cnnt.setText((String)msg.obj); break; case 5://關閉藍芽可見性 setDiscoverableTimeout(1, 3); break; case 9://顯示接收到的訊息 default: tv_rmsg.append((String)msg.obj); //顯示接收到的訊息 sv.scrollTo(0,tv_rmsg.getMeasuredHeight()); //滾動到最新訊息處 break; } } }; /* * 傳送handler訊息 * @param content 訊息內容 * @param what 訊息型別 */ private void sendHandlerMsg(String content, int what) { Message msg = mHandler.obtainMessage(); msg.what = what; msg.obj = content; mHandler.sendMessage(msg); } /* * "傳送"按鈕單擊事件 */ public void onSendButtonClicked(View v) { if(!mAdapter.isEnabled())//如果還沒有開啟藍芽服務 { showToast("藍芽服務不可用!!"); return ; } if(cs_flag == 0)//未連線藍芽裝置! { showToast("未連線藍芽裝置!"); return ; } //獲取要傳送的資訊 String smsg = edt_smsg.getText().toString(); if(smsg.equals(""))//如果訊息為空則不傳送 { return ; } edt_smsg.setText("");//清空資訊輸入框 //傳送訊息 if(cs_flag == 1)//本機作為伺服器 { accept_thread.write(smsg); } else if(cs_flag == 2)//本機作為客戶端 { connect_thread.write(smsg); } } /* * 接收活動結果,響應startActivityForResult() * @param requestCode:請求碼(自定義一個 整數,代表要請求什麼操作) * @param resultCode:結果碼(自定義一個 整數,代表請求的處理結果,表示成功與否。。。) * @param data:頁面返回的資料 */ public void onActivityResult(int requestCode, int resultCode, Intent data) { switch(requestCode) { case REQUEST_BT_ENABLE_CODE://請求開啟藍芽介面返回 (系統自帶的請求開啟藍芽介面) if (resultCode == RESULT_OK) { //使用者允許開啟藍芽(藍芽開啟需要一定的時間) showToast("藍芽已開啟"); //開啟服務執行緒 accept_thread = new AcceptThread(); accept_thread.start(); } else if (resultCode == RESULT_CANCELED) { //使用者沒有允許開啟藍芽,退出應用 showAlertDialog("退出應用", "抱歉!應用需要開啟藍芽!", "" ,"確認", 0) ; } break; case REQUEST_CONNECT_DEVICE://搜尋周邊藍芽裝置介面結果返回 // 響應返回結果 if (resultCode == Activity.RESULT_OK) //連線成功 { //1 獲取要連線裝置的名稱、MAC地址 //String dev_name = data.getExtras().getString(DeviceListActivity.EXTRA_DEVICE_NAME); String address = data.getExtras().getString(DeviceListActivity.EXTRA_DEVICE_ADDRESS); //2 得到要連線的藍芽裝置控制代碼 mRemoteDev = mAdapter.getRemoteDevice(address); if(mRemoteDev == null) { tv_rmsg.append("獲取藍芽裝置控制代碼失敗\n"); return; } //3 開啟連線執行緒 if(connect_thread != null)//取消之前的 { connect_thread.close();//結束執行緒 connect_thread = null; } connect_thread = new ConnectThread(mRemoteDev); connect_thread.start(); } break; default:break; } } /* * "連線"按鈕響應函式 */ public void onConnectButtonClicked(View v) { if(!mAdapter.isEnabled()) { //如果藍芽服務不可用則提示,可能是還沒有開啟 showToast("藍芽服務不可用!"); return; } if(mSocket == null)//如未連線裝置則開啟DeviceListActivity進行搜尋周邊藍芽裝置,並選擇連線 { Intent serverIntent = new Intent(this, DeviceListActivity.class); //跳轉程式設定 startActivityForResult(serverIntent, REQUEST_CONNECT_DEVICE); //設定返回巨集定義 } else //已經連線上就斷開連線 { //彈出確認框 showAlertDialog("斷開連線", "您確認斷開嗎?", "取消" ,"確認", 3); } } /* * 斷開連線 * */ public void disconnect() { try {//斷開連線 if(cs_flag == 1 && accept_thread != null)//本機作為服務端 { //傳送"__DISCONNECT__",告訴客戶端要斷開操作 accept_thread.write("__DISCONNECT__"); accept_thread.disconnect();//斷開當前連線 } else if(cs_flag == 2 && connect_thread != null )//本機作為客戶端 { //傳送"__DISCONNECT__",告訴服務端要斷開操作 connect_thread.write("__DISCONNECT__"); connect_thread.close();//關閉連線執行緒,即斷開與服務端的連線 accept_thread = null; } cs_flag = 0;//標識為未連線藍芽裝置 btn_cnnt.setText("連線");//將"斷開"按鈕的文字改為連線 tv_reDev.setText("未連線藍芽裝置"); }catch(Exception e){} } /* * 關閉socket,結束執行緒 */ public void reSource() { try { //關閉服務執行緒 if( accept_thread != null) { accept_thread.close();//關閉執行緒 accept_thread = null; } //關閉connect執行緒 if( connect_thread != null) { connect_thread.close();//關閉執行緒 connect_thread = null; } //關閉socket if(mServerSocket != null) { try { mServerSocket.close(); mServerSocket = null; } catch (IOException e) {} } if(mSocket != null) { try { mSocket.close(); mSocket = null; } catch (IOException e) {} } cs_flag = 0;//表示為未連線藍芽裝置 btn_cnnt.setText("連線"); tv_reDev.setText("未連線藍芽裝置"); } catch(Exception e){} } /* * 關閉程式呼叫處理部分 */ public void onDestroy(){ super.onDestroy(); reSource(); } /* * "傳送檔案"按鈕響應函式 */ public void onSendFileButtonClicked(View v) { showAlertDialog("傳送檔案", "此功能有待完善!", "取消" ,"確認", 1); } //"退出"按鈕響應函式 public void onQuitButtonClicked(View v){ //彈出一個對話方塊,確認是否退出 showAlertDialog("退出應用", "您確認退出嗎?", "取消" ,"確認", 0); } //彈出確認對話方塊 public void showAlertDialog(String title, String content, String negative, String positive, final int action) { //建立一個對話方塊 AlertDialog.Builder dialog = new AlertDialog.Builder(MainActivity.this); dialog.setTitle(title); //對話方塊標題 dialog.setMessage(content);//設定對話方塊內容提示 if(!negative.isEmpty() && negative != "" ) { //新增"取消按鈕",並且單擊時響應 dialog.setNegativeButton(negative,new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { if(action == 2) { //使用者沒有允許開啟藍芽,就退出應用,因為應用需要開啟藍芽才正常工作 showAlertDialog("退出應用", "抱歉!應用需要開啟藍芽!", "" ,"確認", 0) ; } } }); } //新增一個確定按鈕,並且單擊時響應 dialog.setPositiveButton(positive, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { switch(action) { case 0://退出應用操作 reSource();//釋放資源 finish();//關閉本頁面 break; case 1://"確認" break; case 2://開啟藍芽 mAdapter.enable();//開啟藍芽 while(!mAdapter.isEnabled());//等待藍芽開啟完畢,需要一定時間 setDiscoverableTimeout(300, 2);//設定藍芽可見性 // 開啟服務執行緒 accept_thread = new AcceptThread(); accept_thread.start(); break; case 3://斷開連線 disconnect(); break; default: break; } } }); dialog.show(); } //彈出Toast提示框 private void showToast(String text) { if( mToast == null) { mToast = Toast.makeText(this, text, Toast.LENGTH_SHORT); } else { mToast.setText(text); } mToast.show(); } /** * 開啟藍芽 * @param activity * @param requestCode * @param mode 開啟藍芽的方式:0詢問式開啟,1直接開啟 */ public void openBlueTooth(Activity activity, int requestCode, int mode) { if(mode == 0)//彈出系統自帶確認框詢問式開啟 { Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); activity.startActivityForResult(intent, requestCode); } else if(mode == 2)//彈出自定義的確認框 { //彈出一個對話方塊,確認是否開啟藍芽 showAlertDialog("開啟藍芽", "確認開啟藍芽?", "取消" ,"確認", 2) ; } else //靜默開啟(不彈出提示框) { mAdapter.enable();//這種方式是直接嘗試開啟藍芽,對使用者不友好 // 開啟服務執行緒 (可能需要等待藍芽開啟完畢,開啟藍芽需要時間) accept_thread = new AcceptThread(); accept_thread.start(); } //靜默設定藍芽可見性,時間為300秒 setDiscoverableTimeout(300, 2); } /* * 通過PrintWriter傳送訊息給其他藍芽裝置 * @param btOs 藍芽輸出流 * @param msg 要傳送的訊息文字 */ public void sendMsg(OutputStream btOs, String msg) { if (btOs != null) { try { if (writer == null) { writer = new PrintWriter(new OutputStreamWriter(btOs, "UTF-8"), true); } writer.println(msg);//傳送給其它藍芽裝置 if(msg == "__DISCONNECT__") { msg = "斷開連線"; } tv_rmsg.append("我:"+msg+"\n"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); sendHandlerMsg("writer錯誤:" + e.getMessage(), 100); } } } /* 設定藍芽可見性 * BluetoothAdapter 裡面的setDiscoverableTimeout和setScanMode起到了關鍵性左右, * BluetoothAdapter原始碼將這2個方法隱藏了。利用反射訪問 * @param timeout 可見時間(秒)最多300秒 * @param mode 1為詢問式設定,2為靜默設定 3關閉藍芽可見性 */ public void setDiscoverableTimeout(int timeout, int mode) { if(mode == 1)//彈出確認框詢問式設定 { Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE); discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, timeout); startActivity(discoverableIntent); } else //靜默式,不彈出確認框。 { try { //利用反射使用 setDiscoverableTimeout和setScanMode Method setDiscoverableTimeout = BluetoothAdapter.class.getMethod("setDiscoverableTimeout", int.class); setDiscoverableTimeout.setAccessible(true); Method setScanMode =BluetoothAdapter.class.getMethod("setScanMode", int.class,int.class); setScanMode.setAccessible(true); if(mode == 2)//靜默式 timeout不起作用,會一直保持可見性 { setDiscoverableTimeout.invoke(mAdapter, timeout); setScanMode.invoke(mAdapter, BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE,timeout); //mHandler.sendEmptyMessageDelayed(5, 300000);//300秒後傳送5類訊號讓Handler關閉藍芽可見性 } else if(mode == 3)//實現關閉藍芽可見性 { setDiscoverableTimeout.invoke(mAdapter, 1); setScanMode.invoke(mAdapter, BluetoothAdapter.SCAN_MODE_CONNECTABLE,1); } } catch (Exception e) { e.printStackTrace(); } } } //Accept服務端執行緒 class AcceptThread extends Thread { private InputStream btIs; //用來獲取其他藍芽裝置發來的訊息 private OutputStream btOs; //用來發送訊息給其他藍芽裝置 private boolean thread_run;//執行緒執行的標誌 private boolean cnnt_state;//連線狀態 public AcceptThread() { thread_run = true; cnnt_state = true; } @Override public void run() { String devInfo = null; try { //1 獲取套接字 mServerSocket = mAdapter.listenUsingRfcommWithServiceRecord("TEST", UUID.fromString(MY_UUID)); if (mServerSocket == null) { sendHandlerMsg("accept獲取mServerSocket失敗\n", 100); //這裡應該新增"獲取失敗"處理, 否則後續操作可能出現異常 return; } while(thread_run)//一般程式開始執行,服務就啟動,並一直監聽客戶端的連線 { //2 監聽連線請求 -- 連線一個裝置 (如果需要連線多個裝置,就需要一直mServerSocket.accept(),每accept一個客戶端就就交給一個新的socket去通訊-) if(mSocket != null)//關閉之前的 { try { mSocket.close(); mSocket = null; } catch (IOException e) {} } try { mSocket = mServerSocket.accept();//阻塞等待客戶端連線 } catch(Exception ex) { continue; } devInfo = mSocket.getRemoteDevice().getName()+"("+mSocket.getRemoteDevice().getAddress()+")"; //彈出提示框 sendHandlerMsg("連線 "+mSocket.getRemoteDevice().getName()+" 成功!", 1); //展示已經連線的裝置名稱 sendHandlerMsg("已連線:"+devInfo, 3); sendHandlerMsg("-----已連線:"+devInfo+" -----\n", 9); cs_flag = 1;//標識本機作為伺服器 sendHandlerMsg("斷開", 4);//將"連線"按鈕文字更改為"斷開" try { //3 獲取輸入輸出流 btIs = mSocket.getInputStream(); btOs = mSocket.getOutputStream(); //4 通訊-接收訊息 BufferedReader reader = new BufferedReader(new InputStreamReader(btIs, "UTF-8")); String content = null; cnnt_state = true; while (cnnt_state) { content = reader.readLine(); if(content.startsWith("__DISCONNECT__") )//客戶端發來斷開連線的請求 { sendHandlerMsg("收到訊息:" + "斷開連線" +"\n", 9);//將訊息顯示到TextView break; } else if(content != "" && !content.isEmpty()) { sendHandlerMsg("收到訊息:" + content +"\n", 9);//將訊息顯示到TextView content = ""; } } } catch(Exception e){} sendHandlerMsg("斷開連線", 1); sendHandlerMsg("----已斷開連線----" +"\n", 9);//將訊息顯示到TextView cs_flag = 0;//標識未連線藍芽裝置 sendHandlerMsg("未連線藍芽裝置", 3); sendHandlerMsg("連線", 4); } } catch (IOException e) { e.printStackTrace(); sendHandlerMsg("accept錯誤:" + e.getMessage(), 100); } finally { reSource();//釋放資源 finish();//退出應用 } } public void write(String msg) { sendMsg(btOs, msg); } //關閉執行緒 public void close() { cnnt_state = false; thread_run = false; try { mSocket.close(); mServerSocket.close(); } catch (IOException e) {} } //斷開連線 public void disconnect() { cnnt_state = false; try { mSocket.close(); } catch (IOException e) {} } } //Connect客戶端執行緒 class ConnectThread extends Thread { private BluetoothDevice mDevice;//要連線的藍芽裝置 private InputStream btIs; //用來獲取其他藍芽裝置發來的訊息 private OutputStream btOs; //用來發送訊息給其他藍芽裝置 private boolean thread_run; //用來控制迴圈(如果是true則執行緒一直在執行) public ConnectThread(BluetoothDevice device) { mDevice = device;//被點選選中的藍芽裝置 thread_run = true; } @Override public void run() { if (mDevice != null) { try { try { if(mSocket != null) { mSocket.close(); mSocket = null; } } catch(Exception e) {} //1 獲取套接字 mSocket = mDevice.createInsecureRfcommSocketToServiceRecord(UUID.fromString(MY_UUID)); if (mSocket == null) { sendHandlerMsg("cnnt獲取mSocket失敗\n", 100); return; } try { //2 發起連線請求 mSocket.connect(); //彈出提示框 sendHandlerMsg("連線 " + mDevice.getName() + " 成功!", 1); //在TextView上顯示已經連線上的藍芽裝置名稱 sendHandlerMsg("已連線:"+mDevice.getName()+"("+mDevice.getAddress()+")", 3); sendHandlerMsg("-----已連線:"+mDevice.getName()+"("+mDevice.getAddress()+") -----\n", 9); cs_flag = 2;//標識本機作為客戶端 sendHandlerMsg("斷開", 4);//將"連線"按鈕文字改為斷開 //3 獲取輸入輸出流 btIs = mSocket.getInputStream(); btOs = mSocket.getOutputStream(); //4 通訊-接收訊息 BufferedReader reader = new BufferedReader(new InputStreamReader(btIs, "UTF-8")); String content = null; thread_run = true; while (thread_run) { content = reader.readLine(); if(content.startsWith("__DISCONNECT__") )//收到伺服器端的斷開資訊 { sendHandlerMsg("收到訊息:" + "斷開連線" + "\n", 9); break; } else if(content !="" && !content.isEmpty()) { sendHandlerMsg("收到訊息:" + content + "\n", 9); content = ""; } } }catch(Exception e){} cs_flag = 0; sendHandlerMsg("斷開連線", 1); sendHandlerMsg("未連線藍芽裝置", 3); sendHandlerMsg("----已斷開連線----" +"\n", 9);//將訊息顯示到TextView sendHandlerMsg("連線", 4);//將"斷開"按鈕文字改為"連線" } catch (IOException e) { e.printStackTrace(); sendHandlerMsg("cnnt錯誤:" + e.getMessage(), 100); } finally { if(mSocket != null) { try { mSocket.close(); mSocket = null; } catch (IOException e) {} } } } } //傳送資訊 public void write(String msg) { sendMsg(btOs, msg); } //結束執行緒 public void close() { thread_run = false; try { mSocket.close(); } catch (IOException e) {} } } }
DeviceListActivity.java檔案
package com.liang.bluetooth;
import com.liang.bluetooth.R;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.view.View;
import android.view.Window;
import android.view.View.OnClickListener;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.AdapterView.OnItemClickListener;
//藍芽裝置列表
@SuppressLint("NewApi")
public class DeviceListActivity extends Activity {
// 返回時資料標籤
public static String EXTRA_DEVICE_NAME = "裝置名稱";
public static String EXTRA_DEVICE_ADDRESS = "裝置地址";
// 成員域
private BluetoothAdapter mBtAdapter;//藍芽介面卡
private ArrayAdapter<String> mPairedDevicesArrayAdapter;//已配對列表
private ArrayAdapter<String> mNewDevicesArrayAdapter;//新的裝置
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 建立並顯示視窗
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); //設定視窗顯示模式為視窗方式,有個滾動圈
setContentView(R.layout.device_list);//設定介面
// 設定預設返回值為取消
setResult(Activity.RESULT_CANCELED);
// 設定掃描按鍵響應
Button scanButton = (Button) findViewById(R.id.button_scan);
scanButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
doDiscovery();
v.setVisibility(View.GONE);//將"掃描"按鈕設定為"消失"
}
});
// 初始化裝置儲存陣列
mPairedDevicesArrayAdapter = new ArrayAdapter<String>(this, R.layout.device_name);
mNewDevicesArrayAdapter = new ArrayAdapter<String>(this, R.layout.device_name);
// 設定已配隊裝置列表
ListView pairedListView = (ListView) findViewById(R.id.paired_devices);
pairedListView.setAdapter(mPairedDevicesArrayAdapter);
pairedListView.setOnItemClickListener(mDeviceClickListener);
// 設定新查詢裝置列表
ListView newDevicesListView = (ListView) findViewById(R.id.new_devices);
newDevicesListView.setAdapter(mNewDevicesArrayAdapter);
newDevicesListView.setOnItemClickListener(mDeviceClickListener);
// 註冊接收查詢到裝置action接收器
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
this.registerReceiver(mReceiver, filter);
// 註冊查詢結束action接收器
filter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
this.registerReceiver(mReceiver, filter);
// 得到本地藍芽介面卡
mBtAdapter = BluetoothAdapter.getDefaultAdapter();
}
//銷燬
@Override
protected void onDestroy() {
super.onDestroy();
// 關閉掃描服務
if (mBtAdapter != null) {
mBtAdapter.cancelDiscovery();
}
// 登出action接收器
this.unregisterReceiver(mReceiver);
}
//"取消"按鈕響應函式
public void OnCancel(View v){
finish();//關閉本頁面
}
/**
* 開始服務和裝置查詢
*/
private void doDiscovery() {
// 顯示進度滾動圈
setProgressBarIndeterminateVisibility(true);
//設定視窗標題
setTitle("查詢裝置中...");
//彈出提示框嗎,提示使用者
Toast.makeText(DeviceListActivity.this, "查詢裝置中...", Toast.LENGTH_SHORT).show();
// 顯示其它裝置(未配對裝置)列表
findViewById(R.id.title_new_devices).setVisibility(View.VISIBLE);
// 如果正在查詢,關閉再進行的服務查詢
if (mBtAdapter.isDiscovering()) {
mBtAdapter.cancelDiscovery();
}
//並重新開始
mBtAdapter.startDiscovery();
}
// 選擇裝置響應函式
private OnItemClickListener mDeviceClickListener = new OnItemClickListener() {
public void onItemClick(AdapterView<?> av, View v, int arg2, long arg3) {
// 準備連線裝置,關閉服務查詢
mBtAdapter.cancelDiscovery();
// 得到裝置名稱與MAC地址
String info = ((TextView) v).getText().toString();
String dev_name = info.substring(0, info.length() - 18);//裝置名稱
String address = info.substring(info.length() - 17);//MAC地址
// 設定返回資料
Intent intent = new Intent();
intent.putExtra(EXTRA_DEVICE_NAME, dev_name);
intent.putExtra(EXTRA_DEVICE_ADDRESS, address);
// 設定返回值並結束程式
setResult(Activity.RESULT_OK, intent);
finish();
}
};
// 查詢到裝置和搜尋完成action監聽器
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent)
{
String action = intent.getAction();
// 查詢到裝置action
if (BluetoothDevice.ACTION_FOUND.equals(action))
{
// 得到藍芽裝置
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
// 如果是已配對的則略過,已得到顯示,其餘的在新增到列表中進行顯示
if (device.getBondState() != BluetoothDevice.BOND_BONDED)
{
mNewDevicesArrayAdapter.add(device.getName() + "\n" + device.getAddress());
}
else //新增到已配對裝置列表
{
mPairedDevicesArrayAdapter.add(device.getName() + "\n" + device.getAddress());
}
}
else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) // 搜尋完成action
{
setProgressBarIndeterminateVisibility(false);//滾動圈消失
setTitle("選擇要連線的裝置");
if (mNewDevicesArrayAdapter.getCount() == 0) {
String noDevices = "沒有找到新裝置";
mNewDevicesArrayAdapter.add(noDevices);
}
}
}
};
}
佈局檔案
main.xml檔案
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView
android:id="@+id/tv_dev"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/tv_dev" />
<TextView
android:id="@+id/tv_reDev"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/tv_reDev" />
<ScrollView
android:id="@+id/sv_list"
android:layout_width="fill_parent"
android:layout_height="0dp"
android:layout_weight="1.06"
android:scrollbars="vertical" >
<TextView android:id="@+id/in"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
</ScrollView>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<EditText
android:id="@+id/edt_smsg"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:layout_weight="1"
android:inputType="text" >
</EditText>
<Button
android:id="@+id/btn_send"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onSendButtonClicked"
android:text="@string/btn_send" >
</Button>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<Button
android:id="@+id/btn_cnnt"
android:layout_width="90dp"
android:layout_height="wrap_content"
android:onClick="onConnectButtonClicked"
android:text="@string/btn_cnnt" >
</Button>
<Button
android:id="@+id/btn_send_file"
android:layout_width="152dp"
android:layout_height="wrap_content"
android:onClick="onSendFileButtonClicked"
android:text="@string/btn_send_file" >
</Button>
<Button
android:id="@+id/Button06"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="onQuitButtonClicked"
android:text="@string/btn_exit" >
</Button>
</LinearLayout>
</LinearLayout>
device_list.xml檔案
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<ListView android:id="@+id/paired_devices"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:stackFromBottom="true"
android:layout_weight="1"
/>
<TextView android:id="@+id/title_new_devices"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/tv_disconnect"
android:visibility="gone"
android:background="#666"
android:textColor="#fff"
android:paddingLeft="5dp"
/>
<ListView android:id="@+id/new_devices"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:stackFromBottom="true"
android:layout_weight="2"
/>
<Button
android:id="@+id/button_scan"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/btn_search" />
<Button
android:id="@+id/button_cancel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="OnCancel"
android:text="@string/btn_cancel" />
</LinearLayout>
device_name.xml檔案
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="18sp"
android:padding="5dp"
/>
五、總結
1、程式大致流程圖
裝置雙方開啟藍芽,裝置A設定藍芽可見性,其它裝置可以搜尋到,開啟監聽執行緒,可以監聽並接受其它藍芽裝置的連線請求。裝置B搜尋附近藍芽,搜尋到裝置A的藍芽,發起連線請求,裝置A接收之後就可以互相收發訊息。
2、藍芽開發相關事項
藍芽開發重要物件:本地藍芽介面卡
BluetoothAdapter mAdapter = BluetoothAdapter.getDefaultAdapter();//獲取本地藍芽介面卡
操作 | 程式碼 | 備註 |
藍芽許可權 |
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> <uses-permission android:name="android.permission.BLUETOOTH" /> |
某些Andriod版本需要其他許可權 |
是否支援藍芽 |
BluetoothAdapter mAdapter = BluetoothAdapter.getDefaultAdapter();//獲取藍芽介面卡 |
如果mAdapter 為null就說明裝置不支援藍芽 |
藍芽是否已經開啟 | mAdapter.isEnabled(); | 返回true說明已經開啟 |
開啟藍芽 |
方式1:彈出系統自帶的請求對話方塊。 Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); mAdapter.enable();//這種方式是直接嘗試開啟藍芽,對使用者不友好 |
藍芽開啟需要一定的時間 |
關閉藍芽 | mAdapter.disable(); | |
搜尋藍芽 | mBtAdapter.startDiscovery(); | 搜尋周邊的藍芽裝置(搜尋會持續一段時間,手動結束搜尋mBtAdapter.cancelDiscovery();) 搜尋到一個藍芽裝置系統會發出廣播BluetoothDevice.ACTION_FOUND 可以自定義廣播接受者來接收廣播。 |
監聽連線 | BluetoothServerSocket mServerSocket = mAdapter.listenUsingRfcommWithServiceRecord("TEST", UUID.fromString(MY_UUID)); mSocket = mServerSocket.accept();//阻塞等待客戶端連線 |
(監聽執行緒、等待別的藍芽裝置來連結) MY_UUID = "00001101-0000-1000-8000-00805F9B34FB"; //SPP服務UUID號 |
發起連線 | BluetoothSocket mSocket = mDevice.createInsecureRfcommSocketToServiceRecord(UUID.fromString(MY_UUID)); //發起連線請求 |
用來請求連線某個藍芽裝置 mDevice:是要連線的藍芽裝置物件。 |
設定藍芽可見性 | 方式1:彈出確認框 (timeout:可見時間最大300秒) Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE); 方式2:不彈出提示框
setDiscoverableTimeout.invoke(mAdapter, timeout); |
一般預設不可見,設定為可見,其它藍芽裝置才能搜尋到。 |
收發訊息 | //3 獲取輸入輸出流 InputStream btIs = mSocket.getInputStream();//用來接收訊息 OutputStream btOs = mSocket.getOutputStream();//用來發送訊息 |
3、待完善
a) 例子只簡單演示了一對一通訊,其實可以一對多。
b) 藍芽檔案傳輸(檔案檢索,檔案編碼, 續傳)。
c) 例子中如果斷開連結又進行連結的情況處理不當,可能是對socket等資源的釋放存在問題。
d) 程式沒有使用"廣播"方式來處理藍芽的連結與斷開(推薦)。
e) 程式不夠"健壯性",不夠"人性化", 程式碼不夠精簡。