Android 進(jìn)程保活 的兩種實(shí)現(xiàn)方式

前言

目前市場(chǎng)上主流的項(xiàng)目應(yīng)用app,在其進(jìn)程被殺掉之后,還是可以繼續(xù)運(yùn)行在后臺(tái)(?;睿?;比如,微信,淘寶,釘釘,QQ等。類(lèi)似耍流氓,保證應(yīng)用進(jìn)程不被殺死。當(dāng)然優(yōu)雅的說(shuō)法:常駐進(jìn)程。不過(guò)現(xiàn)在各個(gè)手機(jī)廠商都有白名單,將應(yīng)用加入到白名單,可100%解決進(jìn)程?;畹男枨?。

我是個(gè)小安卓.jpg

差強(qiáng)人意的方法

網(wǎng)上給一些常見(jiàn)的方法:

  1. 提高優(yōu)先級(jí)
    這個(gè)辦法對(duì)普通應(yīng)用而言,
    應(yīng)該只是降低了應(yīng)用被殺死的概率,但是如果真的被系統(tǒng)回收了,還是無(wú)法讓?xiě)?yīng)用自動(dòng)重新啟動(dòng)!

  2. 讓service.onStartCommand返回START_STICKY,START_STICKY是service被kill掉后自動(dòng)重啟
    ?;?00%
    通過(guò)實(shí)驗(yàn)發(fā)現(xiàn),如果在adb shell當(dāng)中kill掉進(jìn)程模擬應(yīng)用被意外殺死的情況(或者用360手機(jī)衛(wèi)士進(jìn)行清理操作),
    如果服務(wù)的onStartCommand返回START_STICKY,
    在進(jìn)程管理器中會(huì)發(fā)現(xiàn)過(guò)一小會(huì)后被殺死的進(jìn)程的確又會(huì)出現(xiàn)在任務(wù)管理器中,貌似這是一個(gè)可行的辦法。
    但是如果在系統(tǒng)設(shè)置的App管理中選擇強(qiáng)行關(guān)閉應(yīng)用,這時(shí)候會(huì)發(fā)現(xiàn)即使onStartCommand返回了START_STICKY,應(yīng)用還是沒(méi)能重新啟動(dòng)起來(lái)!

  3. android:persistent="true"
    網(wǎng)上還提出了設(shè)置這個(gè)屬性的辦法,通過(guò)實(shí)驗(yàn)發(fā)現(xiàn)即使設(shè)置了這個(gè)屬性,應(yīng)用程序被kill之后還是不能重新啟動(dòng)起來(lái)的!

  4. 讓?xiě)?yīng)用成為系統(tǒng)應(yīng)用
    實(shí)驗(yàn)發(fā)現(xiàn)即使成為系統(tǒng)應(yīng)用,被殺死之后也不能自動(dòng)重新啟動(dòng)。
    但是如果對(duì)一個(gè)系統(tǒng)應(yīng)用設(shè)置了persistent="true",情況就不一樣了。
    實(shí)驗(yàn)表明對(duì)一個(gè)設(shè)置了persistent屬性的系統(tǒng)應(yīng)用,即使kill掉會(huì)立刻重啟。
    一個(gè)設(shè)置了persistent="true"的系統(tǒng)應(yīng)用,
    android中具有core service優(yōu)先級(jí),這種優(yōu)先級(jí)的應(yīng)用對(duì)系統(tǒng)的low memory killer是免疫的!

  5. 設(shè)置鬧鐘,定時(shí)喚醒
    這個(gè)效果是百分百的,但是不符合實(shí)際業(yè)務(wù)場(chǎng)景。

應(yīng)用優(yōu)先級(jí)

Android中的進(jìn)程是托管的,當(dāng)系統(tǒng)進(jìn)程空間緊張的時(shí)候,會(huì)依照優(yōu)先級(jí)自動(dòng)進(jìn)行進(jìn)程的回收
Android將進(jìn)程分為5個(gè)等級(jí),它們按優(yōu)先級(jí)順序由高到低依次是:

  • 空進(jìn)程 Empty process
  • 可見(jiàn)進(jìn)程 Visible process
  • 服務(wù)進(jìn)程 Service process
  • 后臺(tái)進(jìn)程 Background process
  • 前臺(tái)進(jìn)程 Foreground process

