时隔半年时间,又遇到了蓝牙开发了,之前是蓝牙连接打印相关方面的,这次需要蓝牙配对数据传输,折腾过去折腾过来,也就那么回事,下定决心系统的梳理这块的知识
Android开发之蓝牙通信(一)
Android开发之蓝牙通信(二)
Android开发之蓝牙通信(三)
为了在您的应用程序中使用蓝牙功能,您必须声明蓝牙权限蓝牙。您需要此权限来执行任何蓝牙通信,如请求一个连接、接受一个连接和传输数据。如果你想让你的应用启动设备发现或操纵蓝牙设置,你也必须申报bluetooth_admin许可。大多数应用程序都需要此权限,仅用于发现本地蓝牙设备的能力。此权限授予的其他权限不应被使用,除非应用程序是一个“电源管理器”,将修改用户请求后的蓝牙设置。注意:如果你使用bluetooth_admin许可,那么你也必须有蓝牙许可。
... >
"android.permission.BLUETOOTH" />
...
在应用程序可以通过蓝牙进行通信之前,您需要验证是否支持蓝牙设备上的支持,如果是这样的话,请确保它已启用。如果不支持蓝牙,那么您应该优雅地禁用任何蓝牙功能。如果蓝牙是支持的,但禁用,那么你可以要求用户启用蓝牙,而不会离开你的应用程序。这个设置是在两个步骤来完成,使用蓝牙适配器。
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (mBluetoothAdapter == null) {
// 设备不支持蓝牙
}
下一步,您需要确保蓝牙功能已启用。isenabled()检查蓝牙目前是否启用。如果此方法返回错误,则禁用蓝牙。要求蓝牙启用,startactivityforresult()与action_request_enable动作意图。这将发出一个请求,使蓝牙通过系统设置(不阻止您的应用程序)。例如:
if (!mBluetoothAdapter.isEnabled()) {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}
使用蓝牙适配器 BluetoothAdapter,您可以通过查询名单发现远程蓝牙设备配对。
设备发现是一个扫描程序,搜索本地区域的蓝牙功能的设备,然后要求一些信息(这是有时被称为“发现”,“查询”或“扫描”)然而,局域网内的蓝牙设备将响应发现请求只有在能够被发现。如果一个设备可以被发现,它会响应发现请求并共享一些信息,比如设备名称、类别,并以其独特的MAC地址。使用此信息,执行发现的设备可以选择启动一个连接到所发现的设备。
一旦一个连接是第一次与一个远程设备,一个配对请求自动提交给用户。当一个设备配对,该设备的基本信息(如设备名称,类和地址)被保存,并可以读取使用蓝牙耳机。使用一个远程设备的已知的mac地址,可以在任何时间启动一个连接,而不进行发现(假设设备处于范围内)。
记住,有一个配对和被连接之间的区别。要配对意味着两个设备都知道彼此的存在,有一个共享的链路密钥,可用于认证,并能够建立一个加密的相互连接。要连接意味着目前的设备共享一个RFCOMM通道可以互相传递数据。目前安卓蓝牙API的设备需要被配对在RFCOMM连接可以建立。(当您启动与蓝牙接口的加密连接时,自动执行配对)。
getBondedDevices获取与本机蓝牙所有绑定的远程蓝牙信息,以BluetoothDevice类实例返回,如果蓝牙开启,该函数会返回一个空集合
Set pairedDevices = mBluetoothAdapter.getBondedDevices();
// If there are paired devices
if (pairedDevices.size() > 0) {
// Loop through paired devices
for (BluetoothDevice device : pairedDevices) {
// Add the name and address to an array adapter to show in a ListView
mArrayAdapter.add(device.getName() + "\n" + device.getAddress());
}
}
开始发现设备,调用startdiscovery()。该进程是异步的,该方法将立即返回一个布尔值,指示是否已成功启动。发现过程通常涉及一个约12秒的查询扫描,然后通过一个页面扫描每个发现的设备来检索其蓝牙名称。
应用程序必须注册ACTION_FOUND意图接收有关每个设备发现BroadcastReceiver。我们通过注册广播接收器接受广播,来处理一些事情:
// Create a BroadcastReceiver for ACTION_FOUND
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
// When discovery finds a device
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
// Get the BluetoothDevice object from the Intent
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
// Add the name and address to an array adapter to show in a ListView
mArrayAdapter.add(device.getName() + "\n" + device.getAddress());
}
}
};
// Register the BroadcastReceiver
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver(mReceiver, filter); // Don't forget to unregister during onDestroy
警告:执行设备发现是蓝牙适配器的一个沉重的过程,并会消耗大量的资源。一旦你找到了一个设备连接,就调用canceldiscovery()取消之前的扫描。
发现设备的Intent意图默认扫描时间120秒,一个应用程序可以设置最大持续时间为3600秒,0值表示设备总是发现。任何值低于0或高于3600自动设置为120秒)(可以这样理解:蓝牙设备运行在多少时间内可以被其他用于扫描到并连接)
Intent discoverableIntent = new
Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
startActivity(discoverableIntent);
关于蓝牙与服务端、客户端的链接涉及到BluetoothServerSocket 、BluetoothSocket ,没啥好说的,稍后通过官方simple一观即可。
相比较上面这块,权限必须有以下两项(4.3的低功耗蓝牙的扫描startLeScan方法必须具备BLUETOOTH_ADMIN权限)
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
如果你确定你的应用程序是4.3以上的手机设备安装,支持低功耗蓝牙,可以在清单文件声明如下
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
如果你不确定你的app使用的设备是否支持低功耗蓝牙,但又想让支持的设备使用低功耗蓝牙,那么你可以这样做
"android.hardware.bluetooth_le" android:required="false"/>
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show();
finish();
}
或BluetoothAdapter可以通过BluetoothManager得到,因为在BluetoothManager被装载时构造函数调用了BluetoothAdapter.getDefualtA..初始化了adapter对象
// Initializes Bluetooth adapter.
final BluetoothManager bluetoothManager =
(BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bluetoothManager.getAdapter();
public static synchronized BluetoothAdapter getDefaultAdapter() {
if (sAdapter == null) {
IBinder b = ServiceManager.getService(BLUETOOTH_MANAGER_SERVICE);
if (b != null) {
IBluetoothManager managerService = IBluetoothManager.Stub.asInterface(b);
sAdapter = new BluetoothAdapter(managerService);
} else {
Log.e(TAG, "Bluetooth binder is null");
}
}
return sAdapter;
}
蓝牙设备的扫描也发生了变化,通过adapter.startScan stopScan控制扫描的开关,扫描的时间可以通过postDelayed设定扫描时间
/** * Activity for scanning and displaying available BLE devices. */
public class DeviceScanActivity extends ListActivity {
private BluetoothAdapter mBluetoothAdapter;
private boolean mScanning;
private Handler mHandler;
// Stops scanning after 10 seconds.
private static final long SCAN_PERIOD = 10000;
...
private void scanLeDevice(final boolean enable) {
if (enable) {
// Stops scanning after a pre-defined scan period.
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
mScanning = false;
mBluetoothAdapter.stopLeScan(mLeScanCallback);
}
}, SCAN_PERIOD);
mScanning = true;
mBluetoothAdapter.startLeScan(mLeScanCallback);
} else {
mScanning = false;
mBluetoothAdapter.stopLeScan(mLeScanCallback);
}
...
}
...
}
通过LeScanCallback 回调接口的实现,来处理返回结果刷新UI等方面功能的实现。
private LeDeviceListAdapter mLeDeviceListAdapter;
...
// Device scan callback.
private BluetoothAdapter.LeScanCallback mLeScanCallback =
new BluetoothAdapter.LeScanCallback() {
@Override
public void onLeScan(final BluetoothDevice device, int rssi,
byte[] scanRecord) {
runOnUiThread(new Runnable() {
@Override
public void run() {
mLeDeviceListAdapter.addDevice(device);
mLeDeviceListAdapter.notifyDataSetChanged();
}
});
}
};
BluetoothGatt BluetoothGattCallback等相关的Gat类都大同小异,根据google官方提供的simple过一遍即可。
蓝牙开发调试如果你想使用adb命令可以参考https://developer.android.com/training/wearables/apps/bt-debugging.html
通过官网找到三个开源项目,但是貌似都不能下载,只能通过github检索google的仓库了
BluetoothAdvertisements
BluetoothLeGatt
BluetoothChat
先看android-BluetoothAdvertisements运行效果图
从BluetoothAdvertisements开源项目首先get到一点,剥离生命周期!相关代码块如下
public class ScannerFragment extends ListFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
setRetainInstance(true);
// 这里传入的不是Activity,而是通过Activity获取Application,让其不再跟随活动Activity的生命周期
mAdapter = new ScanResultAdapter(getActivity().getApplicationContext(),
LayoutInflater.from(getActivity()));
mHandler = new Handler();
}
扫描代码块并没有用BluetoothAdapter.startScan,经过源码查看发现依然是过时方法,simple里面直接通过adapter获取BluetoothLeScanner,调用了scanner的startScan
public void startScanning() {
// .........略............
mScanCallback = new SampleScanCallback();
mBluetoothLeScanner.startScan(buildScanFilters(), buildScanSettings(), mScanCallback);
}
然而你如果想下载完这个开源项目就想运行看效果,伙计,不得不说你是绝对不会成功的,buildScanFilters里面添加了扫描过滤规则,uuid无法匹配你是看不到任何东西的,根据代码提示注释掉下面的过滤条件可以看到所有的蓝牙设备。
private List buildScanFilters() {
List scanFilters = new ArrayList<>();
ScanFilter.Builder builder = new ScanFilter.Builder();
// 注释掉下面的扫描过滤规则,才能扫描到(uuid不匹配没法扫描到的)
// builder.setServiceUuid(Constants.Service_UUID);
//scanFilters.add(builder.build());
return scanFilters;
}
在低功耗模式下执行蓝牙LE扫描。这是默认扫描模式,因为它消耗最小功率
private ScanSettings buildScanSettings() {
ScanSettings.Builder builder = new ScanSettings.Builder();
builder.setScanMode(ScanSettings.SCAN_MODE_LOW_POWER);
return builder.build();
}
扫描的结果回调我们可以根据自己的需求做具现
private class SampleScanCallback extends ScanCallback {
@Override
public void onBatchScanResults(List results) {
super.onBatchScanResults(results);
//批处理扫描结果
for (ScanResult result : results) {
mAdapter.add(result);
}
mAdapter.notifyDataSetChanged();
}
@Override
public void onScanResult(int callbackType, ScanResult result) {
super.onScanResult(callbackType, result);
//扫描到一个立即回调
}
@Override
public void onScanFailed(int errorCode) {
super.onScanFailed(errorCode);
//扫描失败
}
}
扫描开始之前的逻辑判断自行参考simple MainActivity.至于AdvertiserService相关的差不多的就略过啦、
android-BluetoothLeGatt这个simple个人觉得没多少特别之处用到的核心上面都有提到,基于android4.3+版本的蓝牙开发simple,mainfest可以看看,service个人感觉有点乱糟糟的。
再看BluetoothChat,首先需要让蓝牙被感知扫描链接,设置相应的模式和允许被扫描到的时长。
/** * Makes this device discoverable. */
private void ensureDiscoverable() {
if (mBluetoothAdapter.getScanMode() !=
BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
startActivity(discoverableIntent);
}
}
蓝牙适配器getRemoteDevice(address)后进行连接,核心代码参考BluetoothChatService.ConnectThread线程部分。而数据的发送核心代码只需要把发送的string message转换byte[] ,调用ChatServiced的write方法
/** * Write to the ConnectedThread in an unsynchronized manner * * @param out The bytes to write * @see ConnectedThread#write(byte[]) */
public void write(byte[] out) {
// Create temporary object
ConnectedThread r;
// Synchronize a copy of the ConnectedThread
synchronized (this) {
if (mState != STATE_CONNECTED) return;
r = mConnectedThread;
}
// Perform the write unsynchronized
r.write(out);
}
最后提供一个工具类BluetoothHelper.java (这个工具类整理的并不完善,关于扫描和链接兼容都没写进去,具体开发链接参照官方simple的ChartService,LE相关的扫描参照simple吧,兼容主要是api11 、api18LE 、api21)
/** * Created by idea on 2016/7/4. */
public class BluetoothHelper {
private static BluetoothHelper instance;
private BluetoothAdapter mBluetoothAdapter;
private Activity mContext;
/** * 获取BluetoothHelper实例,采用Application剥离Activity生命周期 * @param activity * @return BluetoothHelper */
public static BluetoothHelper getInstance(Activity activity) {
if (instance == null) {
synchronized (BluetoothHelper.class) {
if (instance == null) {
instance = new BluetoothHelper(activity);
}
}
}
return instance;
}
/** * 私有构造函数 * @param activity * @hide */
private BluetoothHelper(Activity activity) {
this.mContext = activity;
mBluetoothAdapter = getAdapter();
}
/*** * 获取BluetoothAdapter * * @return BluetoothAdapter * @hide */
private BluetoothAdapter getAdapter() {
BluetoothAdapter mBluetoothAdapter;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
mBluetoothAdapter = ((BluetoothManager) mContext.getSystemService(Context.BLUETOOTH_SERVICE)).getAdapter();
} else {
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
}
return mBluetoothAdapter;
}
/** * 获取已经存在的adapter实例 * @return */
public BluetoothAdapter getBluetoothAdapter() {
return mBluetoothAdapter;
}
/** * 判断手机手否支持蓝牙通信 * * @return boolean */
public boolean checkSupperBluetooth() {
return mBluetoothAdapter == null;
}
/** * 检查蓝牙状态是否打开可用 * * @return boolean */
public boolean checkBluetoothEnable() {
return mBluetoothAdapter.isEnabled();
}
/** * 如果蓝牙设备支持的情况下,并未打开蓝牙,需要请求蓝牙打开 */
public void requestOpenBluetooth() {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
mContext.startActivityForResult(enableBtIntent, Constants.REQUEST_ENABLE_BT);
}
/** * 判断我们请求打开蓝牙的结果 * * @param requestCode * @param resultCode * @param onOpenBluetoothLisenter */
public void performResult(int requestCode, int resultCode, OnBluetoothListener.OnOpenBluetoothLisenter onOpenBluetoothLisenter) {
switch (requestCode) {
case Constants.REQUEST_ENABLE_BT:
if (onOpenBluetoothLisenter != null) {
if (resultCode == Activity.RESULT_OK) {
if (checkBluetoothEnable()) {
onOpenBluetoothLisenter.onSuccess();
} else {
onOpenBluetoothLisenter.onFail("蓝牙不可用,会影响到相关功能的使用");
}
} else {
onOpenBluetoothLisenter.onFail("Error");
}
}
}
}
/*** * 是否支持蓝牙低功耗广播(4.3+) * * @return boolean * @hide */
@Deprecated
public boolean isSupperBluetoothLE() {
return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE);
}
/** * 判断是否支持LE * * @return boolean * @hide */
@Deprecated
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public boolean isMultipleAdvertisementSupported() {
return mBluetoothAdapter.isMultipleAdvertisementSupported();
}
/** * 检测是否支持蓝牙低功耗,检查方式走版本分之 * * @return boolean */
public boolean checkSupperBluetoothLE() {
final int version = Build.VERSION.SDK_INT;
if (version >= Build.VERSION_CODES.LOLLIPOP) {
return isMultipleAdvertisementSupported();
} else {
return isSupperBluetoothLE();
}
}
/** * 允许蓝牙在某段时间内被扫描链接到 * * @param duration */
private void ensureDiscoverable(int duration) {
if (mBluetoothAdapter.getScanMode() != BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, duration);
mContext.startActivity(discoverableIntent);
}
}
/** * 基于APi21的扫描蓝牙 * @param mScanCallback */
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public void startScanningApi21(ScanCallback mScanCallback){
mBluetoothAdapter.getBluetoothLeScanner().startScan(buildScanFilters(), buildScanSettings(),mScanCallback);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private List buildScanFilters() {
List scanFilters = new ArrayList<>();
ScanFilter.Builder builder = new ScanFilter.Builder();
// 注释掉下面的扫描过滤规则,才能扫描到(uuid不匹配没法扫描到的)
// builder.setServiceUuid(Constants.Service_UUID);
//scanFilters.add(builder.build());
return scanFilters;
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private ScanSettings buildScanSettings() {
ScanSettings.Builder builder = new ScanSettings.Builder();
builder.setScanMode(ScanSettings.SCAN_MODE_LOW_POWER);
return builder.build();
}
/** * 注册广播用于接收扫描结果,Api11的方法需要用到 */
public void registerReceiver(){
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
mContext.registerReceiver(receiver, filter);
filter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
mContext.registerReceiver(receiver, filter);
}
/** * 销毁同步生命周期 */
protected void onDestroy() {
if (mBluetoothAdapter != null) {
mBluetoothAdapter.cancelDiscovery();
}
mContext.unregisterReceiver(receiver);
}
/** * Api11+都可以使用的蓝颜扫描 */
private void doDiscovery() {
if (mBluetoothAdapter.isDiscovering()) {
mBluetoothAdapter.cancelDiscovery();
}
mBluetoothAdapter.startDiscovery();
}
/** * The BroadcastReceiver that listens for discovered devices and changes the title when * discovery is finished */
private final BroadcastReceiver receiver = new BroadcastReceiver() {
ArrayList results = new ArrayList<>();
@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);
if (device.getBondState() != BluetoothDevice.BOND_BONDED) {
results.add(device);
}
} else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {
}
}
};
}
梳理知识,掌握脉络,万变不离其宗。
https://developer.android.com/guide/topics/connectivity/bluetooth.html#ConnectingDevices
https://developer.android.com/guide/topics/connectivity/bluetooth-le.html