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.
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
* - 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
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.
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
tns create my-app
cd my-app
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) {
var action = intent.getAction();
if ("ACTION_START" == action) {
postNotification();
} else if (“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);
}
});
function postNotification() {
// Do something. For example, fetch fresh data from backend to create a rich notification?
var utils = require("utils/utils");
var context = utils.ad.getApplicationContext(); // get a reference to the application context in Android
var builder = new android.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 pressed
var mainIntent = new android.content.Intent(context, com.tns.NativeScriptActivity.class);
var pendingIntent = android.app.PendingIntent.getActivity(context,
1,
mainIntent,
android.app.PendingIntent.FLAG_UPDATE_CURRENT);
builder.setContentIntent(pendingIntent);
builder.setDeleteIntent(getDeleteIntent(context));
var manager = context.getSystemService(android.content.Context.NOTIFICATION_SERVICE);
manager.notify(1, builder.build());
}
/* only necessary for dismissing the notification from the Notifications Screen */
function getDeleteIntent(context) {
var intent = new android.content.Intent(context, com.tns.broadcastreceivers.NotificationEventReceiver.class);
intent.setAction("ACTION_DELETE_NOTIFICATION");
return android.app.PendingIntent.getBroadcast(context, 0, intent, android.app.PendingIntent.FLAG_UPDATE_CURRENT);
}
android.support.v4.content.WakefulBroadcastReceiver.extend("com.tns.broadcastreceivers.NotificationEventReceiver", {
onReceive: function (context, intent) {
var action = intent.getAction();
var serviceIntent;
if ("ACTION_START_NOTIFICATION_SERVICE" == action) {
serviceIntent = createIntentStartNotificationService(context);
} else if ("ACTION_DELETE_NOTIFICATION" == action) {
serviceIntent = createIntentDeleteNotification(context);
}
if (serviceIntent) {
android.support.v4.content.WakefulBroadcastReceiver.startWakefulService(context, serviceIntent);
}
}
})
var Intent = android.content.Intent;
function createIntentStartNotificationService(context) {
var intent = new Intent(context, com.tns.notifications.NotificationIntentService.class);
intent.setAction("ACTION_START");
return intent;
}
function createIntentDeleteNotification(context) {
/* Similar as above, just with a different action */
}
function getStartPendingIntent(context) {
var alarmIntent = new android.content.Intent(context, com.tns.broadcastreceivers.NotificationEventReceiver.class);
intent.setAction("ACTION_START_NOTIFICATION_SERVICE");
return android.app.PendingIntent.getBroadcast(context, 0, intent, android.app.PendingIntent.FLAG_UPDATE_CURRENT);
}
function setupAlarm(context) {
var alarmManager = context.getSystemService(android.content.Context.ALARM_SERVICE);
var alarmIntent = 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;
function createViewModel() {
var utils = require("utils/utils");
var services = require("./service-helper");
/* … */
viewModel.onTap = function () {
services.setupAlarm(utils.ad.getApplicationContext());
}
/* insert your code for cancelling the alarm manager on user interaction */
return viewModel;
}
xml version="1.0" encoding="utf-8"
package="__PACKAGE__"
android:versionCode="1"
android:versionName="1.0">
<supports-screens
android:smallScreens="true"
android:normalScreens="true"
android:largeScreens="true"
android:xlargeScreens="true"/>
<uses-sdk
android:minSdkVersion="17"
android:targetSdkVersion="__APILEVEL__"/>
<!-- all user-permissions are declared above →
<!-- using the WakefulBroadcastReceiver requires that a WAKE_LOCK permission is granted -->
<uses-permission android:name="android.permission.WAKE_LOCK" />
<application ... >
<activity
...
</intent-filter>
</activity>
<activity android:name="com.tns.ErrorReportActivity"/>
<service
android:name="com.tns.notifications.NotificationIntentService"
android:enabled="true"
android:exported="false" />
<receiver android:name="com.tns.broadcastreceivers.NotificationEventReceiver" />
</application>
</manifest>
Now you are ready to test away your notifications scheduler!
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.
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.