如何在程序殺死的清下重啟進(jìn)程-----SIGLE信號(hào)

  • 思路
  1. 利用am命令,啟動(dòng)主進(jìn)程的一個(gè)service
  2. SIGLE信號(hào),通過(guò)SIGLE信號(hào)來(lái)判斷程序是否被殺死
    在Linux系統(tǒng)下,如果使用sigaction將信號(hào)SIGCHLD的sa_flags中的SA_NOCLDSTOP選項(xiàng)打開(kāi),
    當(dāng)子進(jìn)程停止(STOP作業(yè)控制)時(shí),
    不產(chǎn)生此信號(hào)(即SIGCHLD)。不過(guò),當(dāng)子進(jìn)程終止時(shí),仍舊產(chǎn)生此信號(hào)(即SIGCHLD)。
    僵尸

sigaction函數(shù):
函數(shù)功能是:檢查或修改與指定信號(hào)相關(guān)聯(lián)的處理動(dòng)作

sigaction(SIGCHLD, &sa, NULL);

wait()函數(shù)
函數(shù)功能是:父進(jìn)程一旦調(diào)用了wait就立即阻塞自己,由wait自動(dòng)分析是否當(dāng)前進(jìn)程的某個(gè)子進(jìn)程已經(jīng)退出,如果讓它找到了這樣一個(gè)已經(jīng)變成僵尸的子進(jìn)程,wait就會(huì)收集這個(gè)子進(jìn)程的信息,并把它徹底銷(xiāo)毀后返回;如果沒(méi)有找到這樣一個(gè)子進(jìn)程,wait就會(huì)一直阻塞在這里,直到有一個(gè)出現(xiàn)為止。

int status;
wait(&status);

查看Android進(jìn)程


Android手機(jī)進(jìn)程查看.png

uid Android用戶id 號(hào)
pid 當(dāng)前的進(jìn)程號(hào)
ppid 當(dāng)前進(jìn)程的父進(jìn)程號(hào)


敲代碼.jpg

開(kāi)始擼碼

由于上面講的內(nèi)容都是在c++實(shí)現(xiàn)的,所以搞個(gè)jni工程

  • 創(chuàng)建native方法
  public native void watcher(String userId, int processId);
  • 主進(jìn)程創(chuàng)建一個(gè)service,用來(lái)在主進(jìn)程被殺的時(shí)候,通過(guò)am命令進(jìn)行重啟主進(jìn)程
public class KeepProcessService extends Service {

  private static final String TAG = "BAO";
  private int i = 0;

  @Override
  public void onCreate() {
    super.onCreate();
    ProcessWatcher watcher = new ProcessWatcher();
    watcher.watcher(String.valueOf(Process.myUid()),  Process.myPid());

    Timer timer = new Timer();
    timer.scheduleAtFixedRate(new TimerTask() {
      @Override
      public void run() {
        Log.i(TAG, "服務(wù)進(jìn)程,運(yùn)行中 i = "+i);
        i++;
      }
    }, 0,  3000);
  }

  ......省略其他代碼
}
  • C++的實(shí)現(xiàn)
const char *_user_id;
int _process_id;

//子進(jìn)程變成僵尸進(jìn)程會(huì)調(diào)用這個(gè)方法
void sig_handler(int sino) {

    int status;
    //阻塞式函數(shù)
    LOGE("等待死亡信號(hào)");
    wait(&status);

    LOGE("創(chuàng)建進(jìn)程");
    create_child_process();
}


extern "C"
JNIEXPORT void JNICALL
Java_com_jason_signal_process_ProcessWatcher_watcher(JNIEnv *env, jobject thiz, jstring user_id, jint process_id) {
    _process_id = process_id;
    _user_id = env->GetStringUTFChars(user_id, NULL);
    //為了防止子進(jìn)程被弄成僵尸進(jìn)程
    struct  sigaction sa;
    sa.sa_flags=0;

    sa.sa_handler = sig_handler;
    sigaction(SIGCHLD, &sa, NULL);
    create_child_process();
}


void create_child_process() {

    //創(chuàng)建一個(gè)子進(jìn)程
    pid_t pid = fork();

    if(pid < 0) {
        LOGE("創(chuàng)建子進(jìn)程失敗!");
    } else if(pid > 0 && pid < getppid()) {
        LOGE("這個(gè)是父進(jìn)程!");
    } else {  
        LOGE("創(chuàng)建子進(jìn)程成功!");
        LOGE("進(jìn)程PID是%d", getpid());
        LOGE("進(jìn)程PPID是%d", getppid());
        LOGE("創(chuàng)建的子進(jìn)程ID:%d", pid);
        create_process_monitor();
       
    }
}


