Android service daemon using JobScheduler
保活思路 :
1. 将Service设置为前台服务而不显示通知
D-clock :
思路一:API < 18,启动前台Service时直接传入new Notification();
思路二:API >= 18,同时启动两个id相同的前台Service,然后再将后启动的Service做stop处理;
前台服务相对于后台服务的优势,除了优先级的提升以外,还有一点:
在最近任务列表中划掉卡片时,前台服务不会停止;
(更新:经过测试,发现只是对于AOSP/CM/国际上对Framework层改动较小的Android系统是成立的;EMUI/MIUI等未加入白名单的情况下,划掉卡片,前台服务也会停止;加入白名单后划掉卡片的行为与国际厂商的系统相似。)
而后台服务会停止,并在稍后重新启动(onStartCommand 返回 START_STICKY 时)。
前台服务和后台服务被划掉卡片时,回调的都是 onTaskRemoved 方法。
onDestroy 方法只在 设置 -> 开发者选项 -> 正在运行的服务 里停止服务时才会回调。
2. 在 Service 的 onStartCommand 方法里返回 START_STICKY
3. 覆盖 Service 的 onDestroy/onTaskRemoved 方法, 保存数据到磁盘, 然后重新拉起服务
4. 监听 8 种系统广播 :
CONNECTIVITY_CHANGE, USER_PRESENT, ACTION_POWER_CONNECTED, ACTION_POWER_DISCONNECTED, BOOT_COMPLETED, PACKAGE_ADDED, PACKAGE_REMOVED.
在网络连接改变, 用户屏幕解锁, 电源连接 / 断开, 系统启动完成, 安装 / 卸载软件包时拉起 Service.
Service 内部做了判断,若 Service 已在运行,不会重复启动.
5. 开启守护服务 : 定时检查服务是否在运行,如果不在运行就拉起来
6. 守护 Service 组件的启用状态, 使其不被 MAT 等工具禁用
HelloDeamo库的使用
1. 添加依赖
build.gradle 中添加
compile 'com.xdandroid:hellodaemon:+'
2. 继承 AbsWorkService, 实现 6 个抽象方法
/**
* 是否 任务完成, 不再需要服务运行?
* @return 应当停止服务, true; 应当启动服务, false; 无法判断, null.
*/
Boolean shouldStopService();
/**
* 任务是否正在运行?
* @return 任务正在运行, true; 任务当前不在运行, false; 无法判断, null.
*/
Boolean isWorkRunning();
void startWork();
void stopWork();
//Service.onBind(Intent intent)
@Nullable IBinder onBind(Intent intent, Void unused);
//服务被杀时调用, 可以在这里面保存数据.
void onServiceKilled();
别忘了在 Manifest 中注册这个 Service.
3. 自定义 Application
在 Application 的 onCreate()
中, 调用
DaemonEnv.initialize(
Context app, //Application Context.
Class<? extends AbsWorkService> serviceClass, //刚才创建的 Service 对应的 Class 对象.
@Nullable Integer wakeUpInterval); //定时唤醒的时间间隔(ms), 默认 6 分钟.
Context.startService(new Intent(Context app, Class<? extends AbsWorkService> serviceClass));
别忘了在 Manifest 中通过 android:name 使用这个自定义的 Application.
4. API 说明
启动 Service:
Context.startService(new Intent(Context c, Class<? extends AbsWorkService> serviceClass))
停止 Service:
在 ? extends AbsWorkService 中, 添加 stopService()
方法:
1.操作自己维护的 flag, 使 shouldStopService()
返回 true
;
2.调用自己的方法或第三方 SDK 提供的 API, 停止任务;
3.调用 AbsWorkService.cancelJobAlarmSub()
取消 Job / Alarm / Subscription.
需要停止服务时, 调用 ? extends AbsWorkService 上的 stopService()
即可.
Demo演示
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.xdandroid.sample">
<application
android:name=".App"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name">
<activity
android:name=".MainActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<service android:name=".TraceServiceImpl"/>
</application>
</manifest>
package com.xdandroid.sample;
import android.app.*;
import com.xdandroid.hellodaemon.*;
public class App extends Application {
@Override
public void onCreate() {
super.onCreate();
//需要在 Application 的 onCreate() 中调用一次 DaemonEnv.initialize()
DaemonEnv.initialize(this, TraceServiceImpl.class, DaemonEnv.DEFAULT_WAKE_UP_INTERVAL);
TraceServiceImpl.sShouldStopService = false;
DaemonEnv.startServiceMayBind(TraceServiceImpl.class);
}
}
package com.xdandroid.sample;
import android.content.*;
import android.os.*;
import com.xdandroid.hellodaemon.*;
import java.util.concurrent.*;
import io.reactivex.*;
import io.reactivex.disposables.*;
public class TraceServiceImpl extends AbsWorkService {
//是否 任务完成, 不再需要服务运行?
public static boolean sShouldStopService;
public static Disposable sDisposable;
public static void stopService() {
//我们现在不再需要服务运行了, 将标志位置为 true
sShouldStopService = true;
//取消对任务的订阅
if (sDisposable != null) sDisposable.dispose();
//取消 Job / Alarm / Subscription
cancelJobAlarmSub();
}
/**
* 是否 任务完成, 不再需要服务运行?
* @return 应当停止服务, true; 应当启动服务, false; 无法判断, 什么也不做, null.
*/
@Override
public Boolean shouldStopService(Intent intent, int flags, int startId) {
return sShouldStopService;
}
@Override
public void startWork(Intent intent, int flags, int startId) {
System.out.println("检查磁盘中是否有上次销毁时保存的数据");
sDisposable = Observable
.interval(3, TimeUnit.SECONDS)
//取消任务时取消定时唤醒
.doOnDispose(() -> {
System.out.println("保存数据到磁盘。");
cancelJobAlarmSub();
})
.subscribe(count -> {
System.out.println("每 3 秒采集一次数据... count = " + count);
if (count > 0 && count % 18 == 0) System.out.println("保存数据到磁盘。 saveCount = " + (count / 18 - 1));
});
}
@Override
public void stopWork(Intent intent, int flags, int startId) {
stopService();
}
/**
* 任务是否正在运行?
* @return 任务正在运行, true; 任务当前不在运行, false; 无法判断, 什么也不做, null.
*/
@Override
public Boolean isWorkRunning(Intent intent, int flags, int startId) {
//若还没有取消订阅, 就说明任务仍在运行.
return sDisposable != null && !sDisposable.isDisposed();
}
@Override
public IBinder onBind(Intent intent, Void v) {
return null;
}
@Override
public void onServiceKilled(Intent rootIntent) {
System.out.println("保存数据到磁盘。");
}
}
package com.xdandroid.sample;
import android.app.*;
import android.os.*;
import android.view.*;
import com.xdandroid.hellodaemon.*;
public class MainActivity extends Activity {
protected void onCreate(Bundle b) {
super.onCreate(b);
setContentView(R.layout.activity_main);
}
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_start:
TraceServiceImpl.sShouldStopService = false;
DaemonEnv.startServiceMayBind(TraceServiceImpl.class);
break;
case R.id.btn_white:
IntentWrapper.whiteListMatters(this, "轨迹跟踪服务的持续运行");
break;
case R.id.btn_stop:
TraceServiceImpl.stopService();
break;
}
}
//防止华为机型未加入白名单时按返回键回到桌面再锁屏后几秒钟进程被杀
public void onBackPressed() {
IntentWrapper.onBackPressed(this);
}
}