Android背景服务中的BLE扫描

时间:2014-06-27 04:23:58

标签: android android-service bluetooth-lowenergy android-broadcast android-handler

我正在尝试制作一款能够扫描某个蓝牙设备作为后台服务的Android应用。一旦手机在蓝牙设备的特定范围内,通过读取RSSI进行测量,后台服务将启动向用户显示的活动。如果手机移出蓝牙设备的范围,(在RSSI值超过某个阈值后),活动应该被杀死。

以下是后台服务的代码:

public class BeaconScanService extends Service implements BluetoothAdapter.LeScanCallback {

    private Service self = this;

    public static final String UNLOCK = "unlock";
    public static final String STATUS = "status";
    public static final String SIGNAL = "signal";
    public static final String GET_SIGNAL = "get_signal";

    //desired device to find
    private static final String targetMAC = "E1:BE:A8:1A:8B:A0";

    //class to handle saving RSSI values and detecting Bluetooth proximity
    private proxDetector proxy;

    //boolean to determine if phone is in BT range
    static boolean inProx = false;

    private BluetoothAdapter mBluetoothAdapter;

    @Override
    public IBinder onBind(Intent intent) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public void onCreate() {
        super.onCreate();

        //Proximity Detector
        proxy = new proxDetector();

        final BluetoothManager bluetoothManager =
                (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
        mBluetoothAdapter = bluetoothManager.getAdapter();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId){
        /*
         * We need to enforce that Bluetooth is first enabled, and take the
         * user to settings to enable it if they have not done so.
         */

        if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
            //Bluetooth is disabled
            Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            enableBtIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            startActivity(enableBtIntent);
        }

        /*
         * Check for Bluetooth LE Support.  In production, our manifest entry will keep this
         * from installing on these devices, but this will allow test devices or other
         * sideloads to report whether or not the feature exists.
         */
        if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
            Toast.makeText(this, "No LE Support.", Toast.LENGTH_SHORT).show();
            return START_STICKY;
        }

        //Begin scanning for LE devices
        startScan();

        return START_STICKY;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();

        //Cancel any scans in progress
        mHandler.removeCallbacks(mStopRunnable);
        mHandler.removeCallbacks(mStartRunnable);
        mBluetoothAdapter.stopLeScan(this);
    }

    private Runnable mStopRunnable = new Runnable() {
        @Override
        public void run() {
            stopScan();
        }
    };
    private Runnable mStartRunnable = new Runnable() {
        @Override
        public void run() {
            startScan();
        }
    };

    private void startScan() {
        Toast.makeText(this, "Scanning", Toast.LENGTH_SHORT).show();
        //Scan for Bluetooth device with specified MAC
        mBluetoothAdapter.startLeScan(this);
        mHandler.postDelayed(mStopRunnable, 5000);
    }

    private void stopScan() {
        Toast.makeText(this, "Not Scanning", Toast.LENGTH_SHORT).show();
        mBluetoothAdapter.stopLeScan(this);
        mHandler.postDelayed(mStartRunnable, 2500);
    }

    /* BluetoothAdapter.LeScanCallback */

    @Override
    public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
        /*
        * Create a new beacon and pass it up to the main thread
        */
        Toast.makeText(self, "OnLeScan", Toast.LENGTH_SHORT).show();
        BT_Beacon beacon = new BT_Beacon(device.getAddress(), rssi);
        mHandler.sendMessage(Message.obtain(null, 0, beacon));
    }

    /*
     * We have a Handler to process scan results on the main thread
     */
    private Handler mHandler = new Handler() {

        @Override
        public void handleMessage(Message msg) {
            Toast.makeText(self, "Handlering", Toast.LENGTH_SHORT).show();
            BT_Beacon beacon = (BT_Beacon) msg.obj;
            if(beacon.getAddress().equals(targetMAC)){//Only look for target device
                Toast.makeText(self, String.format("%ddBm", beacon.getSignal()), Toast.LENGTH_SHORT).show();
                //HANDLE PROXIMITY DETECTION
                Intent i1 = new Intent(UNLOCK);
                proxy.processProx(beacon.getSignal());
                Intent i2 = new Intent(SIGNAL);
                i2.putExtra(GET_SIGNAL, proxy.prox);
                sendBroadcast(i2);
                if(proxy.crossedLine()){
                    i1.putExtra(STATUS, 1);
                    sendBroadcast(i1);
                    inProx = true;
                    Intent i = new Intent(self,MyActivity.class);
                    i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                    startActivity(i);
                }else{
                    i1.putExtra(STATUS, 0);
                    sendBroadcast(i1);
                    inProx = false;
                }
            }
        }
    };
}

最终我希望这个后台服务在启动时启动,但出于测试目的,我可以从一个也应显示RSSI值的Activity创建或销毁此服务:

public class MainActivity extends Activity implements OnClickListener {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button btnScan = (Button) findViewById(R.id.btnScan);
        Button btnUnscan = (Button) findViewById(R.id.btnUnscan);
        btnScan.setOnClickListener(this);
        btnUnscan.setOnClickListener(this);

}

    @Override
    protected void onResume() {
        super.onResume();
        registerReceiver(mReceiver, new IntentFilter(BeaconScanService.SIGNAL));
    }

    @Override
    protected void onPause() {
        super.onPause();
        unregisterReceiver(mReceiver);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btnScan:
                Intent i = new Intent(this,BeaconScanService.class);
                startService(i);
                break;

            case R.id.btnUnscan:
                Intent i = new Intent(this,BeaconScanService.class);
                stopService(i);
                break;
        }
    }

    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            Bundle bundle = intent.getExtras();
            if(bundle != null) {
                int sig = bundle.getInt(BeaconScanService.GET_SIGNAL);
                TextView rssiView = (TextView) findViewById(R.id.text_rssi);
                rssiView.setText(String.format("%ddBm", sig));
            }
        }
    };
}

这是MainActivity的布局:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/btnScan"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Scan"/>

    <Button
        android:id="@+id/btnUnscan"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Stop Scan"
        android:layout_below="@+id/btnScan"/>

    <TextView
        android:id="@+id/text_rssi"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:textAppearance="?android:attr/textAppearanceListItem"
        android:text="dBm"
        android:layout_below="@+id/btnUnscan"/>

</RelativeLayout>

在BeaconScanService启动和结束的活动中,BroadcastReceiver注册如下:

@Override
protected void onResume() {
    super.onResume();
    registerReceiver(beaconScanReceiver, new IntentFilter(BeaconScanService.UNLOCK));
}

并且BroadcastReceiver看起来像这样:

private final BroadcastReceiver beaconScanReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        Bundle bundle = intent.getExtras();
        if(bundle != null) {
            int status = bundle.getInt(BeaconScanService.STATUS);
            if(status == 0) {
                finish();
            }
        }
    }
};

当我运行此代码时,我可以启动和停止BeaconScanService,并且由于显示调试Toast,我知道该服务正在扫描蓝牙设备。据我所知,代码在服务的Handler中出现故障。 TextView中没有显示RSSI值,并且当手机足够靠近蓝牙设备时,MyActivity不会启动。

我对Android编程的进程间通信方面不太熟悉,所以我可能在那里做错了。有什么想法吗?

0 个答案:

没有答案