The problem
I wrote a program for Android 4.3, with a main activity, a broadcast receiver and a service. The activity binds to the service in its onCreate
method. The activity has a button that schedules an alarm 10 seconds in the future. The alarm triggers the BroadcastReceiver.onReceive
. This method attempts to get a hold on the binder, but there is a circumstance in which this fails and peekService
returns null
.
What works
- Clicking the button and waiting 10 seconds
- Clicking the button, clicking home, and wait in the home screen till 10 seconds are elapsed.
- Clicking the button, clicking the activity list, close the activity by swiping it to the left, reopen the program and wait till 10 seconds are elapsed (you need to be fast :-).
What doesn't work
- Clicking the button, clicking the activity list, close the activity by swiping it to the left and wait till 10 seconds are elapsed; this is essentially like (3.) without reopening the program.
Specifically, if I execute these 4 tests I get the following log:
02-05 20:53:29.992: D/ServiceSSCCE.MyService(476): I've been bound.
02-05 20:53:30.179: D/ServiceSSCCE.MainActivity(476): Service connected.
02-05 20:53:43.265: D/ServiceSSCCE.MyReceiver(476): Awesome, let's get this **** done!
02-05 20:53:55.460: D/ServiceSSCCE.MyReceiver(476): Awesome, let's get this **** done!
02-05 20:54:08.531: D/ServiceSSCCE.MyService(764): I've been bound.
02-05 20:54:08.663: D/ServiceSSCCE.MainActivity(764): Service connected.
02-05 20:54:10.890: D/ServiceSSCCE.MyReceiver(764): Awesome, let's get this **** done!
02-05 20:54:23.593: D/ServiceSSCCE.MyReceiver(788): I just received a null binder.
The last message shows that test (4.) failed, while previous messages show that (1.), (2.) and (3.) succedeed.
I'm aware of this, this and this answer, as well as pretty much any relevant result that Google lists in the first two pages. I tried several things, including but not limited to:
- Calling
startService
, both fromBroadcastReceiver.onReceive
and from the main activity (although I'm not sure I understood howbindService
andstartService
interact) - Fiddling with intents, in particular with context (
this
VSgetApplicationContext
and so on). - Setting the service as foreground (see this)
I'm really interested in why this happens, more than I'm interested in the solution.
MainActivity.java
package it.damix.examples.servicesscce;
import android.app.Activity;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import android.view.View;
public class MainActivity extends Activity {
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
Log.d("ServiceSSCCE.MainActivity", "Service disconnected.");
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d("ServiceSSCCE.MainActivity", "Service connected.");
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bindService(new Intent(this, MyService.class), mConnection, Context.BIND_AUTO_CREATE);
}
public void scheduleAlarm(View view) {
AlarmManager am = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
PendingIntent pi = PendingIntent.getBroadcast(this, 101, new Intent(this, MyReceiver.class), 0);
am.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + 10000, pi);
}
}
MyReceiver.java
package it.damix.examples.servicesscce;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
public class MyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
IBinder binder = peekService(context, new Intent(context, MyService.class));
if (binder == null)
Log.d("ServiceSSCCE.MyReceiver", "I just received a null binder.");
else
Log.d("ServiceSSCCE.MyReceiver", "Awesome, let's get this **** done!");
}
}
MyService.java
package it.damix.examples.servicesscce;
import android.app.Service;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.os.Messenger;
import android.util.Log;
public class MyService extends Service {
static class MyHandler extends Handler {
}
@Override
public IBinder onBind(Intent arg0) {
Log.d("ServiceSSCCE.MyService", "I've been bound.");
return new Messenger(new MyHandler()).getBinder();
}
@Override
public boolean onUnbind(Intent intent) {
Log.d("ServiceSSCCE.MyService", "I've been unbound.");
return super.onUnbind(intent);
}
@Override
public void onRebind(Intent intent) {
Log.d("ServiceSSCCE.MyService", "I've been rebound.");
super.onRebind(intent);
}
}
ApplicationManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="it.damix.examples.servicesscce"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="18"
android:targetSdkVersion="18" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name=".MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver android:name="it.damix.examples.servicesscce.MyReceiver" android:enabled="true"></receiver>
<service android:name="it.damix.examples.servicesscce.MyService" android:enabled="true"></service>
</application>
</manifest>
activity_main.xml
<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:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="it.damix.examples.servicesscce.MainActivity" >
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Click me to schedule an alarm..."
android:onClick="scheduleAlarm"/>
</RelativeLayout>