1 藍牙基本操作
隨著可穿戴設(shè)備的流行,研究藍牙是必不可少的一門技術(shù)了。
總結(jié)了下藍牙開發(fā)使用的一些東西分享一下。
藍牙權(quán)限
首先需要AndroidManifest.xml文件中添加操作藍牙的權(quán)限。
<uses-permissionandroid:name="android.permission.BLUETOOTH" />
//允許程序連接到已配對的藍牙設(shè)備。
<uses-permissionandroid:name="android.permission.BLUETOOTH_ADMIN" />
//允許程序發(fā)現(xiàn)和配對藍牙設(shè)備。
BluetoothAdapter
操作藍牙主要用到的類 BluetoothAdapter類,使用時導(dǎo)包import android.bluetooth.BluetoothAdapter;
源碼具體位置:frameworks/base/core/Java/android/bluetooth/BluetoothAdapter.javaBluetoothAdapter
代表本地設(shè)備的藍牙適配器。該BluetoothAdapter可以執(zhí)行基本的藍牙任務(wù),例如啟動設(shè)備發(fā)現(xiàn),查詢配對的設(shè)備列表,使用已知的MAC地址實例化一個BluetoothDevice類,并創(chuàng)建一個BluetoothServerSocket監(jiān)聽來自其他設(shè)備的連接請求。
獲取藍牙適配器
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
開啟藍牙
if(!mBluetoothAdapter.isEnabled()){
//彈出對話框提示用戶是后打開
Intent enabler = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enabler, REQUEST_ENABLE);
//不做提示,直接打開,不建議用下面的方法,有的手機會有問題。
// mBluetoothAdapter.enable();
}
獲取本地藍牙信息
//獲取本機藍牙名稱
String name = mBluetoothAdapter.getName();
//獲取本機藍牙地址
String address = mBluetoothAdapter.getAddress();
Log.d(TAG,"bluetooth name ="+name+" address ="+address);
//獲取已配對藍牙設(shè)備
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());
}
搜索設(shè)備
mBluetoothAdapter.startDiscovery();
停止搜索
mBluetoothAdapter.cancelDiscovery();
監(jiān)聽掃描結(jié)果
搜索藍牙設(shè)備,該過程是異步的,通過下面注冊廣播接受者,可以監(jiān)聽是否搜到設(shè)備。
IntentFilter filter = new IntentFilter();
//發(fā)現(xiàn)設(shè)備
filter.addAction(BluetoothDevice.ACTION_FOUND);
//設(shè)備連接狀態(tài)改變
filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
//藍牙設(shè)備狀態(tài)改變
filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
registerReceiver(mBluetoothReceiver, filter);
通過廣播接收者查看掃描到的藍牙設(shè)備,每掃描到一個設(shè)備,系統(tǒng)都會發(fā)送此廣播(BluetoothDevice.ACTION_FOUNDE)。其中參數(shù)intent可以獲取藍牙設(shè)備BluetoothDevice。
該demo中是連接指定名稱的藍牙設(shè)備,BLUETOOTH_NAME為"Galaxy Nexus",如果掃描不到,記得改這個藍牙名稱。
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)){//每掃描到一個設(shè)備,系統(tǒng)都會發(fā)送此廣播。
//獲取藍牙設(shè)備
BluetoothDevice scanDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if(scanDevice == null || scanDevice.getName() == null) return;
Log.d(TAG, "name="+scanDevice.getName()+"address="+scanDevice.getAddress());
//藍牙設(shè)備名稱
String name = scanDevice.getName();
if(name != null && name.equals(BLUETOOTH_NAME)){
mBluetoothAdapter.cancelDiscovery();
//取消掃描
mProgressDialog.setTitle(getResources().getString(R.string.progress_connecting)); //連接到設(shè)備。
mBlthChatUtil.connect(scanDevice);
}
}else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)){
}
}
};
設(shè)置藍牙可見性
有時候掃描不到某設(shè)備,這是因為該設(shè)備對外不可見或者距離遠,需要設(shè)備該藍牙可見,這樣該才能被搜索到。
可見時間默認(rèn)值為120s,最多可設(shè)置300。
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);
}
}
2 服務(wù)端
android 藍牙之間可以通過SDP協(xié)議建立連接進行通信,通信方式類似于平常使用socket。
首先創(chuàng)建BluetoothServerSocket ,BluetoothAdapter中提供了兩種創(chuàng)建BluetoothServerSocket 方式,如下圖所示為創(chuàng)建安全的RFCOMM Bluetooth socket,該連接是安全的需要進行配對。而通過listenUsingInsecureRfcommWithServiceRecord創(chuàng)建的RFCOMM Bluetooth socket是不安全的,連接時不需要進行配對。
其中的uuid需要服務(wù)器端和客戶端進行統(tǒng)一。
private class AcceptThread extends Thread {
// 本地服務(wù)器套接字
private final BluetoothServerSocket mServerSocket;
public AcceptThread() {
BluetoothServerSocket tmp = null;
// 創(chuàng)建一個新的偵聽服務(wù)器套接字
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;
// 循環(huán),直到連接成功
while (mState != STATE_CONNECTED) {
try {
// 這是一個阻塞調(diào)用 返回成功的連接
// mServerSocket.close()在另一個線程中調(diào)用,可以中止該阻塞
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:
// 沒有準(zhǔn)備或已連接。新連接終止。
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()等待客戶端的連接(阻塞),直到連接成功或失敗。
3 客戶端
客戶端主要用來創(chuàng)建RFCOMM socket,并連接服務(wù)端。
先掃描周圍的藍牙設(shè)備,如果掃描到指定設(shè)備則進行連接。mBlthChatUtil.connect(scanDevice)連接到設(shè)備,
連接過程主要在ConnectThread線程中進行,先創(chuàng)建socket,方式有兩種,
如下代碼中是安全的(createRfcommSocketToServiceRecord)。另一種不安全連接對應(yīng)的函數(shù)是createInsecureRfcommSocketToServiceRecord。
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 連接,該調(diào)用會阻塞,直到連接成功或失敗
mmSocket.connect();
} catch (IOException e) {
connectionFailed();
try {//關(guān)閉這個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主動連接服務(wù)端。連接過程中會自動進行配對,需要雙方同意才可以連接成功。
4 數(shù)據(jù)傳輸
客戶端與服務(wù)端連接成功后都會調(diào)用connected(mmSocket, mmDevice),創(chuàng)建一個ConnectedThread線程()。
該線程主要用來接收和發(fā)送數(shù)據(jù)。客戶端和服務(wù)端處理方式一樣。該線程通過socket獲得輸入輸出流。
private InputStream mmInStream = socket.getInputStream();
private OutputStream mmOutStream =socket.getOutputStream();
發(fā)送數(shù)據(jù)
public void write(byte[] buffer) {
try {
mmOutStream.write(buffer);
// 分享發(fā)送的信息到Activity
mHandler.obtainMessage(MESSAGE_WRITE, -1, -1, buffer)
.sendToTarget();
} catch (IOException e) {
Log.e(TAG, "Exception during write", e);
}
}
接收數(shù)據(jù)
線程循環(huán)進行接收數(shù)據(jù)。
public void run() {
// 監(jiān)聽輸入流
while (true) {
try {
byte[] buffer = new byte[1024];
// 讀取輸入流
int bytes = mmInStream.read(buffer);
// 發(fā)送獲得的字節(jié)的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;
}
}
}
DEMO下載:http://www.demodashi.com/demo/10676.html
歡迎大家關(guān)注、評論、點贊。
你們的支持是我堅持的動力。
歡迎關(guān)注我的微信公眾號