void *thread_fun_signal(void *data) {
    //ppid 表示的是父進(jìn)程號(hào)  pid表示當(dāng)前進(jìn)程號(hào)
    pid_t pid;
    while((pid = getppid()) != 1) {
        sleep(2);
        LOGE("循環(huán) %d ",pid);
    }
    //當(dāng)子進(jìn)程的父進(jìn)程號(hào)等于1 ,表示主進(jìn)程被殺死了,子進(jìn)程被init進(jìn)程托管了
    LOGE("重啟父進(jìn)程");
    // 用am命令 啟動(dòng)KeepProcessService,來(lái)啟動(dòng)主進(jìn)程
    execlp("am", "am", "startservice", "--user", _user_id,
           "com.jason.signal.process/com.jason.signal.process.KeepProcessService", (char*)NULL);
}

//創(chuàng)建一個(gè)線程
void create_process_monitor() {
    pthread_t  pt_t;
    pthread_create(&pt_t, NULL, thread_fun_signal,  NULL);
}

以上就是利用Android的linux內(nèi)核的signal信號(hào)來(lái),重啟被殺掉的進(jìn)程。

如何在程序殺死的清下重啟進(jìn)程-----socket方式 進(jìn)程間通信

  • 思路
  1. 創(chuàng)建一個(gè)子進(jìn)程作為socket的的服務(wù)端
  2. 將主進(jìn)程作為客戶端,通過(guò)socket進(jìn)行連接,當(dāng)主進(jìn)程被殺死之后,子進(jìn)程服務(wù)端會(huì)受到一個(gè)主進(jìn)程被殺的消息,這個(gè)時(shí)候通過(guò)am命令啟動(dòng)service重新啟動(dòng)主進(jìn)程。
  • 介紹函數(shù)
  1. int socket()函數(shù)
int  socket(int protofamily, int type, int protocol);//返回sockfd

socket函數(shù)對(duì)應(yīng)于普通文件的打開(kāi)操作。普通文件的打開(kāi)操作返回一個(gè)文件描述字,而socket()用于創(chuàng)建一個(gè)socket描述符(socket descriptor),它唯一標(biāo)識(shí)一個(gè)socket。這個(gè)socket描述字跟文件描述字一樣,后續(xù)的操作都有用到它,把它作為參數(shù),通過(guò)它來(lái)進(jìn)行一些讀寫(xiě)操作。

參數(shù) 說(shuō)明
protofamily 即協(xié)議域,又稱為協(xié)議族(family)。常用的協(xié)議族有,AF_INET(IPV4)、AF_INET6(IPV6)、AF_LOCAL(或稱AF_UNIX,Unix域socket)、AF_ROUTE等等。協(xié)議族決定了socket的地址類(lèi)型
type 指定socket類(lèi)型, 常用的socket類(lèi)型SOCK_STREAM IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它們分別對(duì)應(yīng) 流協(xié)議,TCP傳輸協(xié)議、UDP傳輸協(xié)議、 STCP傳輸協(xié)議、TIPC傳輸協(xié)議
protocol socket支持哪些協(xié)議,https://www.cnblogs.com/liyuanhong/articles/10591069.html
  1. bind()函數(shù)
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

參數(shù)介紹:

參數(shù) 說(shuō)明
sockfd socket描述字,它是通過(guò)socket()函數(shù)創(chuàng)建了,唯一標(biāo)識(shí)一個(gè)socket。bind()函數(shù)就是將給這個(gè)描述字綁定一個(gè)socket
addr 一個(gè)const struct sockaddr *指針,指向要綁定給sockfd的協(xié)議地址。這個(gè)地址結(jié)構(gòu)根據(jù)地址創(chuàng)建socket時(shí)的地址協(xié)議族的不同而不同
addrlen 對(duì)應(yīng)的是地址的長(zhǎng)度
醒醒改不bug.jpg

開(kāi)始擼碼

  • 創(chuàng)建native方法
  public native void watch(String userId);
  public native void connect();
  • 同上方法創(chuàng)建service,在service的oncreate,進(jìn)行socket的創(chuàng)建和連接
watcher.watch(String.valueOf(Process.myUid()));
watcher.connect();
  • C++的實(shí)現(xiàn):子進(jìn)程創(chuàng)建socket的服務(wù)單,主進(jìn)程進(jìn)行連接
int m_child;

const char *userId;
const char *PATH = "/data/data/com.jason.socket.process/my.sock";

extern "C"
JNIEXPORT void JNICALL
Java_com_jason_socket_process_Watcher_watch(JNIEnv *env, jobject thiz, jstring user_id) {

    userId = env->GetStringUTFChars(user_id, NULL);
    create_child_process();
}

