最近公司開發需要用到藍牙,花了大約一天的時間總結整理了一下。主要是爲了以後自己方便看。有需要的朋友可以看下。歡迎一起討論。後面會帶上demo。裏面是實現了藍牙搜索,配對,連接,數據互傳。
首先需要AndroidManifest.xml文件中添加操作藍牙的權限。
<uses-permission android:name="android.permission.BLUETOOTH" />允許程序連接到已配對的藍牙設備。 <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />允許程序發現和配對藍牙設備。
Android 6.0後需要加上 <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
一、我們需要用到了幾個相關的類:
1.BluetoothAdapter 顧名思義,藍牙適配器,直到我們建立bluetoothSocket連接之前,都要不斷操作它
BluetoothAdapter裏的方法很多,常用的有以下幾個:
cancelDiscovery() 根據字面意思,是取消發現,也就是說當我們正在搜索設備的時候調用這個方法將不再繼續搜索
disable()關閉藍牙
enable()打開藍牙,這個方法打開藍牙不會彈出提示,更多的時候我們需要問下用戶是否打開,以下這兩行代碼同樣是打開藍牙,不過會提示用戶:
Intemtenabler=new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enabler,reCode);//同startActivity(enabler);
getAddress()獲取本地藍牙地址
getDefaultAdapter()獲取默認BluetoothAdapter,實際上,也只有這一種方法獲取BluetoothAdapter
getName()獲取本地藍牙名稱
getRemoteDevice(String address)根據藍牙地址獲取遠程藍牙設備
getState()獲取本地藍牙適配器當前狀態(感覺可能調試的時候更需要)
isDiscovering()判斷當前是否正在查找設備,是返回true
isEnabled()判斷藍牙是否打開,已打開返回true,否則,返回false
listenUsingRfcommWithServiceRecord(String name,UUID uuid)根據名稱,UUID創建並返回BluetoothServerSocket,這是創建BluetoothSocket服務器端的第一步
startDiscovery()開始搜索,這是搜索的第一步
2.BluetoothDevice看名字就知道,這個類描述了一個藍牙設備
createRfcommSocketToServiceRecord(UUIDuuid)根據UUID創建並返回一個BluetoothSocket
這個方法也是我們獲取BluetoothDevice的目的——創建BluetoothSocket
這個類其他的方法,如getAddress(),getName(),同BluetoothAdapter
3.BluetoothServerSocket如果去除了Bluetooth相信大家一定再熟悉不過了,既然是Socket,方法就應該都差不多,這個類一種只有三個方法
兩個重載的accept(),accept(inttimeout)兩者的區別在於後面的方法指定了過時時間,需要注意的是,執行這兩個方法的時候,直到接收到了客戶端的請求(或是過期之後),都會阻塞線程,應該放在新線程裏運行!
還有一點需要注意的是,這兩個方法都返回一個BluetoothSocket,最後的連接也是服務器端與客戶端的兩個BluetoothSocket的連接
close()這個就不用說了吧,關閉資源!
4.BluetoothSocket,跟BluetoothServerSocket相對,是客戶端
一共5個方法,不出意外,都會用
close(),關閉
connect()連接
getInptuStream()獲取輸入流
getOutputStream()獲取輸出流
getRemoteDevice()獲取遠程設備,這裏指的是獲取bluetoothSocket指定連接的那個遠程藍牙設備
二、直接上代碼。
MainActivity主要開啓藍牙,需要動態獲取權限的獲取權限。藍牙傳遞數據分客戶端和服務端,這裏給一個入口,進客戶端和服務端。
package com.qtwl.bluetooth;
import android.Manifest;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.provider.Settings;
import android.support.annotation.RequiresApi;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
public class MainActivity extends Activity implements View.OnClickListener {
BluetoothAdapter mBluetoothAdapter;
public static final String MY_UUID = "00001101-0000-1000-8000-00805F9B34FB";
@RequiresApi(api = Build.VERSION_CODES.M)
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button service = findViewById(R.id.service);
Button clent = findViewById(R.id.clent);
service.setOnClickListener(this);
clent.setOnClickListener(this);
if (Build.VERSION.SDK_INT >= 6.0) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 1000);
}
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (mBluetoothAdapter != null) {
if (!mBluetoothAdapter.isEnabled()) {
Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
// 設置藍牙可見性,最多300秒
intent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
startActivity(intent);
}
} else {
Toast.makeText(this, "沒有藍牙模塊", Toast.LENGTH_SHORT).show();
}
}
@Override
public void onClick(View view) {
if (mBluetoothAdapter == null) {
if (!mBluetoothAdapter.isEnabled()) {
Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
// 設置藍牙可見性,最多300秒
intent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
startActivity(intent);
return;
}
}
Intent intent = null;
// 要在兩臺設備上的應用之間創建連接,必須同時實現服務器端和客戶端機制,因爲其中一臺設備必須開放服務器套接字,而另一臺設備必須發起連接(使用服務器設備的 MAC 地址發起連接)。
// 服務器設備和客戶端設備分別以不同的方法獲得需要的 BluetoothSocket。服務器將在傳入連接被接受時收到套接字。 客戶端將在其打開到服務器的 RFCOMM 通道時收到該套接字。
switch (view.getId()) {
case R.id.service://服務端
intent = new Intent(this, ServiceActivity.class);
break;
case R.id.clent://客戶端
intent = new Intent(this, ClentActivity.class);
break;
}
startActivity(intent);
}
}
ServiceActivity服務端的代碼。
package com.qtwl.bluetooth;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.UUID;
/**
* 連接爲服務器
* 當您需要連接兩臺設備時,其中一臺設備必須通過保持開放的 BluetoothServerSocket 來充當服務器。
* 1.通過調用 listenUsingRfcommWithServiceRecord(String, UUID) 獲取 BluetoothServerSocket。
* 當客戶端嘗試連接此設備時,它會攜帶能夠唯一標識其想要連接的服務的 UUID。 兩個 UUID 必須匹配,在下一步中,連接纔會被接受。
* 通過調用 accept() 開始偵聽連接請求。
* 除非您想要接受更多連接,否則請調用 close()。
* 這將釋放服務器套接字及其所有資源,但不會關閉 accept() 所返回的已連接的 BluetoothSocket。 與 TCP/IP 不同,RFCOMM 一次只允許每個通道有一個已連接的客戶端,因此大多數情況下,在接受已連接的套接字後立即在 BluetoothServerSocket 上調用 close() 是行得通的。
*/
public class ServiceActivity extends BaseActivity {
BluetoothServerSocket mmServerSocket;
TextView title;
TextView tvReceive;
@SuppressLint("HandlerLeak")
Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
tvReceive.setText(tvReceive.getText() + "\n" + msg.obj.toString());
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_service);
title = findViewById(R.id.tv_service_isStart);
tvReceive = findViewById(R.id.tv_service);
try {
mmServerSocket = BluetoothAdapter.getDefaultAdapter().listenUsingRfcommWithServiceRecord("com.qtwl.bluetooth", UUID.fromString(MainActivity.MY_UUID));
title.setText("藍牙服務器開啓成功");
} catch (IOException e) {
e.printStackTrace();
title.setText("藍牙服務器開啓失敗");
}
//寫一個線程來做服務器,等待客戶端來連接它
new Thread() {
@Override
public void run() {
super.run();
while (true) {
try {
showToast("等待別人來連接");
BluetoothSocket socket = mmServerSocket.accept();
if (socket != null) {
Message msg = Message.obtain();
InputStream is = socket.getInputStream();
byte[] buffer = new byte[1024 * 128];
int count = is.read(buffer);
msg.obj = new String(buffer, 0, count, "utf-8");
handler.sendMessage(msg);
OutputStream os = socket.getOutputStream();
os.write("服務器收到了".getBytes("UTF-8"));
showToast("服務器做了返回");
}
} catch (IOException e) {
break;
}
}
}
}.start();
}
}
客戶端的代碼,主要是搜索附近的藍牙,並配對。
package com.qtwl.bluetooth;
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.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.ListView;
import android.widget.Toast;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
/**
* 作爲客戶端
*/
public class ClentActivity extends BaseActivity {
ListView mLvBle;//藍牙列表
Button search;//搜索附近藍牙
private List<BluetoothDevice> mDeviceList = new ArrayList<>();//藍牙列表
private BleAdapter mAdapter;
BluetoothAdapter mBluetoothAdapter;
/**
* 廣播接收者
*/
private BroadcastReceiver bTReceive = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
Log.i("BroadcastReceiver", "BluetoothDevice.ACTION_FOUND device = "+device.getName()+" "+device.getAddress());
for (BluetoothDevice d : mDeviceList) {
if (TextUtils.isEmpty(d.getAddress()) || TextUtils.isEmpty(device.getAddress()) || d.getAddress().equalsIgnoreCase(device.getAddress())) {
return;
}
}
mDeviceList.add(device);
mAdapter.notifyDataSetChanged();
} else if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)) {
Log.e("BroadcastReceiver", "BluetoothDevice.ACTION_BOND_STATE_CHANGED");
BluetoothDevice blnDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
setProgressMsg("正在配對..");
switch (blnDevice.getBondState()) {
case BluetoothDevice.BOND_BONDING://正在配對
setProgressMsg("正在配對..");
break;
case BluetoothDevice.BOND_BONDED://配對結束
mAdapter.notifyDataSetChanged();
setProgressMsg("配對完成,開始連接..");
startActivity(blnDevice);
break;
case BluetoothDevice.BOND_NONE://取消配對/未配對
showToast("配對失敗!");
for (BluetoothDevice d : mDeviceList) {
if (d.getName().equalsIgnoreCase(blnDevice.getName())) {
mAdapter.notifyDataSetChanged();
return;
}
}
dismissProgressDialog();
default:
break;
}
} else if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) {
Log.e("BroadcastReceiver", "BluetoothAdapter.ACTION_BOND_STATE_CHANGED");
Toast.makeText(context, "BluetoothAdapter.ACTION_BOND_STATE_CHANGED", Toast.LENGTH_SHORT).show();
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_clent);
mLvBle = findViewById(R.id.lv_ble);//藍牙列表
search = findViewById(R.id.btn_search);//搜索附近藍牙
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
registerBTReceiver();// 註冊藍牙相關的各種廣播
//給藍牙列表設置數據
mAdapter = new BleAdapter(this);
mAdapter.setData(mDeviceList);
mLvBle.setAdapter(mAdapter);
mLvBle.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
if (mBluetoothAdapter != null)
mBluetoothAdapter.cancelDiscovery();
BluetoothDevice device = mDeviceList.get(i);
bondBT(device);
}
});
search.setOnClickListener(new View.OnClickListener() {//點擊搜索按鈕
@Override
public void onClick(View view) {
if (mBluetoothAdapter.isDiscovering()) {
return;
}
mBluetoothAdapter.startDiscovery();
}
});
}
/**
* 註冊廣播
*/
public void registerBTReceiver() {
// 設置廣播信息過濾
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(BluetoothDevice.ACTION_FOUND); //發現藍牙事件
intentFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);//藍牙掃描結束事件
// intentFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
intentFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);//狀態改變
intentFilter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
// 註冊廣播接收器,接收並處理搜索結果
registerReceiver(bTReceive, intentFilter);
}
@Override
protected void onStop() {
super.onStop();
mBluetoothAdapter.cancelDiscovery();
}
@Override
protected void onResume() {
super.onResume();
}
@Override
protected void onDestroy() {
super.onDestroy();
//註銷廣播
unregisterReceiver(bTReceive);
}
/**
* 綁定藍牙
*/
private void bondBT(BluetoothDevice device) {
showProgressDialog("配對藍牙開始");
// 搜索藍牙設備的過程佔用資源比較多,一旦找到需要連接的設備後需要及時關閉搜索
// 獲取藍牙設備的連接狀態
int connectState = device.getBondState();
switch (connectState) {
case BluetoothDevice.BOND_NONE: // 未配對
setProgressMsg("開始配對");
try {
Method createBondMethod = BluetoothDevice.class.getMethod("createBond");
createBondMethod.invoke(device);
} catch (Exception e) {
showToast("配對失敗!");
dismissProgressDialog();
e.printStackTrace();
}
break;
case BluetoothDevice.BOND_BONDED: // 已配對
//進入連接界面
startActivity(device);
break;
}
}
//如果配對好了,就跳轉到發消息界面
private void startActivity(BluetoothDevice device) {
dismissProgressDialog();
Intent intent = new Intent(this, ClentSendActivity.class);
intent.putExtra("device", device);
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
startActivity(intent);
}
}
ClentSendActivity客戶端連接的代碼。客戶端連接服務器,併發送數據,然後收到服務器返回的數據。
package com.qtwl.bluetooth;
import android.app.Activity;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.UUID;
/**
* 客戶端發消息
*/
public class ClentSendActivity extends BaseActivity {
private BluetoothDevice device;
private BluetoothSocket bTSocket;
TextView btNmae;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_clent_send);
btNmae = findViewById(R.id.bluetooth_name);
device = getIntent().getParcelableExtra("device");
btNmae.setText(device.getName() + " " + device.getAddress());
connectDevice();
}
/**
* 連接藍牙
*/
private void connectDevice() {
showProgressDialog("正在連接藍牙");
new Thread() {
@Override
public void run() {
super.run();
try {
bTSocket = device.createRfcommSocketToServiceRecord(UUID.fromString(MainActivity.MY_UUID)); //創建一個Socket連接:只需要服務器在註冊時的UUID號
bTSocket.connect(); //連接
showToast("連接成功");
dismissProgressDialog();
OutputStream os = bTSocket.getOutputStream();
os.write("test 測試數據".getBytes("UTF-8"));
showToast("寫數據成功");
InputStream is = bTSocket.getInputStream();
byte[] buffer = new byte[1024 * 128];
int count = is.read(buffer);
String string = new String(buffer, 0, count, "utf-8");
showToast("讀到數據 string = " + string);
} catch (IOException e) {
showToast("連接異常,請退出本界面再次嘗試!");
dismissProgressDialog();
e.printStackTrace();
}
}
}.start();
}
}
主要功能上面都有,這個是上面的 源碼