无法让警报管理器触发蓝牙设备扫描

Can't get an alarmmanager to trigger scanning of bluetooth devices

我是 Android 和 Java 的初学者(八十年代我在 Z80 机器语言方面相当先进)。我想创建一个应用程序在后台每 5 分钟扫描一次蓝牙设备。目的是监控我家周围各种设备的使用情况。我有 3 个 classes:MainActivity,它有 UI 的代码,MbtScanner,它作为扫描蓝牙设备的代码,BluetoothTScheduler,它有闹钟响起时调用扫描的代码。出于测试目的,警报设置为每 60 秒重复一次,但一旦代码起作用,我将其设置为每 5 分钟一次。然后将结果写入文件。

当从 MainActivity 调用时扫描工作正常,但由于 alarmmanager 而从 BluetoothTScheduler 调用时扫描效果不佳。

我在 Whosebug 和互联网上搜索了答案,但没有成功。我认为问题在于我传递给 MbtScanner activity 的上下文,这是错误的上下文类型,但我并不真正理解这个问题,也不知道如何解决它。

这是我的 MainActivity 的代码

public class MainActivity extends AppCompatActivity {

AlarmManager alarmManager;
Intent intent;
public PendingIntent pendingIntent;

MbtScanner mbtscanner;
BluetoothTScheduler bluetoothTScheduler;

public static ArrayAdapter<String> mArrayAdapter;
ListView listView;



@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    listView = (ListView) findViewById(R.id.listView);
    mArrayAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, 0);
    listView.setAdapter(mArrayAdapter);

    mbtscanner = new MbtScanner(this);

    setScheduler();
    mbtscanner.mbtScannerInit();
    mbtscanner.scanResult();
    setBtReceiver();
}


// SCAN FOR BLUETOOTH DEVICES - CALLED BY UI SCAN BUTTON
public void btScan(View v) {
    if (!mbtscanner.stillScanning) {
        mArrayAdapter.clear();
    }
    mbtscanner.mbtScan();
}


// ASK USER TO SWITCH ON BLUETOOTH (CALLED IF BLUETOOTH OFF)
private void turnOnBT() {
    Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
    startActivityForResult(enableBtIntent, 1);
}



// SET BLUETOOTH RECEIVER AND IF FOUND CAPTURE DATA TO FILE AND TO LISTVIEW
private void setBtReceiver() {

    //CHECK FOR BLUETOOTH CAPABILITY AND IF IT IS SWITHCED ON
    if (mbtscanner.mBluetoothAdapter == null) {
        Toast.makeText(getApplicationContext(), "Bluetooth not supported", Toast.LENGTH_SHORT).show();
        finish();
    } else {
        if (!mbtscanner.mBluetoothAdapter.isEnabled()) {
            turnOnBT();
        }

        if (mbtscanner.mBluetoothAdapter.isDiscovering()) mbtscanner.mBluetoothAdapter.cancelDiscovery();
    }


}

public static void updateListView(String s){
    mArrayAdapter.add(s);
}



@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);

    // IF USER REFUSES TO SWITCH ON BLUETOOTH THEN TERMINATE APPLICATION
    if (resultCode == RESULT_CANCELED) {
        Toast.makeText(getApplicationContext(), "Bluetooth must be enabled to Continue", Toast.LENGTH_SHORT).show();
        finish();
    }
}


@Override
protected void onPause() {

    Toast.makeText(this, "On Pause", Toast.LENGTH_SHORT).show();

    super.onPause();
}

@Override
protected void onResume() {
    super.onResume();
    // init();
    // setBtReceiver();
}


@Override
protected void onDestroy() {
    Toast.makeText(this, "On Destroy", Toast.LENGTH_SHORT).show();
    if (mbtscanner.mBluetoothAdapter.isDiscovering()) {
        mbtscanner.mBluetoothAdapter.cancelDiscovery();
    }
    unregisterReceiver(mbtscanner.mReceiver);

    super.onDestroy();
}


// THIS IS CALLED FROM A UI BUTTON "CANCEL ALARM"
public void cancelAlarm(View view) {
    alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
    intent = new Intent(this, BluetoothTScheduler.class);
    pendingIntent = PendingIntent.getBroadcast(
            this.getApplicationContext(), 234324243, intent, 0);
    alarmManager.cancel(pendingIntent);
    Toast.makeText(this, "Alarm cancelled", Toast.LENGTH_LONG).show();
}



public void setScheduler() {

    bluetoothTScheduler = new BluetoothTScheduler();

    alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
    intent = new Intent(this, BluetoothTScheduler.class);
    pendingIntent = PendingIntent.getBroadcast(
            this.getApplicationContext(), 234324243, intent, 0);

    alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis()
            + (30 * 1000), 60 * 1000, pendingIntent);
    Toast.makeText(this, "Alarm set in 10 seconds for every 60 seconds", Toast.LENGTH_LONG).show();

}
}

