原文地址:html
Android 藍牙開發(一)藍牙通訊 CSDNjava
Android 藍牙開發(一)藍牙通訊 簡書android
開發環境:安全
jdk1.8 服務器
Eclipse Luna Service Release 1 (4.4.1)異步
運行環境:socket
華爲榮耀6(Android4.4)、華爲p9(Android7.0)ide
實現功能:函數
Android 藍牙開發 (開關藍牙、搜索設備、藍牙配對、鏈接、通訊、斷開鏈接等)。ui
代碼包裏面,有兩個部分,一個是源碼,一個是V7支持包。
隨着可穿戴設備的流行,研究藍牙是必不可少的一門技術了。
總結了下藍牙開發使用的一些東西分享一下。
藍牙權限
首先須要AndroidManifest.xml文件中添加操做藍牙的權限。
1
2
3
4
|
<
uses-permissionandroid:name
=
"Android.permission.BLUETOOTH"
/>
//容許程序鏈接到已配對的藍牙設備。
<
uses-permissionandroid:name
=
"android.permission.BLUETOOTH_ADMIN"
/>
//容許程序發現和配對藍牙設備。
|
BluetoothAdapter
操做藍牙主要用到的類 BluetoothAdapter類,使用時導包
import android.bluetooth.BluetoothAdapter;
源碼具體位置frameworks/base/core/Java/android/bluetooth/BluetoothAdapter.java
BluetoothAdapter 表明本地設備的藍牙適配器。該BluetoothAdapter能夠執行基本的藍牙任務,例如啓
動設備發現,查詢配對的設備列表,使用已知的MAC地址實例化一個BluetoothDevice類,並建立一個
BluetoothServerSocket監聽來自其餘設備的鏈接請求。
獲取藍牙適配器
1
|
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
|
開啓藍牙
1
2
3
4
5
6
7
|
if
(!mBluetoothAdapter.isEnabled()){
//彈出對話框提示用戶是後打開
Intent enabler =
new
Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enabler, REQUEST_ENABLE);
//不作提示,直接打開,不建議用下面的方法,有的手機會有問題。
// mBluetoothAdapter.enable();
}
|
獲取本地藍牙信息
1
2
3
4
5
6
7
8
9
10
11
|
//獲取本機藍牙名稱
String name = mBluetoothAdapter.getName();
//獲取本機藍牙地址
String address = mBluetoothAdapter.getAddress();
Log.d(TAG,
"bluetooth name ="
+name+
" address ="
+address);
//獲取已配對藍牙設備
Set<BluetoothDevice> devices = mBluetoothAdapter.getBondedDevices();
Log.d(TAG,
"bonded device size ="
+devices.size());
for
(BluetoothDevice bonddevice:devices){
Log.d(TAG,
"bonded device name ="
+bonddevice.getName()+
" address"
+bonddevice.getAddress());
}
|
搜索設備
1
|
mBluetoothAdapter.startDiscovery();
|
中止搜索
1
|
mBluetoothAdapter.cancelDiscovery();
|
搜索藍牙設備,該過程是異步的,經過下面註冊廣播接受者,能夠監聽是否搜到設備。
1
2
3
4
5
6
7
8
|
IntentFilter filter =
new
IntentFilter();
//發現設備
filter.addAction(BluetoothDevice.ACTION_FOUND);
//設備鏈接狀態改變
filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
//藍牙設備狀態改變
filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
registerReceiver(mBluetoothReceiver, filter);
|
監聽掃描結果
經過廣播接收者查看掃描到的藍牙設備,每掃描到一個設備,系統都會發送此廣播(BluetoothDevice.ACTION_FOUNDE)。其中參數intent能夠獲取藍牙設備BluetoothDevice。
該demo中是鏈接指定名稱的藍牙設備,BLUETOOTH_NAME爲"Galaxy Nexus",若是掃描不到,記得改這個藍牙名稱。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
private
BroadcastReceiver mBluetoothReceiver =
new
BroadcastReceiver(){
@Override
public
void
onReceive(Context context, Intent intent) {
String action = intent.getAction();
Log.d(TAG,
"mBluetoothReceiver action ="
+action);
if
(BluetoothDevice.ACTION_FOUND.equals(action)){
//每掃描到一個設備,系統都會發送此廣播。
//獲取藍牙設備
BluetoothDevice scanDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if
(scanDevice ==
null
|| scanDevice.getName() ==
null
)
return
;
Log.d(TAG,
"name="
+scanDevice.getName()+
"address="
+scanDevice.getAddress());
//藍牙設備名稱
String name = scanDevice.getName();
if
(name !=
null
&& name.equals(BLUETOOTH_NAME)){
mBluetoothAdapter.cancelDiscovery();
//取消掃描
mProgressDialog.setTitle(getResources().getString(R.string.progress_connecting));
//鏈接到設備。
mBlthChatUtil.connect(scanDevice);
}
}
else
if
(BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)){
}
}
};
|
設置藍牙可見性
有時候掃描不到某設備,這是由於該設備對外不可見或者距離遠,須要設備該藍牙可見,這樣該才能被搜索到。
可見時間默認值爲120s,最多可設置300。
1
2
3
4
5
6
7
8
9
10
|
if
(mBluetoothAdapter.isEnabled()) {
if
(mBluetoothAdapter.getScanMode() !=
BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
Intent discoverableIntent =
new
Intent(
BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
discoverableIntent.putExtra(
BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION,
120
);
startActivity(discoverableIntent);
}
}
|
android 藍牙之間能夠經過SDP協議創建鏈接進行通訊,通訊方式相似於日常使用socket。
首先建立BluetoothServerSocket ,BluetoothAdapter中提供了兩種建立BluetoothServerSocket 方式,以下圖所示爲建立安全的RFCOMM Bluetooth socket,該鏈接是安全的須要進行配對。而經過listenUsingInsecureRfcommWithServiceRecord建立的RFCOMM Bluetooth socket是不安全的,鏈接時不須要進行配對。
其中的uuid須要服務器端和客戶端進行統一。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
|
private
class
AcceptThread
extends
Thread {
// 本地服務器套接字
private
final
BluetoothServerSocket mServerSocket;
public
AcceptThread() {
BluetoothServerSocket tmp =
null
;
// 建立一個新的偵聽服務器套接字
try
{
tmp = mAdapter.listenUsingRfcommWithServiceRecord(
SERVICE_NAME, SERVICE_UUID);
//tmp = mAdapter.listenUsingInsecureRfcommWithServiceRecord(SERVICE_NAME, SERVICE_UUID);
}
catch
(IOException e) {
Log.e(TAG,
"listen() failed"
, e);
}
mServerSocket = tmp;
}
public
void
run() {
BluetoothSocket socket =
null
;
// 循環,直到鏈接成功
while
(mState != STATE_CONNECTED) {
try
{
// 這是一個阻塞調用 返回成功的鏈接
// mServerSocket.close()在另外一個線程中調用,能夠停止該阻塞
socket = mServerSocket.accept();
}
catch
(IOException e) {
Log.e(TAG,
"accept() failed"
, e);
break
;
}
// 若是鏈接被接受
if
(socket !=
null
) {
synchronized
(BluetoothChatUtil.
this
) {
switch
(mState) {
case
STATE_LISTEN:
case
STATE_CONNECTING:
// 正常狀況。啓動ConnectedThread。
connected(socket, socket.getRemoteDevice());
break
;
case
STATE_NONE:
case
STATE_CONNECTED:
// 沒有準備或已鏈接。新鏈接終止。
try
{
socket.close();
}
catch
(IOException e) {
Log.e(TAG,
"Could not close unwanted socket"
, e);
}
break
;
}
}
}
}
if
(D) Log.i(TAG,
"END mAcceptThread"
);
}
public
void
cancel() {
if
(D) Log.d(TAG,
"cancel "
+
this
);
try
{
mServerSocket.close();
}
catch
(IOException e) {
Log.e(TAG,
"close() of server failed"
, e);
}
}
}
|
mServerSocket經過accept()等待客戶端的鏈接(阻塞),直到鏈接成功或失敗。
客戶端主要用來建立RFCOMM socket,並鏈接服務端。
先掃描周圍的藍牙設備,若是掃描到指定設備則進行鏈接。mBlthChatUtil.connect(scanDevice)鏈接到設備,
鏈接過程主要在ConnectThread線程中進行,先建立socket,方式有兩種,
以下代碼中是安全的(createRfcommSocketToServiceRecord)。另外一種不安全鏈接對應的函數是createInsecureRfcommSocketToServiceRecord。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
private
class
ConnectThread
extends
Thread {
private
BluetoothSocket mmSocket;
private
final
BluetoothDevice mmDevice;
public
ConnectThread(BluetoothDevice device) {
mmDevice = device;
BluetoothSocket tmp =
null
;
// 獲得一個bluetoothsocket
try
{
mmSocket = device.createRfcommSocketToServiceRecord
(SERVICE_UUID);
}
catch
(IOException e) {
Log.e(TAG,
"create() failed"
, e);
mmSocket =
null
;
}
}
public
void
run() {
Log.i(TAG,
"BEGIN mConnectThread"
);
try
{
// socket 鏈接,該調用會阻塞,直到鏈接成功或失敗
mmSocket.connect();
}
catch
(IOException e) {
connectionFailed();
try
{
//關閉這個socket
mmSocket.close();
}
catch
(IOException e2) {
e2.printStackTrace();
}
return
;
}
// 啓動鏈接線程
connected(mmSocket, mmDevice);
}
public
void
cancel() {
try
{
mmSocket.close();
}
catch
(IOException e) {
Log.e(TAG,
"close() of connect socket failed"
, e);
}
}
}
|
接着客戶端socket主動鏈接服務端。鏈接過程當中會自動進行配對,須要雙方贊成才能夠鏈接成功。
客戶端與服務端鏈接成功後都會調用connected(mmSocket, mmDevice),建立一個ConnectedThread線程()。
該線程主要用來接收和發送數據。客戶端和服務端處理方式同樣。該線程經過socket得到輸入輸出流。
private InputStream mmInStream = socket.getInputStream();
private OutputStream mmOutStream =socket.getOutputStream();
發送數據
1
2
3
4
5
6
7
8
9
10
|
public
void
write(
byte
[] buffer) {
try
{
mmOutStream.write(buffer);
// 分享發送的信息到Activity
mHandler.obtainMessage(MESSAGE_WRITE, -
1
, -
1
, buffer)
.sendToTarget();
}
catch
(IOException e) {
Log.e(TAG,
"Exception during write"
, e);
}
}
|
接收數據
線程循環進行接收數據。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
public
void
run() {
// 監聽輸入流
while
(
true
) {
try
{
byte
[] buffer =
new
byte
[
1024
];
// 讀取輸入流
int
bytes = mmInStream.read(buffer);
// 發送得到的字節的ui activity
Message msg = mHandler.obtainMessage(MESSAGE_READ);
Bundle bundle =
new
Bundle();
bundle.putByteArray(READ_MSG, buffer);
msg.setData(bundle);
mHandler.sendMessage(msg);
}
catch
(IOException e) {
Log.e(TAG,
"disconnected"
, e);
connectionLost();
break
;
}
}
}
|
一、運行,右鍵項目:Run as -》Android Application (備註:Eclipse須要配置Android開發環境)
二、運行效果以下:
客戶端
服務端
出處:http://www.demodashi.com/demo/10676.html