Thomas Mary
Thomas Mary

Reputation: 1579

Is sendBroadcast() the best way to keep a WakefulBroadcastReceiver alive in Android?

I try do do some background calculation tasks in an Android application.

My Main class :

public class MainActivity extends AppCompatActivity {

private CalculationReceiver calculationReceiver = new CalculationReceiver();

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

        Button button = (Button) findViewById(R.id.button);

        final Context mContext = this;

        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                calculationReceiver.doAddition(mContext, 2, 2);
            }
        });
    }
}

My service :

public class CalculationService extends IntentService {

    public CalculationService() {
        super("Calculation Service");
    }

    @Override
    protected void onHandleIntent(@Nullable Intent intent) {
        int nb1 = intent.getIntExtra(NUMBER_1,0);
        int nb2 = intent.getIntExtra(NUMBER_2,0);
        doAddition(nb1,nb2);
        CalculationReceiver.completeWakefulIntent(intent);
    }

    public void doAddition(int number1, int number2){
        int result = number1+number2;
        System.out.println("Result : " + result);
    }
}

My receiver :

public class CalculationReceiver extends WakefulBroadcastReceiver {

    public static final String NUMBER_1 = "NUMBER_1";
    public static final String NUMBER_2 = "NUMBER_2";

    @Override
    public void onReceive(Context context, Intent intent) {
        Intent service = new Intent(context, CalculationService.class);

        int receiverNumber1 = intent.getIntExtra(NUMBER_1,0);
        int receiverNumber2 = intent.getIntExtra(NUMBER_2,0);

        service.putExtra(NUMBER_1,receiverNumber1);
        service.putExtra(NUMBER_2,receiverNumber2);

        startWakefulService(context, service);
    }

    public void doAddition (Context context, int number1, int number2){
        Intent intent = new Intent(context, CalculationReceiver.class);

        intent.putExtra(NUMBER_1,number1);
        intent.putExtra(NUMBER_2,number2);

        context.sendBroadcast(intent);
    }
}

My Manifest :

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.testservices">
    <uses-permission android:name="android.permission.WAKE_LOCK"></uses-permission>
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        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>
        <service android:name=".ReductionService"
            android:enabled="true" />
        <receiver android:name=".ReductionReceiver"/>

        <service android:name=".CalculationService"
            android:enabled="true" />
        <receiver android:name=".CalculationReceiver"/>
    </application>
</manifest>

The calculations of the application are more complex than these additions, and can take several minutes (in average 15 minutes) to be done.

According to the Google documentation (https://developer.android.com/training/scheduling/wakelock.html), I decided to implement this architecture to make sure that the calculation is done to the end.

The idea is that the user starts his calculation and then waits for the application to give the result. In the meantime, he can launch other apps or lock his phone, the calculation must not stop.

This approach seems to work.

What bothers me here is the call to service in the receiver:

context.sendBroadcast (intent);

Is there a more "clean" way to start the service?

What strikes me is that it does not seem very "clean", especially the passage of several times the same parameter (number1 and number2)

Thanks

Upvotes: 1

Views: 457

Answers (2)

Thomas Mary
Thomas Mary

Reputation: 1579

It was in this way that I resolved it, following the advice of @CommonsWare

Main Activity :

public class MainActivity extends AppCompatActivity {

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

        Button button = (Button) findViewById(R.id.button);

        final Context mContext = this;

        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                Intent service = new Intent(mContext, CalculationService.class);

                service.putExtra(NUMBER_1,2);
                service.putExtra(NUMBER_2,2);

                startService(service);
            }
        });
    }
}

Calculation Service :

public class CalculationService extends IntentService {

    public static final String NUMBER_1 = "NUMBER_1";
    public static final String NUMBER_2 = "NUMBER_2";

    private static final int FOREGROUND_ID = 42;

    PowerManager.WakeLock wakeLock;

    public CalculationService() {
        super("Calculation Service");
    }

    @Override
    protected void onHandleIntent(@Nullable Intent intent) {
        int nb1 = intent.getIntExtra(NUMBER_1,0);
        int nb2 = intent.getIntExtra(NUMBER_2,0);
        doAddition(nb1,nb2);
    }

    public void doAddition(int number1, int number2){
        int result = number1+number2;
        System.out.println("Result : " + result);
    }

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

        PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE);
        wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
                "CalculationServiceWakelockTag");
        wakeLock.acquire();

        Intent notificationIntent = new Intent(this, MainActivity.class);

        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
                notificationIntent, 0);

        Notification notification = new NotificationCompat.Builder(this)
                .setSmallIcon(R.mipmap.ic_launcher)
                .setContentTitle("Calculation App")
                .setContentText("Calculation in progress")
                .setContentIntent(pendingIntent).build();

        startForeground(FOREGROUND_ID, notification);
    }

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

        wakeLock.release();
    }
}

This is working great :)

But can I call

startForegroundService(service)

instead of

startService(service);

or not ? And why ?

Upvotes: 1

CommonsWare
CommonsWare

Reputation: 1006809

According to the Google documentation (https://developer.android.com/training/scheduling/wakelock.html), I decided to implement this architecture to make sure that the calculation is done to the end.

That is not how the documentation shows using WakefulBroadcastReceiver. Plus, WakefulBroadcastReceiver was deprecated in version 26.0.0 of the support libraries.

The idea is that the user starts his calculation and then waits for the application to give the result.

My interpretation of this is that the user is requesting, through your activity's UI, to start the calculation. This means that at this point in time, the screen is on and you have an activity in the foreground.

Is there a more "clean" way to start the service?

Call startService().

Step #1: Delete your use of the deprecated WakefulBroadcastReceiver

Step #2: Have your activity call startService() to start the service

Step #3: Have your service acquire a partial WakeLock through the PowerManager system service, in the service's onCreate() method

Step #4: Have your service release that WakeLock in the service's onDestroy() method

Step #5: Modify the service to be a foreground service, calling startForeground() in onCreate() with a suitable Notification to allow the user to control the behavior of the service

Note that:

  • If you skip Step #5, your service will stop running after ~1 minute on Android 8.0+.

  • This will still not work on Android 6.0+ if the device enters into Doze mode. That should not happen for ~1 hour, but you need to make sure that your calculations are done by then.

  • Consider offloading the calculation work to a server, rather than burning up the user's CPU for an extended period of time (through your calculation work plus the wakelock)

Upvotes: 1

Related Questions