Making app ready for Oreo

himanshu saluja
Urban Company – Engineering

--

Android O comes with some really good features but every great thing has a price. Android 8+ has put a lot of limitation to apps which do a lot of work in background without letting user know. Most interesting is the way in which it has modified notifications.

Even though the market share of Oreo is less right now but it won’t remain like this for a long time. So it's about time when developers should get their apps Oreo ready 😉

Android has introduced a lot of behavioural changes to the app built for Android 8.0+. Some of these changes affect all applications regardless of whether they are targeting Oreo. Some of the critical changes which affectsapps that target Android 8.0+ are:

  • Android Background Limitation
  • Broadcasts
  • Permission
  • Notification channel

Background Services

  • With the introduction of doze mode, Android already gave a hint that background jobs were no longer going to be same.
  • Background services not only drain a lot of battery (every user wants long battery life) but apps running in the background also consume processing power which in turn can also slow down another app that may be in the foreground or create sloppy OS behaviour.
  • In our application we were using a lot of normal as well as intent services which needed to be migrated.
  • NO MORE STARTSERVICE -
  1. The startService() method now throws an IllegalStateException if an app targeting Android 8.0+ tries to use that method in a situation when it isn’t permitted to create background services.
  2. The new context.startForegroundService() method starts a foreground service but with a implicit contract that service will call start foreground within 5 second of its creation.
  3. You can also call startService and startForegroundService for different OS version. In your service, you can build a notification(keep priority high), then call startForeground.
//The way i use it is
public void
startService(Context context, Intent intent) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(intent);
} else {
context.startService(intent);
}
}
class ForegroundService : Service() {

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
buildNotification()
return super.onStartCommand(intent, flags, startId)
}

private fun buildNotification() {
NotificationsBuilder.buildNotificationChannel(applicationContext)
val notificationManager: NotificationManager? =
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager?.let {
val
mBuilder = NotificationCompat.Builder(applicationContext,
NotificationsBuilder.CHANNEL)
val notification = mBuilder
.setContentTitle("Title")
.setContentText("Description")
.setSmallIcon()
.build()
startForeground((System.currentTimeMillis() and 0xfffffffL).toInt(), notification)
} ?: stopSelf()
}
}

4. Other approach

  • Job scheduler
  • Firebase JobDispatcher
  • FCM + service whitelist- Application added to whitelist are allowed to run for some time in the background and they behave like pre-Oreo. High priority FCM notification can add app to this list

5. Providing backward compatibility android has provided JobIntentService which can be used to replace Intent Service very easily as you can see in the code below. It uses Job Scheduler in Android O or later.

//INTENT SERVICE
//Call startService(intent) for IntentService to start
public class OreoIntentService extends IntentService {

@Override
protected void onHandleIntent(Intent intent) {
performTask();
}

}
/*
JOBINTENT SERVICE
To start JobIntentService
OreoJobIntentService.enqueueWork(context, new Intent(context, OreoJobIntentService.class));
Also in Manifest add
<service
android:name="OreoJobIntentService"
android:permission="android.permission.BIND_JOB_SERVICE" />
*/
public class OreoJobIntentService extends JobIntentService {

public static void enqueueWork(Context context, Intent work) {
enqueueWork(context, OreoJobIntentService.class,JobId, work);
}

@Override
protected void onHandleWork(Intent intent) {
performTask()
}
}

Broadcasts in oreo

App cannot use manifest to declare implicit broadcast.mManifest can be used to declare explicit broadcast that is specific to the app. There are some exceptions to this that can be found here.

When a broadcast is registered in manifest then common broadcasts can wake up multiple apps which causes battery and memory issue.

Our application manifest was full of such declarations. So we removed them from manifest and registered them programmatically, some of them even in the application class.

Permissions

Before Android O, if you asked for a particular permission in permission group and if its granted then all the permission in that group are granted but android has corrected this behaviour. Now you need to ask for all permissions separately. But on asking, permission will be granted instantly without any prompt if any other permission of that group is granted.

Now content provider with relevant permission is required. Before Android O you could observe change but to access data you needed permission. Now for notifying change you also need respective permission.

We were using an observer on call log db and also checking for the permission in onChange whenever we needed to access data but now on we added permission check while setting the content observer.

Notification Channel

One of the biggest change that android has brought is notification channel. If you don’t create notification channel it won’t be delivered.

Android allows developers to group notification in form of a channel so that they can be managed together. On the other hand, this provides the user with the feature to control these groups like blocking a channel, putting a particular channel on silent etc.

It gets more difficult when your app has multiple modules and each module is handling its own notification. A quick and easy approach is to create a channel for each module.

Creating a notification channel

Attempting to create an existing notification channel with its original values performs no operation, so it’s safe to perform the preceding sequence of steps when starting an app.

public static void buildNotificationChannel(Context appContext) {
if (appContext != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel chan1 = new NotificationChannel(PRIMARY_CHANNEL,
appContext.getString(R.string.default_notification_channel_id),
NotificationManager.IMPORTANCE_HIGH);


NotificationChannel chan2 = new NotificationChannel(SILENT_CHANNEL,
appContext.getString(R.string.silent_notification_channel_id),
NotificationManager.IMPORTANCE_DEFAULT);
chan2.setSound(null, null);
NotificationManager nm = (NotificationManager) appContext.getSystemService(Context.NOTIFICATION_SERVICE);
if (nm != null) {
nm.createNotificationChannel(chan1);
nm.createNotificationChannel(chan2);

}
}
}

Shooting a notification

NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(getApplicationContext(),
UploadNotificationManager.PRIMARY_CHANNEL);
Notification notification = mBuilder
.setContentTitle("Uploading Images")
.setContentText("Done uploading images")
.setSmallIcon(R.drawable.icon)
.build();
NotificationManager mNotifyManager = (NotificationManager)
getSystemService(Context.NOTIFICATION_SERVICE);
mNotifyManager.notify(1, notification);
notificationManager.cancel();

Get channel

NotificationManager mNotifyManager = (NotificationManager)
getSystemService(Context.NOTIFICATION_SERVICE);
//specific channel
mNotifyManager.getNotificationChannel("some_id");
//all channel
mNotifyManager.getNotificationChannels();

Delete a channel

NotificationManager mNotifyManager = (NotificationManager)
getSystemService(Context.NOTIFICATION_SERVICE);
mNotifyManager.deleteNotificationChannel(mNotifyManger.getNotificationChannel("some_id"));

IMPORTANT- Moving to Oreo means that your service cannot run in the background, it has to run as a foreground service. So while making a notification, check for a notification channel as you have 5 sec to show a notification and save the crash.

I was not able to create channel when an app gets updated. Will love to know if anyone finds it.

Some other issues

I hope this serves as a helpful guide when you are planning to migrate your android application to Oreo. If you have any suggestions, questions or your own gotchas while migrating to Oreo, please write it in the comments section.

--

--