存在问题
最近在做一个项目A,该项目A是已经是system app,但该App在内存不足、用户清理后台后,进程会死亡。为了保证A能一直处于运行状态,开发一个守护进程用于保活和拉起A。
需求:
- 在开机后,A不能自启动,保证设备快速开机运行
- 用户清理后台后,A能继续存活,以便接听实时通话
- 在应用关闭后,A能够保活
- 内存占用过大,系统释放内存后,A和service能存活
- 重启后,service能够自启动
理论知识准备
service讲解
判断APP处于前台还是后台
进程优先级
Android系统尽量长时间保留应用进程,为了新建进程或运行更加重要的进程,系统会移除旧的进程,回收内存。系统根据重要层级结构中的优先级,优先清除重要性低的进程。
前台进程(Foreground process)
前台进程数量不多,只有在内存不足的情况下,系统才会终止他们。
- 托管用户正在交互的Activity,已调用Activity的onResume()方法
- 托管某个Service,后者绑定到用户正在交互的 Activity
- 托管正在前台运行的Service服务,已调用startForeground()
- 托管正执行一个生命周期回调的Service (onCreate() 、onStart()或onDestroy())
- 托管正执行其onReceive()方法的BroadcastReceiver
可见进程(Visible process)
除非为了保证前台进程的运行,而不得已终止,系统不会终止可见进程。
- 托管不在前台、但仍对用户可见的Activity已调用其onPause()方法。
- 托管绑定到可见(或前台)Activity 的Service。
服务进程(Service process)
- 正在运行 startService() 方法启动的服务,且不属于上述两个更高类别进程的进程。
系统尽量维持服务进程的运行,除非系统内存不足维护前台进程和可见进程的运行。
后台进程(Background process)
- 进程持有一个用户不可见的activity,onStop()别调用,onDestory()没被调用,该进程为一个后台进程。
后台进程不影响用户的体验,系统为了前台进程、可见进程和服务进程,可任意杀死后台进程。
空进程(Empty process)
- 不包含任何活动应用组件的进程
保留这类进程的目的用于缓存,以便缩短下一次在其中运行组件运行所需的启动时间。
常见的保活方案
- 开启一个像素的Activity
- 前台服务
白色保活:启动前台服务使得进程的优先级提高为前台进程,但前台服务和通知绑定,在通知栏会显示,用户可能会删除通知。
灰色保活:利用系统漏洞开启前台服务,去掉通知,用户感应不到。 - 相互唤醒
黑色保活:相互拉起,腾讯系列。 - JobSheduler
- 粘性服务
- 双进程
让两个进程互相保护,其中一个service被清理后,未被清理的进程重启该进程,甚至有多进程, - service设置成START_STICKY,被杀死5秒后会重启,startForegroundService将该进程设置为前台进程。
- 加入白名单
解决方案
获得系统级权限
由于项目A已是system app,在AndroidManifest中添加android:sharedUserId=“android.uid.system”
通过设置同一个User id的使得多个应用可以运行在同一个进程中。而将sharedUserId设置成android.uid.system则可以将该应用和系统应用运行在同一进程中,于是乎便有了系统权限。
判断APP是否存活
- 获取系统所有的app信息,首先遍历所有的app
- 通过进程id去判断该进程是否存活,如果不存活,发送广播,如果存活,不处理。在BroadcastReceiver中,判断action,启动activity。
//根据进程名判断进程是否运行,并返回pid
public static int getPidByProcessName(Context context, String procName) {
ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningAppProcessInfo> processInfoList = manager.getRunningAppProcesses();
if(processInfoList!=null){
for (ActivityManager.RunningAppProcessInfo processInfo : processInfoList) {
LogUtils.d("Utils processName = " + processInfo.processName);
if (procName.equals(processInfo.processName)) {
LogUtils.d("Uitls pid = " + processInfo.pid);
return processInfo.pid;
}
}
}
return -1;
}
去除activiy界面
无界面的app(对于非系统的应用没有activity无法启动service),但可以使用主题@android:style/Theme.NoDisplay 来隐藏Activity达到无界面的app效果。
<application
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@android:style/Theme.NoDisplay">
注意点:
- MyActivity extends 只能是activity,不能为其他的,比如AppCompatActivity(support_v4/v7,带actionbar或者toolbar)等主题与activity不搭导致app运行错误。
- 既然是隐藏的app,那么桌面图标和后台任务也不能让用户看到。经过测试验证,若不隐藏桌面图标和后台任务,点击后会出现无响应的问题,像是死机的样子。
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:screenOrientation="portrait"
android:launchMode="singleInstance"
android:excludeFromRecents="true" //后台任务图标设置为true
>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<!--<category android:name="android.intent.category.LAUNCHER" />--> //桌面启动图标去除
</intent-filter>
</activity>
<service
android:name=".service.TestService"
android:enabled="true"
android:exported="true"></service>
在activity中重写onResume()方法,杀死Activity。
@Override
protected void onResume() {
super.onResume();
this.finish();
}
守护进程
静态配置接收器
守护进程开机自启动
<receiver
android:name=".service.TestReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
</receiver>
广播接收器类的实现
Receiver中接受,并判断action
public class TestReceiver extends BroadcastReceiver {
public final static String TEST_ACTION_BOOT_COMPLETED="android.intent.action.BOOT_COMPLETED";
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if(TEST_ACTION_BOOT_COMPLETED.equals(action)){
Intent intentStart = new Intent(context, MainActivity.class);
intentStart.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intentStart);
}
}
}
Service
采用getPidByProcessName方法定期判断进程是否存活,如果存活不做处理;如果不存活,向进程A发送一个广播。
在进程A配置action,在收到广播后,拉起对应的Activity。
由于作者水平有限,文中若有不正确的地方,欢迎大家指出,若有任何问题,请在下方讨论。