Android蓝牙4.0扫描

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/H__dream/article/details/80667751

本文主要记录一下Android扫描蓝牙设备的方法。


  1. 初始化蓝牙和注册广播:
    private void initBluetooth() {
        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
        if (mBluetoothAdapter != null) {
            IntentFilter filter = new IntentFilter();
            filter.addAction(BluetoothDevice.ACTION_FOUND);
            filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
            filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
            mContext.registerReceiver(mBluetoothReceiver, filter);
        }
    }

首先获取BluetoothAdapter蓝牙适配器,通过适配器可以扫描蓝牙设备,就是BluetoothDevice对象。如果使用广播来获取扫描结果的话,还需要注册蓝牙扫描广播(ACTIONFOUND)、蓝牙开始扫描(ACTION_DISCOVERY_STARTED)和完成扫描的广播(ACTIN_DISCOVERY_FINISHED)。另外一种方法是使用LeScanCallback回调获取结果,这种放在Android API中提示在API 21之后弃用,但是经过我测试,在5.1,7.0上仍然可以使用,5.0上效果仍然很好,但是在7.0上效率会很差,经常搜索不到设备。
2. 开始扫描蓝牙设备和取消扫描:

    /**
     * 开始扫描蓝牙设备
     */
    private void startScan() {
        if (mBluetoothAdapter != null) {
            mBluetoothAdapter.startDiscovery();
        }
    }

    /**
     * 取消扫描
     */
    private void cancelScan() {
        if (mBluetoothAdapter != null) {
            mBluetoothAdapter.cancelDiscovery();
        }
    }

扫描的开始和取消都很简单,分别调用蓝牙适配器的startDiscovery()和cancelDiscovery()函数即可。
3. 最后创建一个广播接收器,来接受扫描结果:

    private BroadcastReceiver mBluetoothReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            if (BluetoothDevice.ACTION_FOUND.equals(intent.getAction())) {
                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                int rssi = intent.getExtras().getShort(BluetoothDevice.EXTRA_RSSI);
                addDevice(device, rssi);
            } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(intent.getAction())) {
                sendDevices();
            }
        }
    };

    /**
     * 添加设备
     */
    private void addDevice(BluetoothDevice device, int rssi) {
        BLeDevice bLeDevice = new BLeDevice(device.getName(), device.getAddress(), rssi);
        Iterator<BLeDevice> iterator = mDeviceList.iterator();
        while (iterator.hasNext()) {
            if (device.getAddress().equals(iterator.next().getAddress()))
                iterator.remove();  //如果多次扫描到同一台设备,则移除之前的设备
        }
        mDeviceList.add(bLeDevice);
    }

    /**
     * 将扫描到的设备发送到前台
     */
    private void sendDevices() {
        Message msg = new Message();
        msg.obj = mDeviceList;
        mHandler.sendMessage(msg);
    }

注册了广播后,每搜索到一台设备,系统就会发送一次ACTION_FOUND广播,所以我们可以通过intent.getAction()来筛选出这个广播,然后通过intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)取出序列化对象BluetoothDevice。因为蓝牙定位需要用到rssi,所以我还获取了rssi值,获取的方法是intent.getExtras().getShort(BluetoothDevice.EXTRA_RSSI)。之后将获取到的蓝牙名称,地址和RSSI赋给创建的实体类BLeDevice,再把BLeDevice方到ArrayList集合中。在扫描完成的广播中,我们将扫描到的设备通过handler发送到前台,可以用来在页面显示。
4. 最后别忘了添加蓝牙权限,还有定位权限,6.0之后定位权限需要主动申请:

    <uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

.
5. 因为蓝牙定位需要周期扫描设备,所以我使用了Timer定时器,本来是用AlarmManager的,但是它在Android7.0之后最小周期必须在5s以上,最终只能把它抛弃了。另外除了使用广播之外还有另外两种方法,分别是在API 21弃用的LeScallback和新增的android.bluetooth.le包下的BluetoothLeScanner和Scallback类。我全部都封装到了BeaconScanner类中。

BeaconScanner类完整的代码:

import android.annotation.TargetApi;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothManager;
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanResult;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Build;
import android.os.Handler;
import android.os.Message;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.Timer;
import java.util.TimerTask;

public class BeaconScanner {

    private Context mContext;
    private Handler mHandler;

    private Timer mTimer;
    private Handler mHandlerTimer;
    private static final int MIN_DELAY = 600;
    private static final int MIN_PERIOD = 2000;

