我正在尝试制作一款能够扫描某个蓝牙设备作为后台服务的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编程的进程间通信方面不太熟悉,所以我可能在那里做错了。有什么想法吗?