Android开发之蓝牙通信

Posted AnalyzeSystem

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android开发之蓝牙通信相关的知识,希望对你有一定的参考价值。

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

Android开发之蓝牙通信(一)

Android开发之蓝牙通信(二)

Android开发之蓝牙通信(三)


蓝牙开发必练基本功

蓝牙权限

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


蓝牙低功耗基于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的仓库了

先看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

以上是关于Android开发之蓝牙通信的主要内容,如果未能解决你的问题,请参考以下文章

Android 蓝牙开发之搜索配对连接通信大全

Android 蓝牙开发之搜索配对连接通信大全

关于蓝牙开发之数据缓存问题(脏数据)

经典蓝牙Android开发(通信)

Android BLE与终端通信——Google API BLE4.0低功耗蓝牙文档解读之案例初探

Android开发之蓝牙(Bluetooth)操作--扫描已经配对的蓝牙设备