    private BluetoothAdapter mBluetoothAdapter;
    private BluetoothLeScanner mBluetoothLeScanner;
    private ArrayList<BeaconDevice> mDeviceList = new ArrayList<>();

    private boolean isRegisterReceiver = false;

    public BeaconScanner(Context mContext, Handler mHandler) {
        this.mContext = mContext;
        this.mHandler = mHandler;
        this.mHandlerTimer = new Handler();
        this.mTimer = new Timer();
        initBluetooth2();
    }

    /**
     * 周期扫描设备
     *
     * @param period 扫描周期,单位毫秒
     */
    public void start(long period) {
        if (period <= 0) {
            period = MIN_PERIOD;
        }
        mTimer.schedule(new TimerTask() {
            @Override
            public void run() {
                startScan();
            }
        }, 0, period);
    }

    public void close() {
        if(isRegisterReceiver){
            mContext.unregisterReceiver(mBluetoothReceiver);
            isRegisterReceiver = false;
        }
        if (mTimer != null) {
            mTimer.cancel();
        }
    }

    private void sendDevices() {
        Collections.sort(mDeviceList, new SignalComparator());
        Message msg = new Message();
        msg.obj = mDeviceList;
        mHandler.sendMessage(msg);
    }

    // 添加设备
    private void addDevice(BluetoothDevice device, int rssi) {
        BeaconDevice beaconDevice = new BeaconDevice(device.getName(), device.getAddress(), rssi);
        Iterator<BeaconDevice> iterator = mDeviceList.iterator();
        while (iterator.hasNext()) {
            if (device.getAddress().equals(iterator.next().getAddress()))
                iterator.remove();  //如果多次扫描到同一台设备,则移除之前的设备
        }
        mDeviceList.add(beaconDevice);
    }

    //===================注册广播接受扫描结果=========================================================
    /**
     * 初始化蓝牙适配器,注册蓝牙搜索,开始搜索蓝牙,完成搜索3个广播
     */
    private void initBluetooth() {
        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
        if (mBluetoothAdapter != null) {
            IntentFilter filter = new IntentFilter();
            filter.addAction(BluetoothDevice.ACTION_FOUND);
            filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
            filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
            mContext.registerReceiver(mBluetoothReceiver, filter);
            isRegisterReceiver = true;
        }
    }


    /**
     * 开始扫描蓝牙设备
     */
    private void startDiscovery() {
        mDeviceList.clear(); // 每一次扫描之前要清除上一次扫描的设备
        if (mBluetoothAdapter != null) {
            mBluetoothAdapter.startDiscovery();
        }
        mHandlerTimer.postDelayed(new Runnable() {
            @Override
            public void run() {
                cancelDiscovery();
            }
        }, MIN_DELAY);
    }

    /**
     * 取消扫描,并将扫到的设备发送到前台
     */
    private void cancelDiscovery() {
        if (mBluetoothAdapter != null) {
            mBluetoothAdapter.cancelDiscovery();
        }
    }

