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"
?>
<
manifest
xmlns:android
=
"http://schemas.android.com/apk/res/android"
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.