Android藍芽搜尋連線通訊
藍芽( Bluetooth® ):是一種無線技術標準,可實現固定裝置、移動裝置和樓宇個人域網之間的短距離資料交換(使用2.4—2.485GHz的ISM波段的UHF無線電波)。藍芽技術最初由電信巨頭愛立信公司於1994年創制,當時是作為RS232資料線的替代方案。
藍芽的基本功能:
- 掃描其他藍芽裝置
- 為可配對藍芽裝置查詢藍芽介面卡。
- 建立RFCOMM通道
- 通過服務搜尋來連線其他裝置。
- 與其他裝置進行資料傳輸。
- 管理多個連線
使用藍芽進行通訊的必要四步:
- 開啟藍芽;
- 查詢附近已配對或可用的裝置;
- 連線裝置;
- 裝置間資料交換。
所有藍芽API都在android.bluetooth
包下.下面有一些類和介面的摘要,可能需要它們來建立藍芽連線:
代表本地藍芽介面卡(藍芽無線電)。BluetoothAdapter是所有藍芽互動的入口。使用這個你可以發現其他藍芽裝置,查詢已配對的裝置列表,使用一個已知的MAC地址來例項化一個BluetoothDevice,以及建立一個BluetoothServerSocket來為監聽與其他裝置的通訊。
代表一個遠端藍芽裝置,使用這個來請求一個與遠端裝置的BluetoothSocket連線,或者查詢關於裝置名稱、地址、類和連線狀態等裝置資訊。
代表一個藍芽socket的介面(和TCP Socket類似)。這是一個連線點,它允許一個應用與其他藍芽裝置通過InputStream和OutputStream交換資料。
代表一個開放的伺服器socket,它監聽接受的請求(與TCP ServerSocket類似)。為了連線兩臺Android裝置,一個裝置必須使用這個類開啟一個伺服器socket。當一個遠端藍芽裝置開始一個和該裝置的連線請求,BluetoothServerSocket將會返回一個已連線的BluetoothSocket,接受該連線。
描述一個藍芽裝置的基本特性和效能。這是一個只讀的屬性集合,它定義了裝置的主要和次要的裝置類以及它的服務。但是,它沒有描述所有的藍芽配置和裝置支援的服務,它只是暗示了裝置的型別。
一個表示藍芽配置檔案的介面。一個Bluetooth profile是一個基於藍芽的通訊無線介面定義。一個例子是Hands-Free profile。更多的討論請見Working with Profiles。
提供對移動手機使用的藍芽耳機的支援。它包含了Headset and Hands-Free (v1.5)配置檔案。
定義高品質的音訊如何通過藍芽連線從一個裝置傳輸到另一個裝置。”A2DP“是Advanced Audio Distribution Profile的縮寫。
表示一個Health Device Profile代理,它控制藍芽服務。
一個抽象類,你可以使用它來實現BluetoothHealth的回撥函式。你必須擴充套件這個類並實現回撥函式方法來接收應用程式的註冊狀態改變以及藍芽串列埠狀態的更新。
BluetoothHealthAppConfiguration
表示一個應用程式配置,Bluetooth Health第三方應用程式註冊和一個遠端Bluetooth Health裝置通訊。
BluetoothProfile.ServiceListener
一個介面,當BluetoothProfile IPC客戶端從伺服器上建立連線或斷開連線時,它負責通知它們(也就是,執行在特性配置的內部服務)
使用藍芽需要在配置檔案Androidmanifest.xml 中註冊兩種許可權:
//獲取藍芽介面卡
<uses-permission android:name="android.permission.BLUETOOTH" />
//藍芽開啟或關閉許可權
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
一、藍芽搜尋
1.獲取藍芽介面卡:
private BluetoothAdapter mBluetoothAdapter;
mBluetoothAdapter=BluetoothAdapter.getDefaultAdapter();
Attention:一定要在真機上除錯,AndroidStudio上建立的AVD沒有藍芽模組,可以先進行一下判斷:
if(mBluetoothAdapter == null){ Toast.makeText(this, "Bluetooth is not supported on the device", Toast.LENGTH_LONG).show(); return; }
2.開啟藍芽:
//判斷藍芽是否開啟,如未開啟則強制開啟 if (!mBluetoothAdapter.isEnabled()) { //註釋掉的方法為提示視窗開啟,部分系統(如EMUI)開啟藍芽自帶提示窗,故而用這種方法可能會有兩個提示窗 /*Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivity(intent); startActivityForResult(intent, REQUEST_CODE_BLUETOOTH_ON);*/ mBluetoothAdapter.enable(); }
藍芽開啟有三種方式:
- enable()直接強制開啟
- 上面註釋掉的方法為提示窗開啟,本人親測華為榮耀8這種方式會出現兩次提示窗,故而棄用
- 開啟藍芽並設定可見時間,具體程式碼就不貼了,到處都是。
3.藍芽廣播
首先使用藍芽廣播要註冊藍芽廣播接收者,我們用的是動態註冊的方式,即在程式中使用Context.registerReceiver註冊。還有一種靜態註冊方式是在AndroidManifest.xml檔案中定義。
IntentFilter filter=new IntentFilter(); //發現裝置 filter.addAction(BluetoothDevice.ACTION_FOUND); //連線斷開 filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED); //完成掃描 filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED); //裝置連線狀態改變 filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED); registerReceiver(mReceiver,filter);
註冊完之後就是定義藍芽廣播接收者啦:
// Create a BroadcastReceiver for ACTION_FOUND private final BroadcastReceiver mReceiver=new BroadcastReceiver(){ private List<BluetoothDevice> bondedlist = new ArrayList <>(); private List<BluetoothDevice> surroundList = new ArrayList <>(); @Override public void onReceive(Context context, Intent intent) { switch (intent.getAction()){ case BluetoothDevice.ACTION_FOUND: BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); if(device.getBondState() != BluetoothDevice.BOND_BONDED){ if (!surroundList.contains(device)) { surroundList.add(device); } System.out.println("裝置列表:"+surroundList); // 為listview設定字元換陣列介面卡,搜尋附近未配對裝置 SimpleAdapter simAdapter = new SimpleAdapter( MainActivity.this, getDeviceList(surroundList), android.R.layout.simple_list_item_2, new String[] { "name" , "address"}, new int[] { android.R.id.text1, android.R.id.text2}); surroundDevices.setAdapter(simAdapter); setListViewHeightBasedOnChildren(surroundDevices); } //已配對裝置資訊 bondedlist = new ArrayList <>(mBluetoothAdapter.getBondedDevices()); System.out.println("BondedList:"+bondedlist); // 為listview設定字元換陣列介面卡 SimpleAdapter simAdapter = new SimpleAdapter( MainActivity.this, getDeviceList(bondedlist), android.R.layout.simple_list_item_2, new String[] { "name" , "address"}, new int[] { android.R.id.text1, android.R.id.text2}); // 為listView繫結介面卡 bondedDevices.setAdapter(simAdapter); setListViewHeightBasedOnChildren(bondedDevices); break; case BluetoothAdapter.ACTION_DISCOVERY_FINISHED: statusText.setText("DISCOVERY_FINISHED"); break; case BluetoothDevice.ACTION_BOND_STATE_CHANGED: BluetoothDevice device1 = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); Log.e("STATE:","address:"+device1.getAddress()+"\nname:"+device1.getName()); switch (device1.getBondState()){ case BluetoothDevice.BOND_BONDING: Log.i("STATE", "正在與" + device1.getName() + "__" + device1.getAddress() + "配對"); statusText.setText("正在與" + device1.getName() +"進行配對"); break; case BluetoothDevice.BOND_BONDED: Log.i("STATE", "與" + device1.getName() + "__" + device1.getAddress() + "完成配對"); statusText.setText("與" + device1.getName() + "配對完成"); break; case BluetoothDevice.BOND_NONE: Log.i("STATE", "取消與" + device1.getName() + "__" + device1.getAddress() + "配對"); statusText.setText("與" + device1.getName() + "配對取消"); default: break; } break; case BluetoothDevice.ACTION_ACL_DISCONNECTED: statusText.setText("Disconnected"); break; } } };
4.開始搜尋,觸發廣播
通過 BluetoothAdapter中startDiscovery( )方法來開始廣播。當廣播的事件是我們剛剛註冊的事件時就會觸發廣播接收器,並且觸發廣播接收器中的onReceiver()方法。
二、藍芽通訊
藍芽配對我偷了個懶,直接在系統設定藍芽裡面配對的,因為做這個的時候主要實現的就是通訊啦~
1.藍芽連線和通訊,我把它們寫到了一起~
/**
* 傳送資訊到另一個藍芽裝置
*
* @param message 資訊
*/
private void sendMessage(String message) {
if (address==null||"".equals(address)){
Toast.makeText(MainActivity.this,"未連線任何裝置",Toast.LENGTH_SHORT).show();
return;
}
try {
mBluetoothAdapter.cancelDiscovery();
Log.e(TAG, "sendMessage: 1");
if (null == bluetoothDevice) {
bluetoothDevice = mBluetoothAdapter.getRemoteDevice(address);
}
Log.e(TAG, "sendMessage: 2");
if (bluetoothSocket == null) {
bluetoothSocket = bluetoothDevice.createRfcommSocketToServiceRecord(MY_UUID);
bluetoothSocket.connect();
outputStream = bluetoothSocket.getOutputStream();
Message msg1 = new Message();
msg1.obj = new String(bluetoothSocket.getRemoteDevice().getName().getBytes("utf-8"), "utf-8");
msg1.what = 3;
mHandler.sendMessage(msg1);
}
Log.e(TAG, "sendMessage: 3");
if (!bluetoothSocket.isConnected()) {
resetSocket();
}
Log.e(TAG, "sendMessage: 4");
if (outputStream != null) {
try {
outputStream.write((message.getBytes("utf-8")));
Log.e(TAG, "onItemClick: " + mBluetoothAdapter.getName() + ":" + message);
Message msg = new Message();
msg.obj = new String(message.getBytes("utf-8"), "utf-8");
msg.what = 0;
mHandler.sendMessage(msg);
} catch (Exception e) {
resetSocket();
sendMessage(message);
}
}
} catch (Exception e) {
Toast.makeText(MainActivity.this,"建立連線失敗",Toast.LENGTH_SHORT).show();
e.printStackTrace();
}
}
private void resetSocket() {
try {
bluetoothSocket.close();
bluetoothSocket = bluetoothDevice.createRfcommSocketToServiceRecord(MY_UUID);
bluetoothSocket.connect();
outputStream = bluetoothSocket.getOutputStream();
Message msg1 = new Message();
msg1.obj = new String(bluetoothSocket.getRemoteDevice().getName().getBytes("utf-8"), "utf-8");
msg1.what = 3;
mHandler.sendMessage(msg1);
} catch (IOException e) {
e.printStackTrace();
}
}
2.服務端接收執行緒:
private class AcceptThread extends Thread {
private BluetoothServerSocket mBluetoothServerSocket;
private BluetoothSocket bluetoothSocket;
private InputStream is;
private OutputStream os;
private boolean isContinue;
AcceptThread() {
try {
mBluetoothServerSocket = mBluetoothAdapter.listenUsingRfcommWithServiceRecord("bluetooth_socket", MY_UUID);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
while (true) {
try {
if (mBluetoothServerSocket == null) {
return;
}
bluetoothSocket = mBluetoothServerSocket.accept();
serverBleName = bluetoothSocket.getRemoteDevice().getName();
Log.e(TAG, "run: accept");
is = bluetoothSocket.getInputStream();
os = bluetoothSocket.getOutputStream();
Message msg = new Message();
msg.obj = new String(serverBleName.getBytes("utf-8"), "utf-8");
msg.what = 4;
mHandler.sendMessage(msg);
isContinue = true;
while (isContinue) {
byte[] buffer = new byte[128];
int count = is.read(buffer);
Message message = new Message();
message.obj = new String(buffer, 0, count, "utf-8");
message.what = 1;
mHandler.sendMessage(message);
}
} catch (IOException e) {
e.printStackTrace();
isContinue = false;
} finally {
try {
if (bluetoothSocket != null) {
bluetoothSocket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
3.更新介面UI執行緒
private Handler mHandler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
//Toast.makeText(MainActivity.this, String.valueOf(msg.obj), Toast.LENGTH_SHORT).show();
switch (msg.what){
//send message
case 0:
receiveText.append("Me : "+String.valueOf(msg.obj)+"\n");
break;
//receive message
case 1:
receiveText.append(serverBleName + " : "+String.valueOf(msg.obj)+"\n");
break;
//connect server
case 3:
statusText.setText("Connnected to "+String.valueOf(msg.obj));
break;
//connectclient
case 4:
statusText.setText("Connnected to "+String.valueOf(msg.obj));
break;
}
return false;
}
});
三、結果展示
下面是各個階段的截圖