Request a free audit

Specialising in Digital positioning and marketing, we tackle challenging questions that yield tangible value. By the end, you’ll receive actionable tips ready for immediate implementation. Take advantage of our complimentary, no-obligation complete audits.

Social media audit Problem definition workshop ISO/SOC readiness

 

Background Execution Limits for Android Devices

Date
April 16, 2019
Hot topics 🔥
Tech Insights
Contributor
David Roman
Background Execution Limits for Android Devices

By Andrii Savytskyi, Android Developer at WeAreBrain.

 


I recently faced a challenging task for one of my projects: how do I send push notifications to users if they haven’t been interacting with an application for more than 6+ days on any devices using Android LOLLIPOP through to Android N?

After I devoted time to carefully think about all the elements involved, I came up with a gameplan on how I would tackle this task. I separated this challenge into 2 subtasks:

  1. Building a continuous checking system which will monitor whether the user has been active on the application or not.
  2. How and when to send the notifications.

MainActivity.kt

The launcher activity is responsible for setting the time of the last app launch and the start of the background service in the case of the first app launch.

lass MainActivity : AppCompatActivity() {

    val storage: Storage by inject()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        if (storage.isFirstRun()) {
            RecentRunService.enqueueWork()
            storage.setIsFirstRun(false)
        }

        storage.setLastRun(System.currentTimeMillis())
    }

    companion object {
        const val TAG = "MainActivity"
        fun getIntent(context: Context) = Intent(context, MainActivity::class.java)
    }
}

RecentRunService.kt

The background service is from JobIntentService. I decided to choose JobIntentService because the service can execute work from the background without any notifications. As you may know, Service and IntentService cannot work in the background on Android O and above, according to Background Execution Limits.

<service
            android:name=".services.RecentRunService"
            android:permission="android.permission.BIND_JOB_SERVICE" />

According to Developer.Android “Job services must be protected with this permission: android.permission.BIND_JOB_SERVICE. If a job service is declared in the manifest but not protected with this permission, that service will be ignored by the system”.

class RecentRunService : JobIntentService(), KoinComponent {

    val notificationManager: PushNotificationManager by inject()
    val alarmManager: AlarmManager by inject()
    val storage: Storage by inject()

    override fun onHandleWork(intent: Intent) {
        if (storage.getLastRun() < (System.currentTimeMillis() - DELAY)) {
            notificationManager.show(context)
        }
        alarmManager.setAlarm(context, DELAY)
        stopSelf(JOB_ID)
    }

    companion object {
        const val JOB_ID = 0x01
        const val TAG = "RecentRunService"
        private val DELAY = 10000L

        fun enqueueWork() {
            enqueueWork(context, RecentRunService::class.java, JOB_ID, Intent())
        }
    }
}

AlarmReceiver.kt

A broadcast receiver is registered in AndroidManifest.xml to receive explicit broadcasts.

<receiver
            android:name=".receivers.AlarmReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="com.savitskiy.reminder.intent.action.ALARM" />
            </intent-filter>
        </receiver>

According to Broadcast Limitations, “Apps that target Android 8.0 or higher can no longer register broadcast receivers for implicit broadcasts in their manifest. An implicit broadcast is a broadcast that does not target that app specifically. For example, ACTION_PACKAGE_REPLACED is an implicit broadcast, since it is sent to all registered listeners, letting them know that some package on the device was replaced. However, ACTION_MY_PACKAGE_REPLACED is not an implicit broadcast, since it is sent only to the app whose package was replaced, no matter how many other apps have registered listeners for that broadcast.”

class AlarmReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context?, intent: Intent?) {
        RecentRunService.enqueueWork()
    }

    companion object {
        const val TAG = "AlarmReceiver"

        const val ACTION = "com.savitskiy.reminder.intent.action.ALARM"

        fun getIntent(context: Context?) = Intent(context, AlarmReceiver::class.java).apply {
            action = ACTION
        }

    }
}

BootReceiver.kt

BroadcastReceiver is registered in AndroidManifest.xml to receive a broadcast after the device is booted.

<receiver
            android:name=".receivers.BootReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED" />
                <action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
            </intent-filter>
        </receiver>

“android.intent.action.BOOT_COMPLETED”, “android.intent.action.LOCKED_BOOT_COMPLETED” are implicit broadcasts which could be declared in AndroidManifest.xml, are exempted from Broadcasts limitations.

class BootReceiver : BroadcastReceiver() {

    override fun onReceive(context: Context?, intent: Intent?) {
        RecentRunService.enqueueWork()
    }

    companion object {
        const val TAG = "BootReceiver"
    }
}

There you have it. You can now send push notifications to devices which are dormant for 6+ days. I implemented a lot of what is contained in this article “Android Notifications — An elegant way to build and display” by Jovche Mitrejchevski.

Thanks for reading, I’d be very glad if my article helps someone and makes their life a bit easier. You can review all projects on my github.

David Roman

David is one our marketing gurus. He loves working with content but has a good eye for marketing analytics as well. Creativity is what drives him, photography being one of his passions.

Working Machines

An executive’s guide to AI and Intelligent Automation. Working Machines takes a look at how the renewed vigour for the development of Artificial Intelligence and Intelligent Automation technology has begun to change how businesses operate.