service进程——保活和拉起

存在问题

最近在做一个项目A,该项目A是已经是system app,但该App在内存不足、用户清理后台后,进程会死亡。为了保证A能一直处于运行状态,开发一个守护进程用于保活和拉起A。
需求:

  1. 在开机后,A不能自启动,保证设备快速开机运行
  2. 用户清理后台后,A能继续存活,以便接听实时通话
  3. 在应用关闭后,A能够保活
  4. 内存占用过大,系统释放内存后,A和service能存活
  5. 重启后,service能够自启动

理论知识准备

service讲解

service基础知识

判断APP处于前台还是后台

6种判断方法

进程优先级

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是否存活

  1. 获取系统所有的app信息,首先遍历所有的app
  2. 通过进程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">

注意点:

  1. MyActivity extends 只能是activity,不能为其他的,比如AppCompatActivity(support_v4/v7,带actionbar或者toolbar)等主题与activity不搭导致app运行错误。
  2. 既然是隐藏的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。

由于作者水平有限,文中若有不正确的地方,欢迎大家指出,若有任何问题,请在下方讨论。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猫哥说

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值