android雙進程守護,讓程序崩潰后一定可以重啟


由於我們做的是機器人上的軟件,而機器人是24小時不間斷服務的,這就要求我們的軟件不能退出到系統桌面。當然最好是能夠做到程序能夠不卡頓,不崩潰,自己不退出。由於我們引用了很多第三方的開發包,也不能保證他們的穩定性,所以,要做到完全不崩潰也是不可能的。

退而求其次,如果崩潰了我們就要保證程序能夠被拉起來,期間也看過很多保活的方案,比如service前台的方法,比如jni里寫守護進程,比如接收系統廣播喚醒,比如用alarmmanager喚醒等等,感覺不是效率底,就是被系統屏蔽了。經過不斷篩選,我認為使用aidl進行雙進程守護其實是效率很好的一個解決方案。

其實這個原理也很簡單,簡單的說就是創建兩個service,其中一個再程序主進程,另外一個在其他進程,這兩個進程通過aidl通信,一旦其中一個進程斷開連接,那么就重啟該服務,兩個程序互相監聽,就能夠做到一方被殺死,另一方被啟動了。當然,如果使用 adb shell force-stop packageName的方法殺死程序,肯定是不能夠重啟的。這種方式僅僅是為了避免ndk層崩潰,java抓不到從而不能使用java層重啟應用的一種補充方式。要想做到完全不被殺死,那就太流氓了。

說了這么多,看代碼吧

兩個service,localservice和remoteservice

LocalService.java

 1 package guide.yunji.com.guide.processGuard;
 2 
 3 import android.app.Application;
 4 import android.app.Service;
 5 import android.content.ComponentName;
 6 import android.content.Context;
 7 import android.content.Intent;
 8 import android.content.ServiceConnection;
 9 import android.os.IBinder;
10 import android.os.RemoteException;
11 import android.util.Log;
12 import android.widget.Toast;
13 
14 import guide.yunji.com.guide.MyApplication;
15 import guide.yunji.com.guide.activity.MainActivity;
16 import guide.yunji.com.guide.testFace.IMyAidlInterface;
17 
18 public class LocalService extends Service {
19     private static final String TAG = LocalService.class.getName();
20     private MyBinder mBinder;
21 
22     private ServiceConnection connection = new ServiceConnection() {
23         @Override
24         public void onServiceConnected(ComponentName name, IBinder service) {
25             IMyAidlInterface iMyAidlInterface = IMyAidlInterface.Stub.asInterface(service);
26             try {
27                 Log.e("LocalService", "connected with " + iMyAidlInterface.getServiceName());
28                 //TODO whh 本地service被拉起,檢測如果mainActivity不存在則拉起
29                 if (MyApplication.getMainActivity() == null) {
30                     Intent intent = new Intent(LocalService.this.getBaseContext(), MainActivity.class);
31                     intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
32                     getApplication().startActivity(intent);
33                 }
34             } catch (RemoteException e) {
35                 e.printStackTrace();
36             }
37         }
38 
39         @Override
40         public void onServiceDisconnected(ComponentName name) {
41             Toast.makeText(LocalService.this, "鏈接斷開,重新啟動 RemoteService", Toast.LENGTH_LONG).show();
42             Log.e(TAG, "onServiceDisconnected: 鏈接斷開,重新啟動 RemoteService");
43             startService(new Intent(LocalService.this, RemoteService.class));
44             bindService(new Intent(LocalService.this, RemoteService.class), connection, Context.BIND_IMPORTANT);
45         }
46     };
47 
48     public LocalService() {
49     }
50 
51     @Override
52     public void onCreate() {
53         super.onCreate();
54     }
55 
56     @Override
57     public int onStartCommand(Intent intent, int flags, int startId) {
58         Log.e(TAG, "onStartCommand: LocalService 啟動");
59         Toast.makeText(this, "LocalService 啟動", Toast.LENGTH_LONG).show();
60         startService(new Intent(LocalService.this, RemoteService.class));
61         bindService(new Intent(LocalService.this, RemoteService.class), connection, Context.BIND_IMPORTANT);
62         return START_STICKY;
63     }
64 
65     @Override
66     public IBinder onBind(Intent intent) {
67         mBinder = new MyBinder();
68         return mBinder;
69     }
70 
71     private class MyBinder extends IMyAidlInterface.Stub {
72 
73         @Override
74         public String getServiceName() throws RemoteException {
75             return LocalService.class.getName();
76         }
77 
78         @Override
79         public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {
80 
81         }
82     }
83 }

