转载请标明出处:
http://blog.csdn.net/djy1992/article/details/10144843
本文出自:【奥特曼超人的博客】
蓝牙开发,应该是很多人会涉及到的,现在为大家推荐一些资料,还有要注意的是,蓝牙用BLE设备搜索 Activity 的话需要 SDK 4.3 以上。
由于Android蓝牙开发的通信都需要用到UUID,如果由手机发起搜索,当搜索到电脑的蓝牙时,能够得到蓝牙的地址(address),但通信时需要得到BluetoothSocket,而BluetoothSocket则需要电脑蓝牙的UUID,请问这个是怎么样得到的呢?
在蓝牙中,每个服务和服务属性都唯一地由"全球唯一标识符" (UUID)来校验。正如它的名字所暗示的,每一个这样的标识符都要在时空上保证唯一。UUID类可表现为短整形(16或32位)和长整形(128 位)UUID。他提供了分别利用String和16位或32位数值来创建类的构造函数,提供了一个可以比较两个UUID(如果两个都是128位)的方法,还有一个可以转换一个UUID为一个字符串的方法。UUID实例是不可改变的(immutable),只有被UUID标示的服务可以被发现。
在Linux下你用一个命令uuidgen -t可以生成一个UUID值;
在Windows下则执行命令uuidgen
UUID看起来就像如下的这个形式:2d266186-01fb-47c2-8d9f-10b8ec891363。当使用生成的UUID去创建一个 UUID对象,你可以去掉连字符。
如果出现 Service discovery failed 这样的错误: 是因为当两个UUID想同时建立Rfcomm的通道时,我们的选择都是在两个线程中分别实现,但是忽略了一件最重要的事情:同一个时间只能充当一个角色!所以,解决这个问题的方法就是在我们相连接的设备上也安装同样的应用程序,谁先发起连接谁就是客户端。
Demo示例:https://github.com/sheep0704/DuBluetooth
首先来了解一些坑
一、电脑和android手机的蓝牙通信问题:
首先解答几个问题
1.两边的UUID必须是一样的,这是一个服务的唯一标识,而且这个UUID的值必须是
00001101-0000-1000-8000-00805F9B34FB。为什么呢?因为这个是android的API上面说明的,用于普通蓝牙适配器和android手机蓝牙模块连接的,请大家自己看一下android有关bluetooth的API。
2.在连接的时候,如果电脑作为server(一直监听是否有服务连接),android手机作为client(主动和电脑建立连接),则需要在手机端调用这样一行代码:
mmSocket.connect();
其中mmSocket是一个BluetoothSocket类,在这句话之前请确定你已经把手机和电脑进行了配对,而且那些乱七八糟的设置都搞定了。
http://developer.android.com/reference/android/bluetooth/BluetoothDevice.html
翻译:
公共BluetoothSocket createInsecureRfcommSocketToServiceRecord(UUID UUID) 自:API级别10创建一个RFCOMM BluetoothSocket插座准备好开始一个不安全的输出连接到远程设备使用SDP查找的uuid。 通信通道关键我不会有一个身份验证链接。e它将受到中间人攻击。2.1蓝牙设备,关键的联系将被加密,加密是强制性的。 遗留设备(蓝牙2.1之前设备)连接键不会被加密。UsecreateRfcommSocketToServiceRecord(UUID)如果一个加密和身份验证通信信道是理想的。 这是设计用于与listenUsingInsecureRfcommWithServiceRecord(字符串,UUID)对等蓝牙应用程序。 使用connect()来初始化输出连接。这也将执行SDP查找给定的uuid,以确定哪些通道连接。 远程设备将认证和通信套接字将被加密。 提示:如果你是连接到蓝牙串口板然后试着用著名的SPP UUID 0000 - 0000 - 1000 - 8000 - 1000 - f9b34fb。 但是如果你是连接到一个Android同行请生成自己的独特的UUID。
二、实战坑
开发手机与带蓝牙的智能设备(蓝牙血压计、血糖仪、手环等)设备对接的APP。也就是说,在设备端没有什么可以操作的,手机负责发起数据传输。
1.蓝牙连接,不需要配对
由于被曾经使用蓝牙的思路所误导,一直以为使用蓝牙是必须一个配对的过程的。实际上不是这样。搜索到设备以后,直接去connect
设备而不去配对,目前在我这里是没问题的,搜索到设备以后,可以直接用一下代码进行连接:
final String SPP_UUID = "00001101-0000-1000-8000-00805F9B34FB"; UUID uuid = UUID.fromString(SPP_UUID); BluetoothSocket socket; socket = device.createInsecureRfcommSocketToServiceRecord(uuid); adapter.cancelDiscovery(); socket.connect(); 这里的UUID是比较好用的一个,设备都可以识别。
2.startDiscovey
有可能启动失败
- 一般程序中会有两步:
开启蓝牙
、开始寻找设备
。之前我写的代码是用户按下按钮就直接顺序执行这两步,导致的结果就是经常性搜索失败。仔细看了一下API,发现adapter.startDiscovery()
函数是有一个boolean返回值的,也就是说如果启动失败就返回false。这就解释了为什么会启动失败了:顺序执行了开启蓝牙
-寻找设备
的步骤,但是由于蓝牙还没有完全打开,就开始寻找设备,导致寻找失败。于是最后我把代码改成了这样,问题解决:adapter = BluetoothAdapter.getDefaultAdapter(); if (adapter == null) { // 设备不支持蓝牙 } // 打开蓝牙 if (!adapter.isEnabled()) { adapter.enable(); adapter.cancelDiscovery(); } // 寻找蓝牙设备,android会将查找到的设备以广播形式发出去 while (!adapter.startDiscovery()){ Log.e("BlueTooth", "尝试失败"); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } }
- 接收数据转换
使用
socket.getInputStream
接收到的数据是字节流,这样的数据是没法分析的。又由于一般来说厂家给的协议都是类似于"FA 22 89 D0"这样的十六进制数据,所以很多情况需要一个byte转十六进制String的函数:public static String bytesToHex(byte[] bytes) { char[] hexChars = new char[bytes.length * 2]; for ( int j = 0; j < bytes.length; j++ ) { int v = bytes[j] & 0xFF; hexChars[j * 2] = hexArray[v >>> 4]; hexChars[j * 2 + 1] = hexArray[v & 0x0F]; } return new String(hexChars);
}
介绍下安卓蓝牙小知识
*Bluetooth结构
1、JAVA层
frameworks/base/core/java/android/bluetooth/
包含了bluetooth的JAVA类。
2、JNI层
frameworks/base/core/jni/android_bluetooth_开头的文件
定义了bluez通过JNI到上层的接口。
frameworks/base/core/jni/android_server_bluetoothservice.cpp
调用硬件适配层的接口system/bluetooth/bluedroid/bluetooth.c
3、bluez库
external/bluez/
这是bluez用户空间的库,开源的bluetooth代码,包括很多协议,生成libbluetooth.so。
4、硬件适配层
system/bluetooth/bluedroid/bluetooth.c
包含了对硬件操作的接口
system/bluetooth/data/*
一些配置文件,复制到/etc/bluetooth/。
还有其他一些测试代码和工具。
*简略介绍Bluetooth开发使用到的类
1、BluetoothAdapter,蓝牙适配器,可判断蓝牙设备是否可用等功能。
常用方法列举如下:
cancelDiscovery() ,取消搜索过程,在进行蓝牙设备搜索时,如果调用该方法会停止搜索。(搜索过程会持续12秒)
disable()关闭蓝牙,也就是我们常说的禁用蓝牙。
enable()打开蓝牙,这个方法打开蓝牙但不会弹出提示,正常流程操作下,我们会让系统提示用户是否打开蓝牙设备。如下两行代码可轻松搞定。
Intent enabler=new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enabler,reCode);//同startActivity(enabler);(在主Activity启动一个二级Activity,reCode一般等于3,一定记得要在AndroidManifest.xml里面添加蓝牙权限)
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
这个类有几个隐藏方法,涉及到蓝牙的自动配对,setPin,createBond,cancelPairingUserInput,等方法(需要通过java的反射,调用这几个隐藏方法)
3.BluetoothServerSocket如果去除了Bluetooth相信大家一定再熟悉不过了,既然是Socket,方法就应该都差不多,
这个类一种只有三个方法
两个重载的accept(),accept(inttimeout)两者的区别在于后面的方法指定了过时时间,需要注意的是,执行这两个方法的时候,直到接收到了客户端的请求(或是过期之后),都 会阻塞线程,应该放在新线程里运行!
还有一点需要注意的是,这两个方法都返回一个BluetoothSocket,最后的连接也是服务器端与客户端的两个BluetoothSocket的连接
close()关闭!
4.BluetoothSocket,跟BluetoothServerSocket相对,是客户端
一共5个方法,不出意外,都会用到
close(),关闭
connect()连接
getInptuStream()获取输入流
getOutputStream()获取输出流
getRemoteDevice()获取远程设备,这里指的是获取bluetoothSocket指定连接的那个远程蓝牙设备
*蓝牙设备的发现、查找。
1.基于安全性考虑,设置开启可被搜索后,Android系统会默认给出120秒的时间,其他设备能在这120秒内搜索到它。
Intent enable = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
startActivityForResult(enalbe,REQUEST_DISCOVERABLE);
2.搜索蓝牙设备
BluetoothAdapter _bluetooth = BluetoothAdapter.getDefaultAdapter();
_bluetooth.startDiscovery();
3.关闭蓝牙设备
BluetoothAdapter _bluetooth = BluetoothAdapter.getDefaultAdapter();
_bluetooth.disable();
4.创建蓝牙客户端
BluetoothSocket socket = device.createRfcommSocketToServiceRecord(UUID.fromString(UUID号));
socket.connect();
4.创建蓝牙服务端
BluetoothServerSocket _serverSocket = _bluetooth.listenUsingRfcommWithServiceRecord(服务端名称,UUID.fromeString(UUID号));
BluetoothSocket socket = _serverSocket.accept();
InputStream inputStream = socket.getInputStream();
*相关代码的实现
注:tcp_ip模块需要在系统setting模块中,完成蓝牙的配对,只有配对成功的,才能进行socket通信(具体如何配对和如何自动配对,将在bluetoot3或者4中进行讲解)
AndridManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.thecaseforbluetooth" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="15" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> <uses-permission android:name="android.permission.BLUETOOTH" /> <application android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name=".BluetoothActivity" android:label="@string/title_activity_bluetooth" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/LinearLayout1" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/btnSearch" android:text="查找设备" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/btnExit" android:text="退出应用" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/btnDis" android:text="设置可被发现" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/btnserver" android:text="启动服务端" /> <ToggleButton android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/tbtnSwitch" android:text="开/关 蓝牙设备" /> <ListView android:layout_width="fill_parent" android:layout_height="wrap_content" android:id="@+id/lvDevices" /> </LinearLayout>
BluetoothActivity.java
package com.example.thecaseforbluetooth; import java.util.ArrayList; import java.util.Set; import android.app.Activity; 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.os.Bundle; import android.view.Menu; import android.view.View; import android.view.View.OnClickListener; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.ListView; import android.widget.Toast; import android.widget.ToggleButton; public class BluetoothActivity extends Activity { public Button searchBtn;//搜索蓝牙设备 public Button exitBtn;//退出应用 public Button discoverBtn;//设置可被发现 public ToggleButton openBtn;//开关蓝牙设备 public Button serverbtn; public ListView listView;//蓝牙设备清单 public ArrayAdapter<String> adapter; public ArrayList<String> list =new ArrayList<String>(); private BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); Set<BluetoothDevice> bondDevices ; public Context context ; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); searchBtn = (Button)findViewById(R.id.btnSearch); exitBtn = (Button)findViewById(R.id.btnExit); discoverBtn = (Button)findViewById(R.id.btnDis); openBtn = (ToggleButton)findViewById(R.id.tbtnSwitch); serverbtn = (Button)findViewById(R.id.btnserver); listView = (ListView)findViewById(R.id.lvDevices); context = getApplicationContext(); adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1,list); listView.setAdapter(adapter); openBtn.setChecked(false); //注册广播接收信号 IntentFilter intent = new IntentFilter(); intent.addAction(BluetoothDevice.ACTION_FOUND);// 用BroadcastReceiver来取得搜索结果 intent.addAction(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED); //每当扫描模式变化的时候,应用程序可以为通过ACTION_SCAN_MODE_CHANGED值来监听全局的消息通知。比如,当设备停止被搜寻以后,该消息可以被系统通知給应用程序。 intent.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); //每当蓝牙模块被打开或者关闭,应用程序可以为通过ACTION_STATE_CHANGED值来监听全局的消息通知。 registerReceiver(searchReceiver, intent); //显示已配对设备以及搜索未配对设备 searchBtn.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub if(bluetoothAdapter.isDiscovering()){ bluetoothAdapter.cancelDiscovery(); } list.clear(); bondDevices = bluetoothAdapter.getBondedDevices(); for(BluetoothDevice device : bondDevices) { String str = " 已配对完成 " + device.getName() +" " + device.getAddress(); list.add(str); adapter.notifyDataSetChanged(); } bluetoothAdapter.startDiscovery(); } }); //退出应用 exitBtn.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub BluetoothActivity.this.finish(); } }); //设置蓝牙设备可发现 discoverBtn.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub Intent discoverIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE); discoverIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300); startActivity(discoverIntent); } }); //开关蓝牙设备 openBtn.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub if(openBtn.isChecked() == true){ bluetoothAdapter.disable(); } else if(openBtn.isChecked() == false){ bluetoothAdapter.enable(); } } }); //作为服务端开启 serverbtn.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub ServerThread serverThread = new ServerThread(bluetoothAdapter, context); Toast.makeText(context, "server 端启动", 5000).show(); serverThread.start(); } }); listView.setOnItemClickListener(new ItemClickListener()); } @Override public void onStart() { super.onStart(); // If BT is not on, request that it be enabled. if(bluetoothAdapter == null){ Toast.makeText(context, "蓝牙设备不可用", 5000).show(); } if (!bluetoothAdapter.isEnabled()) { Intent enableIntent = new Intent( BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivityForResult(enableIntent, 3); } } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.main, menu); return true; } private final BroadcastReceiver searchReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { // TODO Auto-generated method stub String action = intent.getAction(); BluetoothDevice device = null; if(BluetoothDevice.ACTION_FOUND.equals(action)){ device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); if (device.getBondState() == BluetoothDevice.BOND_NONE) { Toast.makeText(context, device.getName()+"", 5000).show(); String str = " 未配对完成 " + device.getName() +" " + device.getAddress(); if (list.indexOf(str) == -1)// 防止重复添加 list.add(str); } adapter.notifyDataSetChanged(); } } }; public class ItemClickListener implements OnItemClickListener { @Override public void onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3) { // TODO Auto-generated method stub if(bluetoothAdapter.isDiscovering()) bluetoothAdapter.cancelDiscovery(); String str = list.get(arg2); String address = str.substring(str.length() - 17); BluetoothDevice btDev = bluetoothAdapter.getRemoteDevice(address); ClientThread clientThread = new ClientThread(btDev, context); clientThread.start(); }} }
Bluetoothprotocol.java
package com.example.thecaseforbluetooth; public interface Bluetoothprotocol { public static final String PROTOCOL_SCHEME_L2CAP = "btl2cap"; public static final String PROTOCOL_SCHEME_RFCOMM = "btspp"; public static final String PROTOCOL_SCHEME_BT_OBEX = "btgoep"; public static final String PROTOCOL_SCHEME_TCP_OBEX = "tcpobex"; }
ServerThread.java
package com.example.thecaseforbluetooth; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.UUID; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothServerSocket; import android.bluetooth.BluetoothSocket; import android.content.Context; import android.os.Message; import android.util.Log; import android.widget.Toast; public class ServerThread extends Thread { public BluetoothServerSocket mserverSocket; public BluetoothAdapter bluetoothAdapter; public BluetoothSocket socket; public Context context; public ServerThread(BluetoothAdapter bluetoothAdapter,Context context) { this.bluetoothAdapter = bluetoothAdapter; this.context = context; } public void run() { try { /* 创建一个蓝牙服务器 * 参数分别:服务器名称、UUID */ mserverSocket = bluetoothAdapter.listenUsingRfcommWithServiceRecord(Bluetoothprotocol.PROTOCOL_SCHEME_RFCOMM, UUID.fromString("00001101-0000-1000-8000-00805F9B34FB")); // /* 接受客户端的连接请求 */ socket = mserverSocket.accept(); //下面代码作者偷懒,读写另外起一个线程最好 //接收数据 byte[] buffer = new byte[1024]; int bytes; InputStream mmInStream = null; try { mmInStream = socket.getInputStream(); } catch (IOException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } System.out.println("zhoulc server"); while(true){ if( (bytes = mmInStream.read(buffer)) > 0 ) { byte[] buf_data = new byte[bytes]; for(int i=0; i<bytes; i++) { buf_data[i] = buffer[i]; } String s = new String(buf_data); System.out.println(s+"zhoulc server is in"); } } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
ClientThread.java
package com.example.thecaseforbluetooth; import java.io.IOException; import java.io.OutputStream; import java.util.UUID; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothSocket; import android.content.Context; import android.widget.Toast; public class ClientThread extends Thread { public BluetoothSocket socket; public BluetoothDevice device; public Context context; public ClientThread(BluetoothDevice device,Context context){ this.device = device; this.context = context; } public void run() { try { //创建一个Socket连接:只需要服务器在注册时的UUID号 socket = device.createRfcommSocketToServiceRecord(UUID.fromString("00001101-0000-1000-8000-00805F9B34FB")); //连接 socket.connect(); //下面代码作者偷懒,读写另外起一个线程最好 //发送数据 if (socket == null) { Toast.makeText(context, "链接失败", 5000).show(); return; } System.out.println("zhoulc client"); while(true){ try { System.out.println("zhoulc client is in"); String msg = "hello everybody I am client"; OutputStream os = socket.getOutputStream(); os.write(msg.getBytes()); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } catch (IOException e) { e.printStackTrace(); } } }
蓝牙设备之间自动配对
1、蓝牙设备之间自动配对,需要两个设备都安装进行配对的apk(网上好多自动配对的帖子都没有说明情况)
2、在自动匹配的时候想通过反射调用BluetoothDevice的setPin、createBond、cancelPairingUserInput实现设置密钥、配对请求创建、取消密钥信息输入等。
1)createBond()创建,最终会调到源码的BluetoothService的createBond(String address)方法,通过对源码浅显的了解,createBond主要是写入匹配密钥(BluetoothService的writeDockPin())以及进入jni注册回调函数onCreatePairedDeviceResult观察匹配结果
比如: // Pins did not match, or remote device did not respond to pin
// request in time
// We rejected pairing, or the remote side rejected pairing. This
// happens if either side presses 'cancel' at the pairing dialog.
// Not sure if this happens
// Other device is not responding at all
// already bonded
等,在jni中创建了进行匹配的device("CreatePairedDevice"),这时bluetooth会发送一个ACTION_PAIRING_REQUEST的广播,只有当前会出现密钥框的蓝牙设备收到。写完密钥之后,发送广播给另外一个蓝牙设备接收,然后打开密钥输入框进行匹配。
2)setPin()设置密钥,通过查看setting源码,发现在确认输入密钥之后会调用setPin()(如果点取消,就会调用cancelPairingUserInput,取消密钥框),setPin具体通过D-BUS做了什么没有去深究,但是在调用setPin的时候会remove掉一个map里面的键值对(address:int),也就是我们在调用setPin之后如果再去调用onCreatePairedDeviceResult,则该方法一定返回false,并且出现下面的打印提示:cancelUserInputNative(B8:FF:FE:55:EF:D6) called but no native data available, ignoring. Maybe the PasskeyAgent Request was already cancelled by the remote or by bluez.(因为该方法也会remove掉一个键值对)
3)cancelPairingUserInput()取消用户输入密钥框,个人觉得一般情况下不要和setPin(setPasskey、setPairingConfirmation、setRemoteOutOfBandData)一起用,这几个方法都会remove掉map里面的key:value(也就是互斥的)。
3、蓝牙耳机、手柄等一些无法手动配置的设备是如何完成自动配对的。
在源码里面有一个自动配对的方法,也就是把pin值自动设为“0000”
/*package*/
synchronized boolean attemptAutoPair(String address) { if (!mBondState.hasAutoPairingFailed(address) && !mBondState.isAutoPairingBlacklisted(address)) { mBondState.attempt(address); setPin(address, BluetoothDevice.convertPinToBytes("0000")); return true; } return false; }
该方法是在底层回调到java层的onRequestPinCode方法时被调用,首先 Check if its a dock(正常输入的密钥,走正常配对方式,双方输入匹配值),然后再 try 0000 once if the device looks dumb(涉及到Device.AUDIO_VIDEO相关部分如:耳机,免提等进入自动匹配模式)进行自动配对。
言归正传,虽然个人觉得自动配对需要双方乃至多方蓝牙设备都需要装上实现自动配对的apk,已经失去了自动配对的意义,但有可能还是会派上用场。下面我们看看现实情况的自动配对是什么样的吧。
由于BluetoothDevice配对的方法都是hide的,所以我们需要通过反射调用被隐藏的方法,现在基本都是通用的工具类型了,网上模式基本一样。
ClsUtils.java
package cn.bluetooth; import java.lang.reflect.Field; import java.lang.reflect.Method; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.util.Log; public class ClsUtils { public static BluetoothDevice remoteDevice=null; /** * 与设备配对 参考源码:platform/packages/apps/Settings.git * /Settings/src/com/android/settings/bluetooth/CachedBluetoothDevice.java */ @SuppressWarnings("unchecked") static public boolean createBond(@SuppressWarnings("rawtypes") Class btClass, BluetoothDevice btDevice) throws Exception { Method createBondMethod = btClass.getMethod("createBond"); Boolean returnValue = (Boolean) createBondMethod.invoke(btDevice); return returnValue.booleanValue(); } /** * 与设备解除配对 参考源码:platform/packages/apps/Settings.git * /Settings/src/com/android/settings/bluetooth/CachedBluetoothDevice.java */ @SuppressWarnings("unchecked") static public boolean removeBond(Class btClass, BluetoothDevice btDevice) throws Exception { Method removeBondMethod = btClass.getMethod("removeBond"); Boolean returnValue = (Boolean) removeBondMethod.invoke(btDevice); return returnValue.booleanValue(); } @SuppressWarnings("unchecked") static public boolean setPin(Class btClass, BluetoothDevice btDevice, String str) throws Exception { try { Method removeBondMethod = btClass.getDeclaredMethod("setPin", new Class[] {byte[].class}); Boolean returnValue = (Boolean) removeBondMethod.invoke(btDevice, new Object[] {str.getBytes()}); Log.d("returnValue", "setPin is success " +btDevice.getAddress()+ returnValue.booleanValue()); } catch (SecurityException e) { // throw new RuntimeException(e.getMessage()); e.printStackTrace(); } catch (IllegalArgumentException e) { // throw new RuntimeException(e.getMessage()); e.printStackTrace(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } return true; } // 取消用户输入 @SuppressWarnings("unchecked") static public boolean cancelPairingUserInput(Class btClass, BluetoothDevice device) throws Exception { Method createBondMethod = btClass.getMethod("cancelPairingUserInput"); // cancelBondProcess() Boolean returnValue = (Boolean) createBondMethod.invoke(device); Log.d("returnValue", "cancelPairingUserInput is success " + returnValue.booleanValue()); return returnValue.booleanValue(); } // 取消配对 @SuppressWarnings("unchecked") static public boolean cancelBondProcess(Class btClass, BluetoothDevice device) throws Exception { Method createBondMethod = btClass.getMethod("cancelBondProcess"); Boolean returnValue = (Boolean) createBondMethod.invoke(device); return returnValue.booleanValue(); } /** * * @param clsShow */ @SuppressWarnings("unchecked") static public void printAllInform(Class clsShow) { try { // 取得所有方法 Method[] hideMethod = clsShow.getMethods(); int i = 0; for (; i < hideMethod.length; i++) { //Log.e("method name", hideMethod.getName() + ";and the i is:" // + i); } // 取得所有常量 Field[] allFields = clsShow.getFields(); for (i = 0; i < allFields.length; i++) { //Log.e("Field name", allFields.getName()); } } catch (SecurityException e) { // throw new RuntimeException(e.getMessage()); e.printStackTrace(); } catch (IllegalArgumentException e) { // throw new RuntimeException(e.getMessage()); e.printStackTrace(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
Bluetooth1.java 主activity,所有界面操作实现地方
package cn.bluetooth; import java.io.IOException; import java.util.ArrayList; import java.util.List; import android.app.Activity; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothSocket; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.Bundle; import android.util.Log; import android.view.Menu; import android.view.View; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.ListView; import android.widget.Toast; import android.widget.ToggleButton; public class Bluetooth1 extends Activity { /** Called when the activity is first created. */ Button btnSearch, btnDis, btnExit; ToggleButton tbtnSwitch; ListView lvBTDevices; ArrayAdapter<String> adtDevices; List<String> lstDevices = new ArrayList<String>(); BluetoothAdapter btAdapt; public static BluetoothSocket btSocket; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); // Button 设置 btnSearch = (Button) this.findViewById(R.id.btnSearch); btnSearch.setOnClickListener(new ClickEvent()); btnExit = (Button) this.findViewById(R.id.btnExit); btnExit.setOnClickListener(new ClickEvent()); btnDis = (Button) this.findViewById(R.id.btnDis); btnDis.setOnClickListener(new ClickEvent()); // ToogleButton设置 tbtnSwitch = (ToggleButton) this.findViewById(R.id.tbtnSwitch); tbtnSwitch.setOnClickListener(new ClickEvent()); // ListView及其数据源 适配器 lvBTDevices = (ListView) this.findViewById(R.id.lvDevices); adtDevices = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, lstDevices); lvBTDevices.setAdapter(adtDevices); lvBTDevices.setOnItemClickListener(new ItemClickEvent()); btAdapt = BluetoothAdapter.getDefaultAdapter();// 初始化本机蓝牙功能 // ======================================================== // modified by wiley /* * if (btAdapt.getState() == BluetoothAdapter.STATE_OFF)// 读取蓝牙状态并显示 * tbtnSwitch.setChecked(false); else if (btAdapt.getState() == * BluetoothAdapter.STATE_ON) tbtnSwitch.setChecked(true); */ if (btAdapt.isEnabled()) { tbtnSwitch.setChecked(false); } else { tbtnSwitch.setChecked(true); } // ============================================================ // 注册Receiver来获取蓝牙设备相关的结果 IntentFilter intent = new IntentFilter(); intent.addAction(BluetoothDevice.ACTION_FOUND);// 用BroadcastReceiver来取得搜索结果 intent.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED); intent.addAction(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED); intent.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); registerReceiver(searchDevices, intent); } private final BroadcastReceiver searchDevices = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); Bundle b = intent.getExtras(); Object[] lstName = b.keySet().toArray(); // 显示所有收到的消息及其细节 for (int i = 0; i < lstName.length; i++) { String keyName = lstName.toString(); Log.e(keyName, String.valueOf(b.get(keyName))); } BluetoothDevice device = null; // 搜索设备时,取得设备的MAC地址 if (BluetoothDevice.ACTION_FOUND.equals(action)) { device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); if (device.getBondState() == BluetoothDevice.BOND_NONE) { String str = " 未配对|" + device.getName() + "|" + device.getAddress(); if (lstDevices.indexOf(str) == -1)// 防止重复添加 lstDevices.add(str); // 获取设备名称和mac地址 adtDevices.notifyDataSetChanged(); } }else if(BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)){ device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); switch (device.getBondState()) { case BluetoothDevice.BOND_BONDING: Log.d("BlueToothTestActivity", "正在配对......"); break; case BluetoothDevice.BOND_BONDED: Log.d("BlueToothTestActivity", "完成配对"); //connect(device);//连接设备 break; case BluetoothDevice.BOND_NONE: Log.d("BlueToothTestActivity", "取消配对"); default: break; } } } }; @Override protected void onDestroy() { this.unregisterReceiver(searchDevices); super.onDestroy(); android.os.Process.killProcess(android.os.Process.myPid()); } class ItemClickEvent implements AdapterView.OnItemClickListener { @Override public void onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3) { if(btAdapt.isDiscovering())btAdapt.cancelDiscovery(); String str = lstDevices.get(arg2); String[] values = str.split("\\|"); String address = values[2]; Log.e("address", values[2]); BluetoothDevice btDev = btAdapt.getRemoteDevice(address); try { Boolean returnValue = false; if (btDev.getBondState() == BluetoothDevice.BOND_NONE) { Toast.makeText(Bluetooth1.this, "远程设备发送蓝牙配对请求", 5000).show(); //这里只需要createBond就行了 ClsUtils.createBond(btDev.getClass(), btDev); }else if(btDev.getBondState() == BluetoothDevice.BOND_BONDED){ Toast.makeText(Bluetooth1.this, btDev.getBondState()+" ....正在连接..", 1000).show(); } } catch (Exception e) { e.printStackTrace(); } } } class ClickEvent implements View.OnClickListener { @Override public void onClick(View v) { if (v == btnSearch){ // 搜索蓝牙设备,在BroadcastReceiver显示结果 if (btAdapt.getState() == BluetoothAdapter.STATE_OFF) {// 如果蓝牙还没开启 Toast.makeText(Bluetooth1.this, "请先打开蓝牙", 1000) .show(); return; } if (btAdapt.isDiscovering()) btAdapt.cancelDiscovery(); lstDevices.clear(); Object[] lstDevice = btAdapt.getBondedDevices().toArray(); for (int i = 0; i < lstDevice.length; i++) { BluetoothDevice device = (BluetoothDevice) lstDevice[i]; String str = " 已配对|" + device.getName() + "|" + device.getAddress(); lstDevices.add(str); // 获取设备名称和mac地址 adtDevices.notifyDataSetChanged(); } setTitle("本机:" + btAdapt.getAddress()); btAdapt.startDiscovery(); } else if (v == tbtnSwitch) {// 本机蓝牙启动/关闭 if (tbtnSwitch.isChecked() == false) btAdapt.enable(); else if (tbtnSwitch.isChecked() == true) btAdapt.disable(); } else if (v == btnDis){// 本机可以被搜索 Intent discoverableIntent = new Intent( BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE); discoverableIntent.putExtra( BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300); startActivity(discoverableIntent); } else if (v == btnExit) { try { if (btSocket != null) btSocket.close(); } catch (IOException e) { e.printStackTrace(); } Bluetooth1.this.finish(); } } } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.main, menu); return true; } }
PairingRequest.java (重要部分,自动配对主要是这个部分完成,activity只是创建了一个配对请求)
package cn.bluetooth; import android.bluetooth.BluetoothDevice; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.widget.Toast; public class PairingRequest extends BroadcastReceiver { String strPsw = "0000"; final String ACTION_PAIRING_REQUEST = "android.bluetooth.device.action.PAIRING_REQUEST"; static BluetoothDevice remoteDevice = null; @Override public void onReceive(Context context, Intent intent) { if (intent.getAction().equals(ACTION_PAIRING_REQUEST)) { BluetoothDevice device = intent .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); if (device.getBondState() != BluetoothDevice.BOND_BONDED) { try { ClsUtils.setPin(device.getClass(), device, strPsw); // 手机和蓝牙采集器配对 // ClsUtils.cancelPairingUserInput(device.getClass(), // device); //一般调用不成功,前言里面讲解过了 Toast.makeText(context, "配对信息" + device.getName(), 5000) .show(); } catch (Exception e) { // TODO Auto-generated catch block Toast.makeText(context, "请求连接错误...", 1000).show(); } } // */ // pair(device.getAddress(),strPsw); } } }
AndroidManifest.xml 启动activity,接收广播
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="cn.bluetooth" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="15" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> <uses-permission android:name="android.permission.BLUETOOTH" /> <application android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name=".Bluetooth1" android:label="@string/title_activity_bluetooth1" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <receiver android:name=".PairingRequest"> <intent-filter> <action android:name="android.bluetooth.device.action.PAIRING_REQUEST" /> </intent-filter> </receiver> </application> </manifest>
main.xml 布局
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/LinearLayout1" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/btnSearch" android:text="btnSearch" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/btnExit" android:text="btnExit" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/btnDis" android:text="btnDis" /> <ToggleButton android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/tbtnSwitch" android:text="tbtnSwitch" /> <ListView android:layout_width="fill_parent" android:layout_height="wrap_content" android:id="@+id/lvDevices" /> </LinearLayout>
代码完成,后期上传demo。
此博文持续更新,建议原地址查看 ,请注明转载地址:http://blog.csdn.net/djy1992/article/details/10144843
|| 版权声明:本文为博主杜锦阳原创文章,转载请注明出处。