这是我的 MbtScanner class:

的代码
public class MbtScanner {


public String path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/btTracker";
FileWriter fileWriter;
File file;

ProgressDialog progress;

Context mContext;

BluetoothAdapter mBluetoothAdapter;
IntentFilter filter;
BroadcastReceiver mReceiver;
boolean stillScanning = false;

String s;
int count =0;

public MbtScanner(Context mContext) {
    this.mContext = mContext;
}


public void mbtScan (){
    mBluetoothAdapter.startDiscovery();
    stillScanning = true;

    // DIALOG BOX SAYING PLEASE WAIT
    progress = new ProgressDialog(mContext);
    progress.setTitle("Scanning");
    progress.setMessage("Please wait...");
    //   progress.setCancelable(false);
    progress.show();

}

public void mbtScannerInit(){
    mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
    filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);


    File dir = new File(path);
    dir.mkdirs();
    file = new File(path + "/savedfile.csv");
}


public void scanResult() {
    mReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            // When discovery finds a device


            // IF DEVICE FOUND
            if (BluetoothDevice.ACTION_FOUND.equals(action)) {
                // Get the BluetoothDevice object from the Intent


                // CAPTURE DEVICE DATA TO LISTVIEW
                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);

                count++;

                int rssi = intent.getShortExtra(BluetoothDevice.EXTRA_RSSI, Short.MIN_VALUE);
                String sName, sAddress, sUuids, sDate, sFile;
                int iBondState, iMajorClass;

                sName = device.getName();
                sAddress = device.getAddress();
                iBondState = device.getBondState();
                iMajorClass = device.getBluetoothClass().getMajorDeviceClass();
                sUuids = "" + device.getUuids();
                sDate = DateFormat.getDateTimeInstance().format(new Date());

                s = "Count= " + count + "\n";
                s += "Name= " + sName + "\n";
                s += "Address= " + sAddress + "\n";
                s += "Bond state= " + iBondState + ";  ";
                s += "Major class= " + iMajorClass + "\n";
                s += "Uuids= " + sUuids + ";  ";
                s += "RSSI= " + rssi + "dBm" + "\n";
                s += "Date= " + sDate + "\n";

                MainActivity.updateListView(s);

                // CREATE STRING OF DEVICE DATA TO SAVE TO FILE
                sFile = count + "," + sName + "," + sAddress + "," + iBondState + "," + iMajorClass + "," + sUuids + "," + rssi + "," + sDate;


                // SAVE DEVICE DATA TO FILE
                try {
                    commitToFile(sFile);
                } catch (IOException e) {
                    e.printStackTrace();
                }

            } else if (BluetoothAdapter.ACTION_DISCOVERY_STARTED.equals(action)) {
                Toast.makeText(mContext, "Discovery started", Toast.LENGTH_SHORT).show();

                // SCANNING FINISHED SO INFORM USER
            } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {
                progress.dismiss();
                Toast.makeText(mContext, "Discovery finished. Last device found: " + s, Toast.LENGTH_SHORT).show();
                stillScanning = false;


                // IF SOMEHOW BLUETOOTH HAS BEEN SWITCHED OFF THEN TRY TO SWITCH IT BACK ON
            } else if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) {
                if (mBluetoothAdapter.getState() == mBluetoothAdapter.STATE_OFF) {
                    // Toast.makeText(mActivity, "Bluetooth state changed", Toast.LENGTH_SHORT).show();

                    // turnOnBT(); ALTERNATIVE CODE TO NOTIFY USER THAT APP CAN NO LONGER WORK BCAUSE BLUTOOTH IS OFF, AND CANCEL ALARM
                }
            }
        }
    };


    mContext.registerReceiver(mReceiver, filter); 
    filter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
    mContext.registerReceiver(mReceiver, filter);
    filter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
    mContext.registerReceiver(mReceiver, filter);
    filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
    mContext.registerReceiver(mReceiver, filter);

}

// SAVE DATA TO FILE
private void commitToFile(String str) throws IOException {
    fileWriter = new FileWriter(file, true);
    BufferedWriter bufferWriter = new BufferedWriter(fileWriter);
    PrintWriter printWriter = new PrintWriter(bufferWriter);

    printWriter.print(str + "\n");
    printWriter.close();

    Toast.makeText(mContext, "written to file: ", Toast.LENGTH_SHORT).show();
}

}

这是由 Alarmmanager 调用的 BluetoothTScheduler class 我的代码:

