蓝牙技术是一种近距离通信技术,自从研发至今,已经广泛引用与一些设备中,生活中处处可见蓝牙耳机,有些只能穿戴设备也是基于蓝牙进行通信的,蓝牙通信功耗小,对于近距离通信是一个不错的选择,所以我在做一个小项目的时候使用了蓝牙通信,本文中制作的蓝牙聊天Demo就是为了尝试蓝牙通信而写的小Demo,所以如有错误,请指正。
蓝牙通信在Android6.0中需要设置的权限
根据需要设置自己的权限:
<pre name="code" class="java"><uses-permissionandroid:name="android.permission.BLUETOOTH"/>
<!--设备之间申请连接,交流等-->
<uses-permissionandroid:name="android.permission.BLUETOOTH_ADMIN"/>
<!--允许程序去搜索和配对蓝牙设备。-->
<uses-permissionandroid:name="android.permission.BLUETOOTH_PRIVILEGED"/>
<!--允许设备不经过用户操作自动配对-->
<uses-permission-sdk-23android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<!--动态扫描的权限,android6.0需要设置此权限-->
打开蓝牙的方法
bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
// if (bluetoothAdapter == null) {
// Toast.makeText(context,"对不起,您的设备不支持蓝牙,即将退出", Toast.LENGTH_SHORT).show();
// finish();
// } else if(!bluetoothAdapter.isEnabled()) {//蓝牙未开启
// Intent intent = newIntent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
// startActivityForResult(intent, REQUEST_BLUETOOTH_OPEN);
// }
设置蓝牙可见性的方法
1. 通过Intent启动
private void ensureBluetoothDiscoverable() {
if(bluetoothAdapter.getScanMode() !=
BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE){
Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
intent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 3);
startActivity(intent);
}
}
2. 通过系统setting强制开启
public void setDiscoverableTimeout(int timeout) {
BluetoothAdapter adapter=BluetoothAdapter.getDefaultAdapter();
try {
Method setDiscoverableTimeout = BluetoothAdapter.class.getMethod("setDiscoverableTimeout", int.class);
setDiscoverableTimeout.setAccessible(true);
Method setScanMode =BluetoothAdapter.class.getMethod("setScanMode", int.class,int.class);
setScanMode.setAccessible(true);
setDiscoverableTimeout.invoke(adapter, timeout);
setScanMode.invoke(adapter, BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE,timeout);
} catch (Exception e) {
e.printStackTrace();
}
}
第一种方法,让用户看到自己开启了蓝牙的可见性,第二种直接从后台开启蓝牙可见,但会一直可见下去,直到蓝牙关闭。我在小米miui + android6.0使用了第一种方法开启了蓝牙可见性,但并没有想象中的到时间自动关闭,因为这时的小米已经去掉了蓝牙可见性的计时关闭,只会一直开启下去,所以我找了好久,才找到下面的方法关闭了蓝牙可见性。
关闭蓝牙可见性
private void closeBluetoothDiscoverable(){
//尝试关闭蓝牙可见性
try {
Method setDiscoverableTimeout = BluetoothAdapter.class.getMethod("setDiscoverableTimeout", int.class);
setDiscoverableTimeout.setAccessible(true);
Method setScanMode =BluetoothAdapter.class.getMethod("setScanMode", int.class,int.class);
setScanMode.setAccessible(true);
setDiscoverableTimeout.invoke(bluetoothAdapter, 1);
setScanMode.invoke(bluetoothAdapter, BluetoothAdapter.SCAN_MODE_CONNECTABLE,1);
} catch (Exception e) {
e.printStackTrace();
}
}
BluetoothAdapter的常用方法
getAddress()获取本地蓝牙地址
getDefaultAdapter()获取默认BluetoothAdapter,实际上,也只有这一种方法获取BluetoothAdapter
getName()获取本地蓝牙名称
getRemoteDevice(String address)根据蓝牙地址获取远程蓝牙设备
getState()获取本地蓝牙适配器当前状态(感觉可能调试的时候更需要)
isDiscovering()判断当前是否正在查找设备,是返回true
isEnabled()判断蓝牙是否打开,已打开返回true,否则,返回false
listenUsingRfcommWithServiceRecord(String name,UUID uuid)根据名称,UUID创建并返回
BluetoothServerSocket,这是创建BluetoothSocket服务器端的第一步
startDiscovery()开始搜索,这是搜索的第一步
BluetoothAdapter里的方法很多,常用的有以下几个:
cancelDiscovery() 根据字面意思,是取消发现,也就是说当我们正在搜索设备的时候调用这个方法将不再继续搜索
disable()关闭蓝牙
enable()打开蓝牙,这个方法打开蓝牙不会弹出提示,更多的时候我们需要问下用户是否打开,一下这两行代码同样是打开蓝牙,不过会提示用户:
搜索蓝牙设备
Android6.0蓝牙搜索需要定位权限,具体博文请看Android6.0权限详解, 蓝牙搜索使用的权限申请方法如下:
private void mayRequestLocation(){
Log.d(TAG, "mayRequestLocation: androidSDK--" + Build.VERSION.SDK_INT);
if(Build.VERSION.SDK_INT >= 23){
//6.0以上设备
int checkCallPhonePermission = checkSelfPermission(Manifest.permission.
ACCESS_COARSE_LOCATION);
if(checkCallPhonePermission != PackageManager.PERMISSION_GRANTED) {
Log.d(TAG, "mayRequestLocation: 请求粗略定位的权限");
requestPermissions(new String[]{Manifest.permission.
ACCESS_COARSE_LOCATION}, REQUEST_PERMISSION_LOCATION);
return;
}
}
蓝牙搜索具体方法:
//查找配对过的设备
mayRequestLocation();
Set<BluetoothDevice> pairedDevices = bluetoothAdapter.getBondedDevices();
if (pairedDevices.size() > 0) {
for (BluetoothDevice device : pairedDevices) {
Log.d(TAG, "已配对设备:" + device.getName() + " address: " + device.getAddress());
}
} else {
Log.d(TAG, "onClick: 没有找到配对的设备");
}
//尝试搜索设备
BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
switch (action) {
case BluetoothAdapter.ACTION_DISCOVERY_FINISHED: {
Log.d(TAG, "onReceive: 结束查找设备");
break;
}
case BluetoothAdapter.ACTION_DISCOVERY_STARTED: {
Log.d(TAG, "onReceive: 开始查找设备");
break;
}
case BluetoothDevice.ACTION_FOUND: {
/* 从intent中取得搜索结果数据 */
Log.d(TAG, "onReceive: 查找到设备");
BluetoothDevice device = intent
.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
Log.d(TAG, "设备:" + device.getName() + " address: " + device.getAddress());
break;
}
}
}
};
IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
registerReceiver(receiver, filter);
filter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
registerReceiver(receiver, filter);
filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver(receiver, filter);
if (bluetoothAdapter.isDiscovering()) {//正在查找
Log.d(TAG, "onClick: 正在查找设备");
} else {
bluetoothAdapter.startDiscovery();
}
两个蓝牙设备的连接
import java.io.IOException;
/**
* 客户端,用于连接服务端
*/
class ClientThread extends Thread {
/**
* 在创建客户端的时候,创建bluetoothSocket
*/
public ClientThread() {
try {
bluetoothSocket =bluetoothDevice.createRfcommSocketToServiceRecord(uuid);
} catch (IOException e) {
Log.e(TAG, "ClientThread:", e);
e.printStackTrace();
}
}
/**
* 在线程内创建子线程,进行连接,我觉的这是由于多线程思想决定的
* 在一个主线程中开辟子线程实现操作,从而可以实现并发,不会导致
* 主线程的阻塞,导致IOException
*/
@Override
public void run() {
new Thread(new Runnable() {
@Override
public void run() {
if(bluetoothSocket == null){
return;
}else{
try {
bluetoothSocket.connect();
/**
* 连接成功后,将连接完成的socket传入已连接线程
*/
connectedThread = new ConnectedThread(bluetoothSocket);
connectedThread.start();
} catch (IOException e) {
Log.e(TAG, "run: ", e);
e.printStackTrace();
}
}
}
}).start();
}
}
/**
* 服务器线程,用于接受来自客户端的访问
*/
private class ServerThread extends Thread {
/**
* 蓝牙通信,两者需要使用同一个UUID
*/
public ServerThread() {
Log.d(TAG, "ServerThread: 构件服务端");
try {
bluetoothServerSocket =BluetoothAdapter.getDefaultAdapter().
listenUsingRfcommWithServiceRecord("test", uuid);
} catch (IOException e) {
Log.e(TAG, "ServerThread: construct", e);
e.printStackTrace();
}
}
/**
* 服务端等待连接线程,从等级上讲,比客户端的连接线程高一个级别,应该是由于服务器端
* 只需要等待一个连接请求,但并不考虑多线程,因为请求发出必有先后,而客户端却需要考
* 虑线程,所以低了一个级别
*/
@Override
public void run() {
while (bluetoothServerSocket != null) {
try {
Log.e(TAG, "run:serverThread 等待连接");
bluetoothSocket1 =bluetoothServerSocket.accept();
Message message = new Message();
message.what = MSG_WAIT_CONNECT;
handler.sendMessage(message);
} catch (IOException e) {
Log.e(TAG, "run:ServerThread", e);
e.printStackTrace();
}
if (bluetoothSocket1 != null) {
Log.d(TAG, "run: connectsuccess");
connectedThread = new ConnectedThread(bluetoothSocket1);
connectedThread.start();
Message message = new Message();
message.what = MSG_CONNECT_SUCCESS;
handler.sendMessage(message);
}
}
}
}
/**
* 已连接的线程,用于通信
*/
private class ConnectedThread extends Thread{
private BluetoothSocket bluetoothSocket;
private OutputStream outputStream;
private InputStream inputStream;
byte [] bytes = new byte[1024];
/**
* 获取输入输出流
* @param bluetoothSocket
*/
public ConnectedThread(BluetoothSocket bluetoothSocket) {
this.bluetoothSocket = bluetoothSocket;
try {
outputStream = this.bluetoothSocket.getOutputStream();
inputStream = this.bluetoothSocket.getInputStream();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 读取线程,一直工作
*/
@Override
public void run() {
int i = 0;
if(inputStream == null)
return;
do {
try {
i = inputStream.read(bytes);
Message message = new Message();
message.what = MSG_READ_STRING;
String string = new String(bytes);
message.obj = string;
handler.sendMessage(message);
} catch (IOException e) {
e.printStackTrace();
}
}while (i != 0);
}
/**
* 写不需要线程,值得一提的是读取和写入都是用bytes串的形式传输的
* @param string
*/
public void write(String string){
byte [] bytes;
bytes = string.getBytes();
try {
outputStream.write(bytes);
MessageForChat msg = new MessageForChat(true, string);
messageList.add(msg);
adapter.notifyDataSetChanged();
} catch (IOException e) {
e.printStackTrace();
}
}
}
以下是做的一个蓝牙聊天的Demo
顶上的文字区域用来显示正在连接的蓝牙设备的名字和地址,而聊天窗口模仿微信聊天窗口,如下图:
Demo的git地址是:https://github.com/NoClay/WeChat.git