Using Android Background Services in NativeScript
When your application needs to perform heavy, non-UI work, you must take care not to block the UI thread and disrupt the otherwise smooth user experience. On Android, using background services is the right technique for the job.
Important: This article will not teach you what background services in Android are, or how to use them. If you’d like to acquaint yourself with services, head to the Android API Guides.
Hey, since it’s my first blog post, a proper introduction is in order. My name is Peter, I work as a software developer, and am part of the core Android Runtime team in NativeScript. Today I’ll show you how Android Background Services behave in a NativeScript application, and provide guidelines to writing your own implementations.
Services in Android
When your application needs to perform heavy, non-UI work, you must take care not to block the UI thread and disrupt the otherwise smooth user experience. On Android, using background services is the right technique for the job.
Common examples of heavy non-UI work are
- fetching data from a remote backend (mail, chat messages)*
- writing data into a local DB**
- downloading/uploading an image*
- firing a scheduled notification
- getting current GPS location
- communicating with other applications on the device
* - the core modules package already comes with ready-to-use solutions for Async Http Client, Async handling of images, and more
** - the NativeScript open source community creates plugins that enable developers to work with SQLite and other NOSQL databases
Services in NativeScript
Almost everything that you can do when developing with Android - you can do with NativeScript too. Due to the nature of the NativeScript Android Runtime - where the JavaScript code runs on the main UI thread, there are certain known limitations when using Android services.
- IntentService’s onHandleIntent will execute on the main UI thread (versus on a dedicated worker Thread when implemented in pure Android)
- IntentService’s implementation needs to use a constructor that takes no arguments, but that currently is not possible through Java.
- The base Service class and all its descendants execute on the main UI thread by default in Android. Implementing a service that should run on a background thread must be managed in Java by the developer (see Notes).
- BroadcastReceivers should not be registered with the android: process =":remote" property in the AndroidManifest as the receiver will be unable to execute JavaScript in an independent process
Below is an example of how we can use the Android Alarm Manager to schedule calling an IntentService which will create Notifications even when the Application’s Activities have been destroyed (the application has been closed). We will leverage one of NativeScript’s perks - directly access native (Java) API with JavaScript in order to write code that is as close as possible to that which you will find on the internet when looking up Android Guides. Original code SO
-
Create a default NativeScript application
tns create my-appcd my-app -
Extend the android.app.IntentService class and describe the work that needs to be done in the onHandleIntent method callback. In this case we want the class to create a notification object using the Notification Builder, and send it to the Notifications Service. Create a new file called NotificationIntentService.js and write the following:
android.app.IntentService.extend("com.tns.notifications.NotificationIntentService"/* give your class a valid name as it will need to be declared in the AndroidManifest later */, {onHandleIntent:function(intent) {varaction = intent.getAction();if("ACTION_START"== action) {postNotification();}elseif(“ACTION_STOP” == action) {/* get the system alarm manager and cancel all pending alarms, which will stop the service from executing periodically */}android.support.v4.content.WakefulBroadcastReceiver.completeWakefulIntent(intent);}});functionpostNotification() {// Do something. For example, fetch fresh data from backend to create a rich notification?varutils = require("utils/utils");varcontext = utils.ad.getApplicationContext();// get a reference to the application context in Androidvarbuilder =newandroid.app.Notification.Builder(context);builder.setContentTitle("Scheduled Notification").setAutoCancel(true).setColor(android.R.color.holo_purple)// optional.setContentText("This notification has been triggered by Notification Service").setVibrate([100, 200, 100])// optional.setSmallIcon(android.R.drawable.btn_star_big_on);// will open main NativeScript activity when the notification is pressedvarmainIntent =newandroid.content.Intent(context, com.tns.NativeScriptActivity.class);varpendingIntent = android.app.PendingIntent.getActivity(context,1,mainIntent,android.app.PendingIntent.FLAG_UPDATE_CURRENT);builder.setContentIntent(pendingIntent);builder.setDeleteIntent(getDeleteIntent(context));varmanager = context.getSystemService(android.content.Context.NOTIFICATION_SERVICE);manager.notify(1, builder.build());}/* only necessary for dismissing the notification from the Notifications Screen */functiongetDeleteIntent(context) {varintent =newandroid.content.Intent(context, com.tns.broadcastreceivers.NotificationEventReceiver.class);intent.setAction("ACTION_DELETE_NOTIFICATION");returnandroid.app.PendingIntent.getBroadcast(context, 0, intent, android.app.PendingIntent.FLAG_UPDATE_CURRENT);} -
Create a BroadcastReceiver that listens for events fired from the system or the Alarm Manager and starts the Service. We will only implement the intent with action (ACTION_START) for posting notifications.The same logic is applicable if you want to stop the service. Using WakefulBroadcastReceiver instead of a BroadcastReceiver guarantees the CPU will not sleep until completion of request processing in the Service(until the IntentService calls completeWakefulIntent)
android.support.v4.content.WakefulBroadcastReceiver.extend("com.tns.broadcastreceivers.NotificationEventReceiver", {onReceive:function(context, intent) {varaction = intent.getAction();varserviceIntent;if("ACTION_START_NOTIFICATION_SERVICE"== action) {serviceIntent = createIntentStartNotificationService(context);}elseif("ACTION_DELETE_NOTIFICATION"== action) {serviceIntent = createIntentDeleteNotification(context);}if(serviceIntent) {android.support.v4.content.WakefulBroadcastReceiver.startWakefulService(context, serviceIntent);}}})varIntent = android.content.Intent;functioncreateIntentStartNotificationService(context) {varintent =newIntent(context, com.tns.notifications.NotificationIntentService.class);intent.setAction("ACTION_START");returnintent;}functioncreateIntentDeleteNotification(context) {/* Similar as above, just with a different action */} -
Now we need to create the actual alarm which will periodically send intents to our WakefulBroadcastReceiver. Create a new file called service-helper.js. Create a setupAlarm function and export it, so that it can be called from anywhere in our code.
functiongetStartPendingIntent(context) {varalarmIntent =newandroid.content.Intent(context, com.tns.broadcastreceivers.NotificationEventReceiver.class);intent.setAction("ACTION_START_NOTIFICATION_SERVICE");returnandroid.app.PendingIntent.getBroadcast(context, 0, intent, android.app.PendingIntent.FLAG_UPDATE_CURRENT);}functionsetupAlarm(context) {varalarmManager = context.getSystemService(android.content.Context.ALARM_SERVICE);varalarmIntent = getStartPendingIntent(context);alarmManager.setRepeating(android.app.AlarmManager.RTC_WAKEUP,java.lang.System.currentTimeMillis(),1000 * 60 * 60 * 24,/* alarm will send the `alarmIntent` object every 24h */alarmIntent);}Function cancelAlarm(context) { … }module.exports.setupAlarm = setupAlarm; -
Next - let’s call setupAlarm on button click. In the main-view-model.js script of your application, change the onTap event to the following:
functioncreateViewModel() {varutils = require("utils/utils");varservices = require("./service-helper");/* … */viewModel.onTap =function() {services.setupAlarm(utils.ad.getApplicationContext());}/* insert your code for cancelling the alarm manager on user interaction */returnviewModel;} -
And we are almost set! Finally, we need to register our service, and BroadcastReceivers in the AndroidManifest.xml located in app/App_Resources/Android
<?xmlversion="1.0"encoding="utf-8"?><manifestxmlns:android="http://schemas.android.com/apk/res/android"package="__PACKAGE__"android:versionCode="1"android:versionName="1.0"><supports-screensandroid:smallScreens="true"android:normalScreens="true"android:largeScreens="true"android:xlargeScreens="true"/><uses-sdkandroid:minSdkVersion="17"android:targetSdkVersion="__APILEVEL__"/><!-- all user-permissions are declared above →<!-- using the WakefulBroadcastReceiver requires that a WAKE_LOCK permission is granted --><uses-permissionandroid:name="android.permission.WAKE_LOCK"/><application... ><activity...</intent-filter></activity><activityandroid:name="com.tns.ErrorReportActivity"/><serviceandroid:name="com.tns.notifications.NotificationIntentService"android:enabled="true"android:exported="false"/><receiverandroid:name="com.tns.broadcastreceivers.NotificationEventReceiver"/></application></manifest>
Now you are ready to test away your notifications scheduler!
Conclusion
We have used IntentService to perform operations without a foreground activity. Keep in mind, the code executes on the main thread of a NativeScript Android app. A more intensive operation, like downloading an image, could possibly block the foreground activity- thus freezing the user interface, or throw an exception for violating the strict thread policy in Android. Consider using background services for almost immediate-result operations.
Note: Be very mindful when developing applications which send out notifications. Some users may find them annoying, if they get them too often, and as a result - delete your application.
Note: The suggested approach when you want to offload a long-running job on a different Thread is to write the implementation in Java classes and invoke the job in NativeScript.
What’s next
In the upcoming releases we will be rolling out support for the Web Workers feature in NativeScript, allowing you to execute code in a background thread, all through Angular/JavaScript.