使用 NativeScript 的 Android 持续后台服务
Posted 小陈乱敲代码
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用 NativeScript 的 Android 持续后台服务相关的知识,希望对你有一定的参考价值。
最近,我开始着手在 android 上制作专门的语音助手。至少可以说我与 Java 关系密切,而且我还没有时间玩 Kotlin,NativeScript 似乎是显而易见的选择。
现在这是一项正在进行的工作,但我已经了解了很多关于 Android 的知识,我想与您分享一些我的发现。
首先,对于这项任务,我需要不断地听唤醒词并做出相应的反应。在任何平台上实现此功能的明显选择是某种后台服务或守护程序。
当谷歌搜索 nativescript 和后台服务时,一个优秀的教程和一个示例 repo 出现在顶部(我正在谈论这个)。
唉,这是使用 IntentService ,它只按计划运行并在任务完成后退出。
虽然创建一个连续的后台服务非常容易,但缺少关于这个主题的示例(本文旨在解决这个问题)。
设置
对于本文,我假设我们正在使用
typescript hello_world 模板:
tns create ServiceExample --ts --appid tk.ozymandias.ServiceExample
适应其他模板/技术应该不难。
服务
首先在 下新建一个子文件夹app/,我们调用它service。这纯粹是为了保持您的项目结构干净整洁。现在使用这些内容
创建一个新文件app/service/continuous_service.android.ts
export const CONTINUOUS_SERVICE_CLASSNAME = "tk.ozymandias.ServiceExample.Continuous_Service";
@JavaProxy("tk.ozymandias.ServiceExample.Continuous_Service")
class Continuous_Service extends android.app.Service
private timerId: number;
onBind(): android.os.IBinder
return null;
onCreate(): void
super.onCreate();
console.log("SERVICE CREATED");
if (!this.timerId)
this.timerId = setInterval(() =>
console.log("PING");
, 1000)
onStartCommand(intent: android.content.Intent, flags: number, startId: number): number
console.log("SERVICE STARTED");
return android.app.Service.START_REDELIVER_INTENT;
onDestroy(): void
console.log("SERVICE DESTROYED");
super.onDestroy();
clearInterval(this.timerId);
现在这是一个非常基本的服务,它只是在后台运行并每秒向控制台打印“PING”。
在顶部,我们将服务名称导出为常量,稍后将在几个地方使用它。
唉,您至少需要在另外两个地方将服务名称指定为字符串文字。
第一个在这里很明显:@JavaProxy注释。
在此处使用变量将引发关于现有扩展的错误,而不是未定义的变量值。
第二个将在清单中。稍后再谈。
onCreate实例化服务时调用一次,onStartCommand每次服务启动onDestroy时调用,服务退出时调用。
服务如何启动和重新启动取决于
您从onStartCommand. 您可能很想回到START_STICKY这里,但这会在您的应用程序被终止时导致崩溃,因为系统会尝试重新启动您的服务null。
使其连续
到目前为止,我们有一个从您的应用程序开始的功能服务!但是当应用程序退出或被杀死时,我们如何让它继续运行呢?
让我们从制作广播接收器开始。
import CONTINUOUS_SERVICE_CLASSNAME from "./continuous-service.android";
export const RESTART_RECEIVER_CLASSNAME = "tk.ozymandias.ServiceExample.Restart_Receiver";
@JavaProxy("tk.ozymandias.ServiceExample.Restart_Receiver")
class Restart_Receiver extends android.content.BroadcastReceiver
onReceive(context: android.content.Context, intent: android.content.Intent): void
console.log("RESTART INTENT RECEIVED");
const serviceIntent = new android.content.Intent();
serviceIntent.setClassName(context, CONTINUOUS_SERVICE_CLASSNAME);
context.startService(serviceIntent);
然后让我们稍微修改一下我们的服务以在退出时调用广播接收器,以便它可以重新启动我们的服务。
// At the top
import RESTART_RECEIVER_CLASSNAME from "./restart-receiver.android";
// In the onDestroy method in our service
onDestroy(): void
// ...
const restartIntent = new android.content.Intent();
restartIntent.setClassName(this, RESTART_RECEIVER_CLASSNAME);
this.sendBroadcast(restartIntent);
您还应该onTaskRemoved在我们的服务中实现方法。
当用户从最近视图中滑动您的应用程序时调用它。
在这种情况下(可能还有其他情况)onDestroy,默认情况下不会调用。
所以让我们通过调用onDestroy来调用stopSelf!
// ...
onTaskRemoved(intent: android.content.Intent): void
console.log("TASK REMOVED");
this.stopSelf();
现在我们有一个持续运行的服务!当应用程序退出或被杀死时,我们调用广播接收器,
然后重新启动我们的服务。
不幸的是,在较新版本的 Android 中,当系统
由于内存不足或电池优化而终止您的应用程序时,onDestroy不能保证会被调用。
前台服务
幸运的是,有一种官方的方法可以解决这个问题。
我们需要的是让我们的服务成为前台服务。
缺点是我们必须提供持久通知,但是从 Oreo 开始,此通知可以从系统设置中隐藏,
而不会影响我们的服务。
我们需要再次修改我们的服务,这次是
onCreate方法:
// In the onCreate method in our service
onCreate(): void
// ...
const builder: android.app.Notification.Builder = new android.app.Notification.Builder(app.android.context);
// Need to check api level, NotificationChannel is required but only available on Oreo and above
if (android.os.Build.VERSION.SDK_INT >= 26)
const channel: android.app.NotificationChannel = new android.app.NotificationChannel(
"persistence", "Service running indicator", android.app.NotificationManager.IMPORTANCE_LOW
);
const manager: android.app.NotificationManager = (<android.app.Activity>app.android.context).getSystemService(android.content.Context.NOTIFICATION_SERVICE);
channel.enableLights(false);
channel.enableVibration(false);
manager.createNotificationChannel(channel);
builder.setChannelId("persistence");
const notification: android.app.Notification = builder.build();
this.startForeground(13, notification);
这使得持续的前台服务具有
持久的通知,
无论如何都会保持运行(它仍然可以从设置中强制停止)。
收尾工作
现在,如果您尝试到目前为止的代码,它将崩溃。
那是因为我们没有在
AndroidManifest.xml!
我们需要声明的是我们需要的权限(仅在最新版本的 Android 上)、服务和接收者。
事不宜迟,这是清单:
<?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-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<application
android:name="com.tns.NativeScriptApplication"
android:allowBackup="true"
android:icon="@drawable/icon"
android:label="@string/app_name"
android:theme="@style/AppTheme">
<activity
android:name="com.tns.NativeScriptActivity"
android:label="@string/title_activity_kimera"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|smallestScreenSize|screenLayout|locale|uiMode"
android:theme="@style/LaunchScreenTheme">
<meta-data android:name="SET_THEME_ON_LAUNCH" android:resource="@style/AppTheme" />
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name="com.tns.ErrorReportActivity"/>
<service android:enabled="true" android:name="tk.ozymandias.ServiceExample.Continuous_Service" />
<receiver
android:name="tk.ozymandias.ServiceExample.Restart_Receiver"
android:enabled="true"
android:exported="true"
android:label="ContinuousServiceRestarter" />
</application>
</manifest>
小彩蛋
您可能已经注意到,我们收到的通知是
通用的“应用程序正在运行”通知,点击时会转到设置。
我们可以做得更好!
// In the onCreate method in our service
onCreate(): void
// ...
const appIntent: android.content.Intent = new android.content.Intent(app.android.context, com.tns.NativeScriptActivity.class);
const pendingIntent: android.app.PendingIntent = android.app.PendingIntent.getActivity(app.android.context, 0, appIntent, 0);
const builder: android.app.Notification.Builder = new android.app.Notification.Builder(app.android.context);
builder
.setContentText("Custom notification, F'Yeah!")
.setSmallIcon(android.R.drawable.btn_star_big_on)
.setContentIntent(pendingIntent);
// ...
以上是关于使用 NativeScript 的 Android 持续后台服务的主要内容,如果未能解决你的问题,请参考以下文章
Nativescript - 在 Android 中隐藏状态栏
NativeScript 使用 Vue 获取当前的 android 上下文?
在 Android 上运行 nativescript 应用程序时出错
使用 NativeScript 的 Android 持续后台服务
尝试在 android (Nativescript-vue) 上使用 RadListView 时出现 Webpack 错误