美文网首页
NDK--双进程守护之利用线程轮询实现APP保活

NDK--双进程守护之利用线程轮询实现APP保活

作者: aruba | 来源:发表于2020-05-20 11:10 被阅读0次

对于开发者而言,始终希望自己的应用能够一直运行着,然而因用户操作,系统回收等原因,app很容易被杀死。目前保活的方法如下:

1.提高优先级
这个办法对普通应用而言,
应该只是降低了应用被杀死的概率,但是如果真的被系统回收了,还是无法让应用自动重新启动!

2 让service.onStartCommand返回START_STICKY
START_STICKY是service被kill掉后自动重启
通过实验发现,如果在adb shell当中kill掉进程模拟应用被意外杀死的情况(或者用360手机卫士进行清理操作),
如果服务的onStartCommand返回START_STICKY,
在进程管理器中会发现过一小会后被杀死的进程的确又会出现在任务管理器中,貌似这是一个可行的办法。
但是如果在系统设置的App管理中选择强行关闭应用,
这时候会发现即使onStartCommand返回了START_STICKY,应用还是没能重新启动起来!

3.android:persistent="true"
网上还提出了设置这个属性的办法,通过实验发现即使设置了这个属性,应用程序被kill之后还是不能重新启动起来的!

4.让应用成为系统应用(ROOT权限)
实验发现即使成为系统应用,被杀死之后也不能自动重新启动。
但是如果对一个系统应用设置了persistent="true",情况就不一样了
。实验表明对一个设置了persistent属性的系统应用,即使kill掉会立刻重启。
一个设置了persistent="true"的系统应用,
android中具有core service优先级,这种优先级的应用对系统的low memory killer是免疫的!

应用优先级

Android中的进程是托管的,当系统进程空间紧张的时候,会依照优先级自动进行进程的回收

Android将进程分为5个等级,它们按优先级顺序由高到低依次是:

● 空进程 Empty process
● 可见进程 Visible process
● 服务进程 Service process
● 后台进程 Background process
● 前台进程 Foreground process

由于之前的方法都不靠谱,双进程守护成为APP保活比较好的办法
在Java中开启双进程:

在组件中声明 android:process=":remote" 字段,Android系统会为我们开辟一个进程并且把这个组件丢到该进程中,开启两个进程互相拉起

Java实现双进程
如果被设置的进程名是以一个冒号开头的,则这个新的进程对于这个应用来说是私有的,
当它被需要或者这个服务需要在新进程中运行的时候,这个新进程将会被创建。
如果这个进程的名字是以小写字符开头的,则这个服务将运行在一个以这个名字命名的全局的进程中,
当然前提是它有相应的权限。这将允许在不同应用中的各种组件可以共享一个进程,从而减少资源的占用。

手机厂商不会允许这样的情况出现,Android系统在java层提出了双进程方案,
大部分手机厂商也会针对于系统源码进行修改。导致大部分双进程不能真正开启起来

手机厂商针对于Android系统源码容易修改,但是针对于Linux内核却无能为力
终极解决方案:

使用Jni,在 c端 fork进程,检测Service是否存活,若Service已被杀死,则进行重启Service.

关于Linux下多进程的概念:https://blog.csdn.net/wucz122140729/article/details/105113379
关于Linux下线程的使用:https://blog.csdn.net/wucz122140729/article/details/105112504
今天利用守护进程开启线程,不断轮询自身的父进程pid是否为1(父进程死亡后,子进程会被系统进程管理,即子进程的父进程pid为1),来实现进程被杀死后,守护进程重新拉起进程

首先在java中启动一个服务,并调用native方法开启守护进程

package com.aruba.processdaemonapplication;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.Process;
import android.support.annotation.Nullable;
import android.util.Log;

import java.util.Timer;
import java.util.TimerTask;

/**
 * Created by aruba on 2020/5/20.
 */
public class ProcessService extends Service {
    public final static String TAG = ProcessService.class.getSimpleName();
    int i=0;
    
    static {
        System.loadLibrary("native-lib");
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Wathcer wathcer = new Wathcer();
        wathcer.startDaemon(Process.myUid());

        Timer timer = new Timer();
        //定时器
        timer.scheduleAtFixedRate(
                new TimerTask() {
                    public void run() {
                        Log.i(TAG, "服务  " + i);
                        i++;
                    }
                }, 0, 1000 * 3);
    }
}

在c++中开启线程轮询,并拉起服务

#include <jni.h>
#include <string>
#include <unistd.h>
#include <android/log.h>

#define LOG_TAG "aruba"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)

int user_id;

void startDaemonProcess();

extern "C"
JNIEXPORT void JNICALL
Java_com_aruba_processdaemonapplication_Wathcer_startDaemon(JNIEnv *env, jobject instance,
                                                            jint userId) {
    user_id = userId;
    //开启守护进程
    startDaemonProcess();
}

void *thread_rt(void *data) {
    pid_t pid;
    while ((pid = getppid()) != 1) {
        sleep(2);
        LOGD("循环 %d ", pid);
    }

    //父进程等于1  app被干掉了
    LOGD("重启父进程");
    execlp("am", "am", "startservice", "--user", user_id,
           "com.aruba.processdaemonapplication/com.aruba.processdaemonapplication.ProcessService", (char *) NULL);
}

void startDaemonProcess() {
    pid_t pid = fork();
    if (pid > 0) {//父进程

    } else {//子进程,创建线程,开启轮询
        pthread_t tid;

        //参数1为为指向线程标识符的地址。
        //参数2用于设置线程属性,一般为空,表示使用默认属性。
        //参数3是线程运行函数的地址,填函数名就可以了。
        //参数4是线程运行函数的参数。新创建的线程从thread_rt函数的地址开始运行,
        // 该函数只有一个无类型指针参数arg。若要想向thread_rt传递多个参数,
        // 可以将多个参数放在一个结构体中,然后把结构体的地址作为arg参数传入,
        // 但是要非常慎重,程序员一般不会这么做。
        pthread_create(&tid, NULL, thread_rt, NULL);
    }
}
应用启动后,使用ps命令查看进程
可以发现守护进程已经起来了,它的父进程pid为20605,我们手动杀掉APP,再使用ps命令查看进程
发现成功的拉起的服务,在服务中打印的日志也正常从头开始打印
和卸载监听同样的,虽然厂商一般不会修改fork函数,但可能修改am命令而导致服务不能够被拉起,保活是绝对不可能做到100%的!
项目地址:https://gitee.com/aruba/ProcessDaemonApplication.git

相关文章

网友评论

      本文标题:NDK--双进程守护之利用线程轮询实现APP保活

      本文链接:https://www.haomeiwen.com/subject/rwcgohtx.html