RemoteService.java

package guide.yunji.com.guide.processGuard;

import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import android.widget.Toast;


import guide.yunji.com.guide.testFace.IMyAidlInterface;

public class RemoteService extends Service {
    private static final String TAG = RemoteService.class.getName();
    private MyBinder mBinder;

    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            IMyAidlInterface iMyAidlInterface = IMyAidlInterface.Stub.asInterface(service);
            try {
                Log.e(TAG, "connected with " + iMyAidlInterface.getServiceName());
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.e(TAG, "onServiceDisconnected: 鏈接斷開,重新啟動 LocalService");
            Toast.makeText(RemoteService.this, "鏈接斷開,重新啟動 LocalService", Toast.LENGTH_LONG).show();
            startService(new Intent(RemoteService.this, LocalService.class));
            bindService(new Intent(RemoteService.this, LocalService.class), connection, Context.BIND_IMPORTANT);
        }
    };

    public RemoteService() {
    }


    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.e(TAG, "onStartCommand: RemoteService 啟動");
        Toast.makeText(this, "RemoteService 啟動", Toast.LENGTH_LONG).show();
        bindService(new Intent(this, LocalService.class), connection, Context.BIND_IMPORTANT);
        return START_STICKY;
    }

    @Override
    public IBinder onBind(Intent intent) {
        mBinder = new MyBinder();
        return mBinder;
    }

    private class MyBinder extends IMyAidlInterface.Stub {

        @Override
        public String getServiceName() throws RemoteException {
            return RemoteService.class.getName();
        }

        @Override
        public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {

        }
    }
}

注意,兩個service要在不通的進程

1  <service
2             android:name=".processGuard.LocalService"
3             android:enabled="true"
4             android:exported="true" />
5         <service
6             android:name=".processGuard.RemoteService"
7             android:enabled="true"
8             android:exported="true"
9             android:process=":RemoteProcess" />

兩個service通過aidl連接,如下

 1 // IMyAidlInterface.aidl
 2 package guide.yunji.com.guide.testFace;
 3 
 4 // Declare any non-default types here with import statements
 5 
 6 interface IMyAidlInterface {
 7     /**
 8      * Demonstrates some basic types that you can use as parameters
 9      * and return values in AIDL.
10      */
11     void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
12             double aDouble, String aString);
13                String getServiceName();
14 }

此外還要注意一點,程序的service初始化的時候如果在自定義的application的時候要注意多進程的問題,本來LocalService是在主進程中啟動的,所以要做一下進程的判斷,如下:

 1 package com.honghe.guardtest;
 2 
 3 import android.app.ActivityManager;
 4 import android.app.Application;
 5 import android.content.Context;
 6 import android.content.Intent;
 7 
 8 public class MyApplication extends Application {
 9     private static MainActivity mainActivity = null;
10 
11     public static MainActivity getMainActivity() {
12         return mainActivity;
13     }
14 
15     public static void setMainActivity(MainActivity activity) {
16         mainActivity = activity;
17     }
18 
19     @Override
20     public void onCreate() {
21         super.onCreate();
22         if (isMainProcess(getApplicationContext())) {
23             startService(new Intent(this, LocalService.class));
24         } else {
25             return;
26         }
27     }
28 
29     /**
30      * 獲取當前進程名
31      */
32     public String getCurrentProcessName(Context context) {
33         int pid = android.os.Process.myPid();
34         String processName = "";
35         ActivityManager manager = (ActivityManager) context.getApplicationContext().getSystemService
36                 (Context.ACTIVITY_SERVICE);
37         for (ActivityManager.RunningAppProcessInfo process : manager.getRunningAppProcesses()) {
38             if (process.pid == pid) {
39                 processName = process.processName;
40             }
41         }
42         return processName;
43     }
44 
45     public boolean isMainProcess(Context context) {
46         /**
47          * 是否為主進程
48          */
49         boolean isMainProcess;
50         isMainProcess = context.getApplicationContext().getPackageName().equals
51                 (getCurrentProcessName(context));
52         return isMainProcess;
53     }
54 }

