1

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

Thomas Mary
  • 1,535
  • 1
  • 13
  • 24

2 Answers2

1

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)

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
  • Is it that by calling the 'startForeground()' method, the calculation will work well in the background ? [< 1 hour is enough for now :)] – Thomas Mary Oct 02 '17 at 15:31
  • 2
    Aren't foreground services exempt from doze on all versions? – Tim Oct 02 '17 at 15:34
  • @TimCastelijns: I don't think so. – CommonsWare Oct 02 '17 at 15:35
  • @ThomasMary: Please get rid of the `WakefulBroadcastReceiver` stuff. Please use wakelocks correctly. *And* please use `startForeground()`. – CommonsWare Oct 02 '17 at 15:36
  • 1
    Can you check this and the referred G+ post? https://stackoverflow.com/a/33077301/1843331 I know that the exact/ complete workings of doze are not documented very well, but I think you may be wrong on this point – Tim Oct 02 '17 at 15:38
  • @TimCastelijns: Well, based on [this issue](https://issuetracker.google.com/issues/37070074), including all the later comments, my interpretation is "it's complicated". In principle, I think you're correct (and I'm sorry for forgetting this case); in practice, it appears that YMMV. – CommonsWare Oct 02 '17 at 15:44
  • @CommonsWare I will follow your advices and use 'startForeground()'. I wanted to follow the doc but it does not seem up to date :/ – Thomas Mary Oct 02 '17 at 15:47
  • Yeah it does indeed seem to be a complicated matter. Which is a shame, because doze isn't exactly an unimportant 'mechanism' – Tim Oct 02 '17 at 15:58
  • I've been looking for an example like this. Would you still say this is correct? Acquire the wakelock in onCreate and release in onDestroy? – Florian Walther Nov 15 '18 at 22:31
  • 1
    @FlorianWalther: Assuming that you need the wakelock for the entire duration of the service's existence, yes, and assuming that your UI is in the foreground when you are starting the service (e.g., it's a media player under user control). If you are going to be starting the service from the background, you should acquire the wakelock before starting the service (the way my old `WakefulIntentService` used to do). On the flip side, if your service is not doing anything most of the time, perhaps only hold the wakelock when it is doing actual work. – CommonsWare Nov 16 '18 at 00:29
1

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 ?

Thomas Mary
  • 1,535
  • 1
  • 13
  • 24
  • I found my answer [here](https://stackoverflow.com/a/46391826/5154891) In API 26+, I must use `startForegroundService(myService);` In API <26, I must use `startService(myService);` – Thomas Mary Oct 18 '17 at 07:33