Android开发之蓝牙通信(一)

来源:互联网 发布:装修公司网络部运营 编辑:程序博客网 时间:2024/05/17 03:16

时隔半年时间,又遇到了蓝牙开发了,之前是蓝牙连接打印相关方面的,这次需要蓝牙配对数据传输,折腾过去折腾过来,也就那么回事,下定决心系统的梳理这块的知识


蓝牙开发必练基本功

蓝牙权限

为了在您的应用程序中使用蓝牙功能,您必须声明蓝牙权限蓝牙。您需要此权限来执行任何蓝牙通信,如请求一个连接、接受一个连接和传输数据。如果你想让你的应用启动设备发现或操纵蓝牙设置,你也必须申报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。我们通过注册广播接收器接受广播,来处理一些事情:

// Create a BroadcastReceiver for ACTION_FOUNDprivate 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 BroadcastReceiverIntentFilter 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 = 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对象

 // 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<ScanFilter> buildScanFilters() {        List<ScanFilter> 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<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);          //扫描失败        }    }

扫描开始之前的逻辑判断自行参考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<ScanFilter> buildScanFilters() {        List<ScanFilter> 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<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)) {            }        }    };}

小结

梳理知识,掌握脉络,万变不离其宗。

参考资料

https://developer.android.com/guide/topics/connectivity/bluetooth.html#ConnectingDevices
https://developer.android.com/guide/topics/connectivity/bluetooth-le.html

2 1
原创粉丝点击