本人水平有限,文章中如果出现什么不正确或者模糊的地方,还请各位小伙伴留下评论,多多指教 : )
之前2篇关于蓝牙的文章是很久之前写的博文,算是CSDN的开篇之作,里面存在许多的不足,而且后面也没能坚持下来,有一些小伙伴私信我要这部分的代码,刚好最近手上的一个项目也是和蓝牙有关,所以今天特意特意把这个项目又下下来,一方面总结最近一段时间关于蓝牙的开发,另一方也算把前两篇的坑给填上~~
至于这个项目当中涉及到的其他内容,例如数据库的使用,界面的编写等都不再在这个系列的博客做详细介绍(当时写的太菜了……没啥好介绍的(:з」∠)有兴趣的小伙伴届时可以看看源码~),本系列的博客将会把重点放在蓝牙的处理上。这里附上之前写的两篇文章地址,有兴趣的小伙伴可以去看一看~~
android 蓝牙锁应用实例开发(一) 简介
android 蓝牙锁应用开发实例(二)客户端基本页面
我一直认为,无论做什么样的开发,一定要思路先行,在了解大致的工作流程后,才便于我们后面对各种琐碎细节的研究。不仅能加深理解,还能养成良好的思维模式。接下来就带大家简单梳理一下蓝牙开发的流程。
(1)获取蓝牙开发的权限
(2)扫描附近的蓝牙设备,并将这些设备加入到devices列表
(3)选择要配对的设备,将次设备加入到已配对设备列表
(4)进行数据通信或其他操作
(5)接触配对状态
若干年之后,你可能具体的代码细节忘记 ,但是上面的步骤应该是做过一次开发就不会忘记了。
在第2步和第3部中涉及了2个列表,一个是【设备列表】,一个是【匹配设备列表】。
设备列表——是指附近的可见蓝牙设备,但是还没匹配
匹配设备列表——是指已经完成匹配的列表
大家要理解这2种列表的区别。因为列表的区别,也代表了设备状态。
即【未匹配状态】、【匹配状态】、【配对状态】
未匹配,就是指从来没有匹配过的设备。
匹配状态,就是指设备间已经相互完成了身份验证,处于待配对(待建立Socket)状态。
不知大家还记不记得,以前蓝牙配对的时候,手机会弹出一个对话框,提示输入pin码,其实也就是提前约定的一个配对码,到后来,手机与与手机之间的连接就不需要配对码了(实际上是程序内部完成了配对的过程)。
当然,手机与一些蓝牙硬件(例如单片机+蓝牙模块的组合)配对时,还是需要输入pin码(不过也能通过程序自动完成验证)
那么什么配对状态呢?
配对的过程,其实就是Socket通信的一个过程,两个蓝牙设备仅仅匹配是还不能够传递数据的,只有当二者建立了Socket通道之后,才能进行数据的传递。
好了,基本是思路差不多介绍完了,为了加深大家对蓝牙的理解,我这里再把上面的内容,通过图的形式再总结一下。同时我也会把蓝牙相关操作的核心API给列在次图中,让大家对下一节的内容有大致的了解
接下来,将对涉及蓝牙操作的几个核心API进行介绍。
本节内容将讲解蓝牙操作的核心API,这里说明一下,android的蓝牙开发有涉及到不同的蓝牙种类,例如低功耗蓝牙(BluetoothGatt)、蓝牙健康(BlueToothHealth)等,这里介绍的依然是常规的蓝牙开发API
再多啰嗦一句……API查看哪家强……那肯定是官方文档啦~当然是对于英文基础不错的小伙伴,不过英语一般的小伙伴也不用太担心,毕竟有很多优秀的翻译软件及API中文站,这里我提供一个传送们,希望深入理解的BlueTooth或者其他API的小伙伴有空可以经常去看看。
Bluetooth www.android-doc.com
这个类代表着本地的蓝牙适配器,让你可以从事各种与蓝牙相关的操作,例如开始和停止设备的查找;查询已匹配的设备并以集合的形式返回;通过已知的设备地址,实例化一个BlueToothDevice;创建一个BluetoothServerSocket来监听来自其他设备的链接请求等等,总之要想使用本机的蓝牙,这个类是极其重要的。
获取本地的蓝牙适配器,一般的调用代码如下:
//获取本地蓝牙适配器
bluetoothAdapter=BluetoothAdapter.getDefaultAdapter();
获取已经匹配的设备列表,并以set集合的方式返回
调用代码如下:
/*
*获取已经配对的设备
* */
private void setPairingDevice() {
Set devices=bluetoothAdapter.getBondedDevices();
if(devices.size()>0){ //存在已配对过的设备
//利用for循环读取每一个设备的信息
for(Iterator it = devices.iterator(); it.hasNext();){
BluetoothDevice btd=it.next();
```````
}
}else{
//不存在已经配对的蓝牙设备
}
}
判断当前是否正在查找设备,是返回true
这里需要注意一下,由于搜素是一个耗时操作,所以这个方法应该在线程中去调用,同时需要配合着广播去使用,关于广播的内容,将在下一个小节中详细讲解。
开始查找设备,一般的调用代码如下:
/**
* 开始附加设备的查找
*/
private void doDiscovery() {
new Thread(new Runnable() {
@Override
public void run() {
//如果adapter正在查询,停止查询
if(bluetoothAdapter.isDiscovering()){
bluetoothAdapter.cancelDiscovery();
}
bluetoothAdapter.startDiscovery();
}
}).start();
}
这里再注意一个细节,那就是如果当前的adapter正在查找,那么必须停止当前查找,然后再重新查找,这是因为查找操作占用很多的系统资源,我们需要避免重复的查找
取消查找设备,在上面的已经调用过
根据一个蓝牙设备的地址来获取该地址,这个方法返回一个BlueToothDevice的实例
字面意思,打开或者关闭本机的蓝牙。
一般的调用代码如下:
//获取蓝牙适配器
mBluetoothAdapter=BluetoothAdapter.getDefaultAdapter();
//如果蓝牙没有打开则打开蓝牙
if (!mBluetoothAdapter.isEnabled()) {
//请求用户开启
Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(intent, REQUEST_COMMUNICATE);
}
getAddress()获取本地蓝牙地址
getName()获取本地蓝牙名称
getState()获取本地蓝牙适配器当前状态
listenUsingRfcommWithServiceRecord(String name,UUID uuid)根据名称,UUID创建并返回BluetoothServerSocket。这个方法是创建BluetoothSocket服务器端不可缺少的一步。
这个类代表一个远程设备,它能够让我们与一个其他相关设备创建连接,或者查询与此设备相关的信息,例如名称(name)、地址(address)、连接状态(boud state)
根据UUID创建并返回一个BluetoothSocket。
getAddress()获取Address设备的蓝牙地址
getName()获取Address设备的地蓝牙名称
getState()获取Address设备的蓝牙适配器当前状态
这里把这两个API放在一起说,这两个方法相互对应,ServerSocket对应的服务端,Socket对应的是客户端,具体内容在下一节中介绍
至此,所有的比较重要的API都已经介绍完毕了,现在正式进入代码编写的阶段。
本篇文章只完成附近蓝牙设备的搜索和链接,关于数据通信的部分将在下一篇文章中介绍
功能:
(1)搜索附近蓝牙设备
(2)与附近蓝牙设备配对
这个类是自定义的BaseAdapter,作为用来显示蓝牙设备的listView的适配器
源码:
/**
* Created by dell on 2016/9/22.
* 存放以匹配蓝牙设备的ListView的Adapter
*/
public class DeviceListAdapter extends BaseAdapter {
//标识符,详见【说明1】
private final int NEAR_DEVICE=1;
private final int PAIRING_DEVICE=2;
//存储设备的容器
private DeviceListListener deviceListListener;
private ArrayList devices;
private BluetoothAdapter mBluetoothAdapter=BluetoothAdapter.getDefaultAdapter();
private LayoutInflater inflater;
private Context context;
//用于标示附近设备和以配对设备的标识
private int flag;
//接口
public interface DeviceListListener{
//更新ArrayList
void notifyArrayList();
//提示取消配对成功
void cancelSuccess();
//配对成功
void bindSuccess();
}
public DeviceListAdapter(Context context, ArrayList devices){
this.devices=devices;
this.context=context;
inflater=LayoutInflater.from(context);
}
//为adapter设置一个flag
public void setFlag(int i ){
flag=i;
}
public void setDeviceListListener(DeviceListListener deviceListListener){
this.deviceListListener=deviceListListener;
}
@Override
public int getCount() {
return devices.size();
}
@Override
public Object getItem(int i) {
return devices.get(i);
}
@Override
public long getItemId(int i) {
return i;
}
@Override
public View getView(int i, View view, ViewGroup viewGroup) {
ViewHolder viewHolder;
//如果View是空,则实例化viewHolder
if(view==null){
viewHolder=new ViewHolder();
view=inflater.inflate(R.layout.binding_device_list_item,null);
viewHolder.deviceAddress= (TextView) view.findViewById(R.id.device_address);
viewHolder.deviceName= (TextView) view.findViewById(R.id.device_name);
view.setTag(viewHolder);
}else{
viewHolder= (ViewHolder) view.getTag();
}
final Device device=devices.get(i);
//设置设备名称和地址
viewHolder.deviceAddress.setText(device.getAddress());
viewHolder.deviceName.setText(device.getDevName());
//为view设置点击事件
switch (flag){
case NEAR_DEVICE:
setNearDeViceEvent(view,device);
break;
case PAIRING_DEVICE:
setPairingDeviceEvent(view,device);
break;
}
return view;
}
/**
* 为配对设备ListView设置相关事件
*/
private void setPairingDeviceEvent(View view,final Device device) {
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
}
});
view.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View view) {
final AlertDialog.Builder builder =new AlertDialog.Builder(context);
builder.setTitle("提示").
setMessage("您确定要与该设备取消配对吗?").
setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
BlueToothUnit.unBindDevice(mBluetoothAdapter,device,deviceListListener);
}
}).show();
return false;
}
});
}
/**
* 为附近设备ListView设置相关事件
*/
private void setNearDeViceEvent(View view,final Device device) {
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
final AlertDialog.Builder builder=new AlertDialog.Builder(context);
builder.setTitle("提示").
setMessage("您确定要和此设备配对吗?")
.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
try {
BlueToothUnit.bindTargetDevice(mBluetoothAdapter,device,deviceListListener);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}).
setNegativeButton("取消",null).
show();
}
});
}
class ViewHolder
{
TextView deviceName;
TextView deviceAddress;
}
}
【说明1】
因为这个adapter既要被已匹配设备的listView使用,也要被附近设备的listView使用,而两个listView对应 item点击事件又是不一样的,所以,在一开始设置了2个标识符,用以区分。
【说明2】
与蓝牙有关的操作,都封装在了BluetoothUnit当中
【说明3】
此类是一个标准BaseAdapter的写法,getView()当中通过LayoutInfater加载了一个自定义的布局,同时使用了viewHolder对listView进行优化。
一个标准的Java Bean,表示一个蓝牙设备的实例
public class Device {
//蓝牙地址
private String address;
//设备名称
private String devName;
public Device(String devName, String address) {
this.address = address;
this.devName = devName;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getDevName() {
return devName;
}
public void setDevName(String devName) {
this.devName = devName;
}
}
封装了与蓝牙操作相关的方法
public class BlueToothUnit {
private final String TAG = "--BlueToothUnit--";
public static void unBindDevice(BluetoothAdapter mBluetoothAdapter, Device device, DeviceListAdapter.DeviceListListener deviceListListener){
//利用反射的方法取消配对
BluetoothDevice device0 = mBluetoothAdapter.getRemoteDevice(device.getAddress());
try {
Boolean returnValue ;
Method m = device0.getClass().getMethod("removeBond");
returnValue = (Boolean) m.invoke(device0);
if (returnValue)
deviceListListener.cancelSuccess();
} catch (Exception e) {
e.printStackTrace();
}
//成功取消配对后更新listView
deviceListListener.notifyArrayList();
}
/**
* 配对指定的设备
*/
public static void bindTargetDevice(BluetoothAdapter localBluetoothAdapter, Device device, DeviceListAdapter.DeviceListListener deviceListListener) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
//在配对之前,一定要停止搜搜
if (localBluetoothAdapter.isDiscovering()) {
localBluetoothAdapter.cancelDiscovery();
}
//获取配对的设备
BluetoothDevice btDev = localBluetoothAdapter.getRemoteDevice(device.getAddress());
Boolean returnValue;
if (btDev.getBondState() == BluetoothDevice.BOND_NONE) {
//利用反射方法调用BluetoothDevice.createBond(BluetoothDevice remoteDevice);
Method createBondMethod = BluetoothDevice.class.getMethod("createBond");
Log.d("---", "开始配对");
returnValue = (Boolean) createBondMethod.invoke(btDev);
if (returnValue){
Log.d("---", "bindTargetDevice "+"配对成功");
deviceListListener.bindSuccess();
}
}
}
/**
* @param bluetoothAdapter 本地蓝牙适配器
* 开始蓝牙设备的搜索
*/
public static void findAvailableDevices(final BluetoothAdapter bluetoothAdapter) {
//开始搜索工作
new Thread(new Runnable() {
@Override
public void run() {
//如果蓝牙正在搜索,则停止搜索,然后再重新开始搜索
if (bluetoothAdapter.isDiscovering()) {
bluetoothAdapter.cancelDiscovery();
}
//调用此方法会不断的发送广播,用户只需自定义一个receiver接受即可
bluetoothAdapter.startDiscovery();
}
}).start();
}
}
通过反射的方法完成连接或取消配对
接受蓝牙相关的广播,并作出相应的操作
public class BtBroadcastReceiver extends BroadcastReceiver {
private final String TAG="--broadcastReceiver--";
private ArrayList nearDevices;
private DeviceListAdapter adapter;
private ProgressDialog progressDialog;
public interface BroadcastListener{
void finishSearch();
void bindSuccess();
}
private BroadcastListener broadcastListener;
public BtBroadcastReceiver(Context context, ArrayList nearDevices,DeviceListAdapter adapter,BroadcastListener broadcastListener){
this.nearDevices=nearDevices;
this.broadcastListener=broadcastListener;
this.adapter=adapter;
progressDialog=new ProgressDialog(context);
progressDialog.setTitle("正在查询附近设备");
progressDialog.setCancelable(false);
progressDialog.setMessage("请稍等……");
}
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
switch (action){
case BluetoothAdapter.ACTION_DISCOVERY_STARTED: //索索开始
Log.w(TAG,"--开始寻找设备");
progressDialog.show();
//清空nearList里面的数据
nearDevices.clear();
break;
case BluetoothDevice.ACTION_FOUND: //发现设备
Log.w(TAG,"--找到设备");
//拿到蓝牙设备
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
//如果此设备的状态是还未匹配,则把它加入到设备列表当中
if(device.getBondState()==BluetoothDevice.BOND_NONE) {
Device d=new Device(device.getName(),device.getAddress());
nearDevices.add(d);
adapter.notifyDataSetChanged();
}
progressDialog.setMessage("已找到"+nearDevices.size()+"台设备……");
break;
case BluetoothDevice.ACTION_BOND_STATE_CHANGED: //状态改变
device=intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
switch (device.getBondState()) {
case BluetoothDevice.BOND_BONDING:
Log.w("BlueToothTestActivity", "正在配对......");
progressDialog.setTitle("正在配对");
progressDialog.setMessage("请稍等……");
progressDialog.show();
break;
case BluetoothDevice.BOND_BONDED:
Log.w("BlueToothTestActivity", "配对完成");
broadcastListener.bindSuccess();
progressDialog.dismiss();
break;
case BluetoothDevice.BOND_NONE:
Log.w("BlueToothTestActivity", "取消配对");
default:
break;
}
break;
case BluetoothAdapter.ACTION_DISCOVERY_FINISHED:// 搜索完成
Log.w("finish:----", "搜索完成!");
progressDialog.dismiss();
if(nearDevices.size()==0){
Toast.makeText(context,"附近没有可用设备",Toast.LENGTH_SHORT).show();
}else{
Toast.makeText(context,"搜索到"+nearDevices.size()+"台设备",Toast.LENGTH_SHORT).show();
}
broadcastListener.finishSearch();
break;
}
}
}
这个类主要是监听蓝牙相关的广播,这些广播的注册逻辑放在了PairingDeviceActivity当中
显示已配对设备的listView,以及搜索附近的设备和广播的注册
/**
* Created by dell on 2016/9/22.
* 本类用于显示已经和手机匹配了的设备,并且可以搜寻附近的设备
*/
public class PairingDeviceActivity extends AppCompatActivity implements BtBroadcastReceiver.BroadcastListener {
private final String TAG="--Pairing--";
//状态标识符
private final int NEAR_DEVICE=1;
private final int PAIRING_DEVICE=2;
private TextView emptyView;
//显示已经匹配的设备
private ListView pairingListView;
//蓝牙适配器
private BluetoothAdapter bluetoothAdapter;
//存储以匹配设备的容器
private ArrayList pairingDevices=new ArrayList<>();
//存储附近设备的容器
private ArrayList nearDevices=new ArrayList<>();
//已匹配设备的Adapter
private DeviceListAdapter pairingDeviceListAdapter;
//附近设备的Adapter
private DeviceListAdapter nearDeviceListAdapter;
//匹配附近设备时弹出的alter
private AlertDialog alertDialog;
private LayoutInflater inflater;
//是否进行了广播接收器注册的标识符
private boolean hasRegister=false;
//广播接收器
private BtBroadcastReceiver mReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.pairing_device_activity);
//初始化控件
init();
//初始化适配器
initAdapter();
//获取当前匹配的数据
setPairingDevice();
nearDeviceListAdapter.setDeviceListListener(new DeviceListAdapter.DeviceListListener() {
@Override
public void notifyArrayList() {
}
@Override
public void cancelSuccess() {
}
@Override
public void bindSuccess() {
// Toast.makeText(PairingDeviceActivity.this,"配对成功!",Toast.LENGTH_SHORT).show();
}
});
//添加更新ListView的方法
pairingDeviceListAdapter.setDeviceListListener(new DeviceListAdapter.DeviceListListener() {
@Override
public void notifyArrayList() {
pairingDevices.clear();
//让adapter获得以匹配的设备
setPairingDevice();
pairingDeviceListAdapter.notifyDataSetChanged();
}
@Override
public void cancelSuccess() {
Toast.makeText(PairingDeviceActivity.this,"已取消配对",Toast.LENGTH_SHORT).show();
}
@Override
public void bindSuccess() {
Toast.makeText(PairingDeviceActivity.this,"配对成功!",Toast.LENGTH_SHORT).show();
}
});
}
/*
* 初始化ui及其他组件
* */
private void init() {
pairingListView= (ListView) findViewById(R.id.list_binding_devices);
emptyView= (TextView) findViewById(R.id.tv_empty);
inflater=LayoutInflater.from(PairingDeviceActivity.this);
}
/**
* 初始化适配器
*/
private void initAdapter() {
//获取本地蓝牙适配器
bluetoothAdapter=BluetoothAdapter.getDefaultAdapter();
//匹配设备和附加设备list的适配器
nearDeviceListAdapter=new DeviceListAdapter(this,nearDevices);
pairingDeviceListAdapter=new DeviceListAdapter(this,pairingDevices);
//设置adapter的标识符
pairingDeviceListAdapter.setFlag(PAIRING_DEVICE);
nearDeviceListAdapter.setFlag(NEAR_DEVICE);
//list与adapter绑定
pairingListView.setAdapter(pairingDeviceListAdapter);
//初始化接收器
mReceiver=new BtBroadcastReceiver(PairingDeviceActivity.this,nearDevices,nearDeviceListAdapter,this);
}
//寻找附近设备
public void onClickFindNearDevices(View view){
//开始查询工作
doDiscovery();
}
/**
* 开始附加设备的查找
*/
private void doDiscovery() {
BlueToothUnit.findAvailableDevices(bluetoothAdapter);
}
@Override
protected void onStart() {
super.onStart();
//注册广播
if(!hasRegister) {
hasRegister = true;
//寻找到设备的过滤器
IntentFilter filterStar = new IntentFilter(BluetoothDevice.ACTION_FOUND);
//绑定状态改变
IntentFilter changeFilter= new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
//搜索完成
IntentFilter filterFinish = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
//开始搜搜
IntentFilter startSearch=new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
registerReceiver(mReceiver,startSearch);
registerReceiver(mReceiver, filterFinish);
registerReceiver(mReceiver, filterStar);
registerReceiver(mReceiver, changeFilter);
}
//注册的广播在活动结束时需要注销
}
/*
*获取已经配对的设备
* */
private void setPairingDevice() {
Set devices=bluetoothAdapter.getBondedDevices();
if(devices.size()>0){ //存在已配对过的设备
emptyView.setVisibility(View.GONE);
pairingListView.setVisibility(View.VISIBLE);
pairingDevices.clear();
for(Iterator it = devices.iterator(); it.hasNext();){
BluetoothDevice btd=it.next();
Device device =new Device(btd.getName(),btd.getAddress());
Log.d("---", "setPairingDevice: name "+btd.getName()+" address is "+btd.getAddress());
//将device添加到PairingDeviceActivity当中的devices,注意this
pairingDevices.add(device);
}
}else{ //不存在已经配对的蓝牙设备
emptyView.setVisibility(View.VISIBLE);
pairingListView.setVisibility(View.GONE);
Toast.makeText(this,"不存在已配对设备",Toast.LENGTH_SHORT).show();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
//如果蓝牙适配器正在扫描,则停止扫描
if(bluetoothAdapter!=null&&bluetoothAdapter.isDiscovering())
bluetoothAdapter.cancelDiscovery();
//注销广播
if(hasRegister){
hasRegister=false;
unregisterReceiver(mReceiver);
}
}
@Override
public void bindSuccess() {
Log.d(TAG, "bindSuccess: ");
alertDialog.dismiss();
Toast.makeText(PairingDeviceActivity.this,"配对成功",Toast.LENGTH_SHORT).show();
//更新数据
setPairingDevice();
pairingDeviceListAdapter.notifyDataSetChanged();
}
//广播完成搜索时
@Override
public void finishSearch() {
//停止查询
//如果蓝牙适配器正在扫描,则停止扫描
if(bluetoothAdapter!=null&&bluetoothAdapter.isDiscovering()){
Log.d("---", "finishSearch: " +"停止扫描");
bluetoothAdapter.cancelDiscovery();
}
AlertDialog.Builder builder=new AlertDialog.Builder(PairingDeviceActivity.this);
View view1=inflater.inflate(R.layout.near_devices_alertdialog,null);
ListView nearAvailableListView= (ListView) view1.findViewById(R.id.list_near_devices);
//为附近可用设备的list绑定adaper
nearAvailableListView.setAdapter(nearDeviceListAdapter);
alertDialog=builder.setTitle("附近的设备")
.setView(view1)
.create();
alertDialog.show();
}
}
【说明1】PairingDeviceActivity实现了BtBroadcastReceiver的接口,这个接口中需要重写三个方法,finishSearch()和bindSuccess()表示完成搜索和绑定成功。
至此,就完成了蓝牙的搜索和配对,上面的代码还请大家更多关注逻辑上的处理,完整的项目代码,会在下此写蓝牙数据通信时一并贴出。