public class BluetoothTScheduler extends BroadcastReceiver  {

public static final String MyPREFERENCES = "MyPrefs" ;
SharedPreferences sharedpreferences;
int brCounter;
MbtScanner sbtscanner;



@Override
public void onReceive(Context context, Intent intent) {
    sharedpreferences = context.getSharedPreferences(MyPREFERENCES, Context.MODE_PRIVATE);
    SharedPreferences.Editor editor = sharedpreferences.edit();
    brCounter = sharedpreferences.getInt("counter", 0);

    Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
    vibrator.vibrate(1000);
    Toast.makeText(context, "Alarm...." + brCounter, Toast.LENGTH_LONG).show();

    brCounter = brCounter +1;
    editor.putInt("counter", brCounter);
    editor.commit();



    sbtscanner = new MbtScanner(context);
    sbtscanner.mbtScannerInit();
    if (sbtscanner.mBluetoothAdapter.isDiscovering()) sbtscanner.mBluetoothAdapter.cancelDiscovery();
    sbtscanner.scanResult();

}

}

这是我的清单文件:

<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.VIBRATE" />

<application
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:supportsRtl="true"
    android:theme="@style/AppTheme" >
    <activity
        android:name=".MainActivity" >
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
    <receiver android:name="BluetoothTScheduler" >
    </receiver>



</application>

这里是 logcat:

D/InputMethodManager: windowDismissed mLockisused = false
D/AndroidRuntime: Shutting down VM
E/AndroidRuntime: FATAL EXCEPTION: main
E/AndroidRuntime: Process: com.orinocosolutions.bluetracker2, PID: 29905
E/AndroidRuntime: java.lang.RuntimeException: Unable to start receiver com.orinocosolutions.bluetracker2.BluetoothTScheduler: android.content.ReceiverCallNotAllowedException: BroadcastReceiver components are not allowed to register to receive intents
E/AndroidRuntime:     at android.app.ActivityThread.handleReceiver(ActivityThread.java:3508)
E/AndroidRuntime:     at android.app.ActivityThread.access00(ActivityThread.java:218)
E/AndroidRuntime:     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1795)
E/AndroidRuntime:     at android.os.Handler.dispatchMessage(Handler.java:102)
E/AndroidRuntime:     at android.os.Looper.loop(Looper.java:145)
E/AndroidRuntime:     at android.app.ActivityThread.main(ActivityThread.java:6917)
E/AndroidRuntime:     at java.lang.reflect.Method.invoke(Native Method)
E/AndroidRuntime:     at java.lang.reflect.Method.invoke(Method.java:372)
E/AndroidRuntime:     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1404)
E/AndroidRuntime:     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1199)
E/AndroidRuntime:  Caused by: android.content.ReceiverCallNotAllowedException: BroadcastReceiver components are not allowed to register to receive intents
E/AndroidRuntime:     at android.app.ReceiverRestrictedContext.registerReceiver(ContextImpl.java:275)
E/AndroidRuntime:     at android.app.ReceiverRestrictedContext.registerReceiver(ContextImpl.java:264)
E/AndroidRuntime:     at com.orinocosolutions.bluetracker2.MbtScanner.scanResult(MbtScanner.java:166)
E/AndroidRuntime:     at com.orinocosolutions.bluetracker2.BluetoothTScheduler.onReceive(BluetoothTScheduler.java:42)
E/AndroidRuntime:     at android.app.ActivityThread.handleReceiver(ActivityThread.java:3501)
E/AndroidRuntime:     at android.app.ActivityThread.access00(ActivityThread.java:218) 
E/AndroidRuntime:     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1795) 
E/AndroidRuntime:     at android.os.Handler.dispatchMessage(Handler.java:102) 
E/AndroidRuntime:     at android.os.Looper.loop(Looper.java:145) 
E/AndroidRuntime:     at android.app.ActivityThread.main(ActivityThread.java:6917) 
E/AndroidRuntime:     at java.lang.reflect.Method.invoke(Native Method) 
E/AndroidRuntime:     at java.lang.reflect.Method.invoke(Method.java:372)

非常感谢任何帮助。

看起来您需要做的就是更改此错误:

sbtscanner = new MbtScanner(context);

为此:

sbtscanner = new MbtScanner(context.getApplicationContext());

这是因为在MbtScanner的scanResult()方法内部,你在运行时使用这个Context注册了一个BroadcastReceiver,传入BluetoothTScheduleronReceive()方法的Context是不允许用于注册另一个 BroadcastReceiver。

来自the documentation

This exception is thrown from registerReceiver(BroadcastReceiver, IntentFilter) and bindService(Intent, ServiceConnection, int) when these methods are being used from an BroadcastReceiver component. In this case, the component will no longer be active upon returning from receiving the Intent, so it is not valid to use asynchronous APIs.

通过上述修复,您的 MbtScanner class 将仅在从警报触发的 BroadcastReceiver 调用时使用应用程序上下文。