    private BroadcastReceiver mBluetoothReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            if (BluetoothDevice.ACTION_FOUND.equals(intent.getAction())) {
                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                int rssi = intent.getExtras().getShort(BluetoothDevice.EXTRA_RSSI);
                addDevice(device, rssi);
            } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(intent.getAction())) {
                sendDevices();
            }
        }
    };
    //===================注册广播接受扫描结果=========================================================

    //===================使用LeScanCallback接受扫描结果===============================================
    private void initBluetooth2(){
        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
    }

    private void startScan(){
        mDeviceList.clear();
        if (mBluetoothAdapter != null) {
            mBluetoothAdapter.startLeScan(mLeScanCallback);
        }
        mHandlerTimer.postDelayed(new Runnable() {
            @Override
            public void run() {
                stopScan();
            }
        }, MIN_DELAY);
    }

    private void stopScan(){
        if (mBluetoothAdapter != null)
            mBluetoothAdapter.stopLeScan(mLeScanCallback);
        sendDevices();
    }

    private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() {
        @Override
        public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
            addDevice(device, rssi);
        }
    };

    //===================使用LeScanCallback接受扫描结果===============================================

    //===================使用ScanCallback接受扫描结果=================================================

    /**
     * Android5.0(API = 21)调用
     * BluetoothLeScanner此类提供了执行蓝牙LE设备扫描相关操作的方法。应用程序可以使用扫描特定类型的蓝牙LE
     * 设备ScanFilter。它也可以请求不同类型的回调来传递结果;
     * <p>
     * 在Android7.0(Mi 5s,E350)上测试效果很差,可以说是间接性抽风,时好时坏。
     * 在Android5.1(G21)上测试效果一般,相比7.0好很多,搜索效率在50-60%
     * 在Android4.4(X3)上报NoClassDefFoundError异常,报错位置是创建ScanCallback匿名内部类。未解决。猜测
     * 是因为不支持蓝牙4.0,或者Android版本过低。
     */
    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    private void initBluetoothLe(){
        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
        mBluetoothLeScanner =  mBluetoothAdapter.getBluetoothLeScanner();
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    private void startScanLe(){
        mDeviceList.clear();
        if (mBluetoothLeScanner != null) {
            mBluetoothLeScanner.startScan(mScanCallback);
        }
        mHandlerTimer.postDelayed(new Runnable() {
            @Override
            public void run() {
                stopScanLe();
            }
        }, MIN_DELAY);
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    private void stopScanLe(){
        if (mBluetoothLeScanner != null) {
            mBluetoothLeScanner.stopScan(mScanCallback);
        }
        sendDevices();
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    private ScanCallback mScanCallback = new ScanCallback() {
        @Override
        public void onScanResult(int callbackType, ScanResult result) {
            super.onScanResult(callbackType, result);
            BluetoothDevice device = result.getDevice();
            int rssi = result.getRssi();
            addDevice(device, rssi);
        }
    };
    //===================使用ScanCallback接受扫描结果=================================================

    /**
     * 根据信号强度降序排列
     */
    class SignalComparator implements Comparator<BeaconDevice> {

        @TargetApi(Build.VERSION_CODES.KITKAT)
        @Override
        public int compare(BeaconDevice o1, BeaconDevice o2) {
            return Integer.compare(o2.getSignal(), o1.getSignal());
        }
    }
}

注意:使用BeaconScanner类:在Activity或Service中调用start()开始周期扫描蓝牙,close()解除注册广播和关闭timer定时器。如果需要更换方法需要在BeaconScanner手动更改,首先在构造函数中更换初始化方法,然后在start()中更换对应的开始扫描方法。
BeaconDevice 实体类:

import android.os.Parcel;
import android.os.Parcelable;

public class BeaconDevice implements Parcelable {

    private String name;
    private String address;
    private int signal;

    public BeaconDevice(String name, String address, int signal) {
        this.name = name;
        this.address = address;
        this.signal = signal;
    }

    protected BeaconDevice(Parcel in) {
        name = in.readString();
        address = in.readString();
        signal = in.readInt();
    }

    public static final Parcelable.Creator<BeaconDevice> CREATOR = new Parcelable.Creator<BeaconDevice>() {
        @Override
        public BeaconDevice createFromParcel(Parcel in) {
            return new BeaconDevice(in);
        }

        @Override
        public BeaconDevice[] newArray(int size) {
            return new BeaconDevice[size];
        }
    };

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public int getSignal() {
        return signal;
    }

    public void setSignal(int signal) {
        this.signal = signal;
    }

    @Override
    public String toString() {
        return "BeaconDevice{" +
                "name='" + name + '\'' +
                ", address='" + address + '\'' +
                ", signal=" + signal +
                '}';
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        // 序列化
        dest.writeString(name);
        dest.writeString(address);
        dest.writeInt(signal);
    }
}

使用的话在Activity或Service中,调用beaconScan.start()函数,传入一个扫描周期时间即可,别忘了在程序结束的时候调用beaconScan.close()取消广播注册和关闭闹钟。
总结
1.注册广播的方法:推荐,经过测试在Android4.4,5.1,7.0上搜索效率都很好,而且稳定。
2.使用LeScanCallback回调:虽说Android API提示在API 21 后弃用,但是经过测试在7.0上仍然可以使用,只是效果很差,而在Android4.4和5.1上面效果比第一种方法效率更高。如果不适配高版本,可以使用这个方法。
3.使用ScanCallback回调:不推荐,效果很差,首先在Android4.4上面直接崩溃,运行到ScanCallback内部类创建时报NoClassDefFoundError,猜测是因为我的测试机不支持蓝牙4.0。另外不管在Android5.1和7.0上,搜索效果都很差,只能说是间接性抽风,时好时坏。
因为测试次数有限,可能受机器和Android版本的影响,所以不保证测试的准确性,具体还要自己测试
文章示例源码下载:Android蓝牙扫描

展开阅读全文

没有更多推荐了,返回首页