然后LocalService重啟后,可以判斷是否要開啟程序的主界面,上面的localService已經寫了,就不多介紹了。

代碼已經有了,我們怎么測試呢?

當然是偽造一個ndk的崩潰來驗證程序的可行性了。

我們寫一個jni,如下

 

寫一個Jni的類

JniLoaderndk.cpp

 1 #include <string.h>
 2 #include <jni.h>
 3 #include <stdio.h>
 4 
 5 //#include "yue_excample_hello_JniLoader.h"
 6 //按照C語言規則編譯。jni依照C的規則查找函數,而不是C++,沒有這一句運行時會崩潰報錯:
 7 // java.lang.UnsatisfiedLinkError: Native method not found:
 8 extern "C"{
 9 
10 JNIEXPORT jstring JNICALL Java_com_honghe_guardtest_JniLoader_getHelloString
11 (JNIEnv *env, jobject _this)
12 {
13 int m=30;
14 int n=0;
15 int j=m/n;
16 printf("hello %d",j);
17 Java_com_honghe_guardtest_JniLoader_getHelloString(env,_this);
18 //return (*env)->NewStringUTF(env, "Hello world from jni)");//C語言格式,文件名應為xxx.c
19 return env->NewStringUTF((char *)("hello whh"));//C++格式,文件名應為xxx.cpp
20 }
21 
22 
23 }

為什么這么寫,因為我本來想通過除0來制造異常,但是ndk本身並不向上層因為除0崩潰,后來無奈只好使用遞歸來制造崩潰了。

android.mk

 1 LOCAL_PATH := $(call my-dir)
 2 include $(CLEAR_VARS)
 3 
 4 # 要生成的.so庫名稱。java代碼System.loadLibrary("firstndk");加載的就是它
 5 LOCAL_MODULE := firstndk
 6 
 7 # C++文件
 8 LOCAL_SRC_FILES := JniLoaderndk.cpp
 9 
10 include $(BUILD_SHARED_LIBRARY)

application.mk

# 注釋掉了,不寫會生成全部支持的平台。目前支持:
 APP_ABI := armeabi arm64-v8a armeabi-v7a mips mips64 x86 x86_64
#APP_ABI := armeabi-v7a

寫完了ndk后需要到jni的目錄下執行一個 ndk-build 的命令,這樣會在main目錄下生成libs文件夾,文件夾中有目標平台的so文件,但是android默認不會讀取該目錄的so文件,所以我們需要在app/build.gradle中加入路徑,使程序能夠識別so

sourceSets {
        main {
            jniLibs.srcDirs = ['src/main/libs']//默認為jniLibs
        }
    }

弄好后,就可以在安卓程序中找到ndk中的方法了。

創建調用類

JniLoader.java

1 package com.honghe.guardtest;
2 
3 public class JniLoader {
4     static {
5         System.loadLibrary("firstndk");
6     }
7 
8     public native String getHelloString();
9 }

 

調用該方法就能夠發現程序在ndk影響下崩潰了,如圖

看logcat

說明舊的進程由於ndk崩潰被殺死了,但是看界面里程序已經重啟了,然后還多出了一個不通pid的同名進程,如下

證明ndk崩潰后我們的軟件重啟成功了。

代碼全部在github,如下

https://github.com/dongweiq/guardTest

我的github地址:https://github.com/dongweiq/study

歡迎關注,歡迎star o(∩_∩)o 。有什么問題請郵箱聯系 dongweiqmail@gmail.com qq714094450

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM