蓝牙开发必练基本功
蓝牙权限
为了在您的应用程序中使用蓝牙功能,您必须声明蓝牙权限蓝牙。您需要此权限来执行任何蓝牙通信,如请求一个连接、接受一个连接和传输数据。如果你想让你的应用启动设备发现或操纵蓝牙设置,你也必须申报bluetooth_admin许可。大多数应用程序都需要此权限,仅用于发现本地蓝牙设备的能力。此权限授予的其他权限不应被使用,除非应用程序是一个“电源管理器”,将修改用户请求后的蓝牙设置。注意:如果你使用bluetooth_admin许可,那么你也必须有蓝牙许可。
<manifest ... > <uses-permission android:name="android.permission.BLUETOOTH" /> ...</manifest>
设置蓝牙
在应用程序可以通过蓝牙进行通信之前,您需要验证是否支持蓝牙设备上的支持,如果是这样的话,请确保它已启用。如果不支持蓝牙,那么您应该优雅地禁用任何蓝牙功能。如果蓝牙是支持的,但禁用,那么你可以要求用户启用蓝牙,而不会离开你的应用程序。这个设置是在两个步骤来完成,使用蓝牙适配器。
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<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices()// If there are paired devicesif (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。我们通过注册广播接收器接受广播,来处理一些事情:
private final BroadcastReceiver mReceiver = new BroadcastReceiver() { public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (BluetoothDevice.ACTION_FOUND.equals(action)) { BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); mArrayAdapter.add(device.getName() + "\n" + device.getAddress()); } }};IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);registerReceiver(mReceiver, filter);
警告:执行设备发现是蓝牙适配器的一个沉重的过程,并会消耗大量的资源。一旦你找到了一个设备连接,就调用canceldiscovery()取消之前的扫描。
发现设备的Intent意图默认扫描时间120秒,一个应用程序可以设置最大持续时间为3600秒,0值表示设备总是发现。任何值低于0或高于3600自动设置为120秒)(可以这样理解:蓝牙设备运行在多少时间内可以被其他用于扫描到并连接)
Intent discoverableIntent = newIntent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE)discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300)startActivity(discoverableIntent)
关于蓝牙与服务端、客户端的链接涉及到BluetoothServerSocket 、BluetoothSocket ,没啥好说的,稍后通过官方simple一观即可。
蓝牙低功耗基于Android4.3
相比较上面这块,权限必须有以下两项(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使用的设备是否支持低功耗蓝牙,但又想让支持的设备使用低功耗蓝牙,那么你可以这样做
<uses-feature android:name="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对象
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; }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
蓝牙设备的扫描也发生了变化,通过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; private static final long SCAN_PERIOD = 10000; ... private void scanLeDevice(final boolean enable) { if (enable) { 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); } ... }...}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
通过LeScanCallback 回调接口的实现,来处理返回结果刷新UI等方面功能的实现。
private LeDeviceListAdapter mLeDeviceListAdapter;...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(); } }); }};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
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); 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<ScanFilter> buildScanFilters() { List<ScanFilter> scanFilters = new ArrayList<>(); ScanFilter.Builder builder = new ScanFilter.Builder(); 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<ScanResult> 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); } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
扫描开始之前的逻辑判断自行参考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) { ConnectedThread r; synchronized (this) { if (mState != STATE_CONNECTED) return; r = mConnectedThread; } r.write(out); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
最后提供一个工具类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<ScanFilter> buildScanFilters() { List<ScanFilter> scanFilters = new ArrayList<>(); ScanFilter.Builder builder = new ScanFilter.Builder(); 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<BluetoothDevice> 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)) { } } };}