void create_child_process() {
    pid_t pid = fork();
    if(pid < 0) {
    } else if (pid > 0) {
    } else {
        do_child_work();
    }
}

void do_child_work() {
    //1 在子進(jìn)程建立socket服務(wù),作為服務(wù)端,等待父進(jìn)程連接
    //2 讀取消息來(lái)自父進(jìn)程的消息:這邊唯一的消息是父進(jìn)程被殺掉
    if(create_socket_server()) {
        child_listen_msg();
    }
}

int create_socket_server() {
    //1 創(chuàng)建socket對(duì)象
    int listenId = socket(AF_LOCAL, SOCK_STREAM, 0);
    //2 斷開(kāi)之前的連接
    unlink(PATH);
    struct sockaddr_un addr;
    //3 清空內(nèi)存
    memset(&addr, 0, sizeof(sockaddr_un));
    addr.sun_family = AF_LOCAL;

    strcpy(addr.sun_path, PATH);
    int connfd = 0;
    LOGE("綁定端口號(hào)");
    if(bind(listenId, (const sockaddr *) &addr, sizeof(addr))<0) {
        LOGE("綁定錯(cuò)誤");
        return 0;
    }
    //設(shè)置最大的連接數(shù)
    listen(listenId, 5);
    while (1) {
        LOGE("子進(jìn)程循環(huán)等待連接  %d ",m_child);
        // 不斷接受客戶端請(qǐng)求的數(shù)據(jù)
        // 等待 客戶端連接  accept阻塞式函數(shù)
        if ((connfd = accept(listenId, NULL, NULL)) < 0) {
            if (errno == EINTR) {
                continue;
            } else{
                LOGE("讀取錯(cuò)誤");
                return 0;
            }
        }
        //apk 進(jìn)程連接上了
        m_child = connfd;
        LOGE("apk 父進(jìn)程連接上了  %d ",m_child);
        break;
    }
    LOGE("返回成功");
    return 1;
}

void child_listen_msg() {

    fd_set rfds;
    while (1) {
        // 清空端口號(hào)
        FD_ZERO(&rfds);
        // 設(shè)置新的端口號(hào)
        FD_SET(m_child,&rfds);
        // 設(shè)置超時(shí)時(shí)間
        struct timeval timeout={3,0};
        int r = select(m_child + 1, &rfds, NULL, NULL, &timeout);
        LOGE("讀取消息前  %d  ",r);
        if (r > 0) {
            char pkg[256] = {0};
            // 確保讀到的內(nèi)容是制定的端口號(hào)
            if (FD_ISSET(m_child, &rfds)) {
                // 阻塞式函數(shù)  客戶端寫(xiě)到內(nèi)容
                int result = read(m_child, pkg, sizeof(pkg));
                // 讀到內(nèi)容的唯一方式 是客戶端斷開(kāi)
                LOGE("重啟父進(jìn)程  %d ",result);
                LOGE("讀到信息  %d    userid  %d ",result, userId);
                execlp("am", "am", "startservice", "--user", userId,
                       "com.jason.socket.process/com.jason.socket.process.KeepProcessService", (char*)NULL);
                break;
            }
        }
    }
}


extern "C"
JNIEXPORT void JNICALL
Java_com_jason_socket_process_Watcher_connect(JNIEnv *env, jobject thiz) {

    //主進(jìn)程socket連接父進(jìn)程
    int sockfd;
    struct sockaddr_un  addr;
    while (1) {
        LOGE("客戶端  父進(jìn)程開(kāi)始連接");
        sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
        if (sockfd < 0) {
            return;
        }
        memset(&addr, 0, sizeof(sockaddr_un));
        addr.sun_family = AF_LOCAL;
        strcpy(addr.sun_path, PATH);
        if (connect(sockfd, (const sockaddr *) &addr, sizeof(addr)) < 0) {
            LOGE("連接失敗  休眠");
            // 連接失敗
            close(sockfd);
            sleep(1);
            // 再來(lái)繼續(xù)下一次嘗試
            continue;
        }
        // 連接成功
        m_parent = sockfd;
        LOGE("連接成功  父進(jìn)程跳出循環(huán)");
        break;
    }
}

以上就是通過(guò)socket進(jìn)行進(jìn)程間通信,來(lái)實(shí)現(xiàn)進(jìn)程?;?。

結(jié)語(yǔ)

上面兩種進(jìn)程被殺重啟的方式,只能實(shí)現(xiàn)支持大部分的手機(jī),有部分廠商進(jìn)行底層修改。這兩種只是提供了兩種思路方案。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容