Android app crashes
Thursday, Apr 7, 2016

As reported previously, our app was crashing. Implementation of crash reporting was of course half of the job. The next problem was to work through and pinpoint the problems and causes.

Description of the problem

Once in a while, when toggling Bluetooth, Android would display the dreaded “App XX has stopped running” popup. However this wouldn’t always happen when Bluetooth was toggled, making it hard to figure out exactly what was going on.

Reproducing the problem

The problem appeared again whilst using the TacX utility. Normally I leave Bluetooth off and I don’t run our app in the background (I quit/kill it when I am not using it). The TacX utility needs Bluetooth and turning Bluetooth on causes the crash pop up (of our app). I was surprised because I was quite sure that our app wasn’t running. I started our app again, killed it and recycled Bluetooth, sure enough it “crashed” again (I quote “crashed” because it wasn’t supposed to be running). Not nice, but at least the problem can be reproduced.

Diagnosing and fixing the problem

As mentioned, a BroadcastReceiver was originally written:

<receiver android:name=".Helper.BluetoothBroadcastReceiver"

Looking at the code of the receiver, the following was happening:

@Override
public void onReceive(Context context, Intent intent) {
    String action = intent.getAction();

    if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) {
        if (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1) == BluetoothAdapter.STATE_OFF) {
            PeripheralStorage.getInstance().bluetoothAdapterStateChanged(false);
        }

The receiver gets the state of the Bluetooth adapter and does something if the state has changed. PeripheralStorage being a singleton class and the stack trace provided the final clue.

ACRA caught a RuntimeException for com.test.mytest
java.lang.RuntimeException: Unable to start receiver com.test.mytest.Helper.BluetoothBroadcastReceiver: java.lang.NullPointerException: Attempt to invoke virtual method 'java.io.File android.content.Context.getCacheDir()' on a null object reference
at android.app.ActivityThread.handleReceiver(ActivityThread.java:2680)
at android.app.ActivityThread.access$1700(ActivityThread.java:156)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1428)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:211)
at android.app.ActivityThread.main(ActivityThread.java:5373)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1020)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:815)
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'java.io.File android.content.Context.getCacheDir()' on a null object reference
at com.test.mytest.Helper.CacheHelper.loadCache(CacheHelper.java:28)
at com.test.mytest.Helper.CacheHelper.loadObjects(CacheHelper.java:65)
at com.test.mytest.Helper.Peripheral.MyPeripheralManager.<init>(MyPeripheralManager.java:43)
at com.test.mytest.Storage.PeripheralStorage.<init>(PeripheralStorage.java:28)
at com.test.mytest.Storage.PeripheralStorage.getInstance(PeripheralStorage.java:22)
at com.test.mytest.Helper.BluetoothBroadcastReceiver.onReceive(BluetoothBroadcastReceiver.java:21)
at android.app.ActivityThread.handleReceiver(ActivityThread.java:2673)
... 9 more

Huh? This line is strange:

at com.test.mytest.Storage.PeripheralStorage.<init>(PeripheralStorage.java:28)

Why is the singleton class trying to init? Sitting back in my chair, the answer struck me; killing / ending the app will of course destroy all objects, including the singletons. So calling getInstance on PeripheralStorage forced it to be recreated and then running into conditions later down the line.

The fix

It seems that using “global” receivers must be treated carefully as there doesn’t seem to be a way to unload them - don’t use them if you don’t need global receivers or receivers to fire app code after your app has exited. We moved the broadcast receiver code to the relevant activity and added a listener:

OurReceiver mBroadcastReceiver;

@Override
protected void onResume() {

    IntentFilter filterRec = new IntentFilter();

    filterRec.addAction("android.bluetooth.adapter.action.STATE_CHANGED");
    mBroadcastReceiver = new OurReceiver();

    getApplicationContext().registerReceiver(mBroadcastReceiver, filterRec);

}

@Override
protected void onPause() {

    getApplicationContext().unregisterReceiver(mBroadcastReceiver);
}

Keeping a reference to OurReceiver allows us to be able to unload it when the activity is paused and restart it when the activity resumes. The code could be optimised slightly, but for now our crashes are gone and, more importantly in my book, something has been learnt.