android雙進程守護,讓程序崩潰後必定能夠重啓

因爲咱們作的是機器人上的軟件,而機器人是24小時不間斷服務的,這就要求咱們的軟件不能退出到系統桌面。固然最好是可以作到程序可以不卡頓,不崩潰,本身不退出。因爲咱們引用了不少第三方的開發包,也不能保證他們的穩定性,因此,要作到徹底不崩潰也是不可能的。java

退而求其次,若是崩潰了咱們就要保證程序可以被拉起來,期間也看過不少保活的方案,好比service前臺的方法,好比jni裏寫守護進程,好比接收系統廣播喚醒,好比用alarmmanager喚醒等等,感受不是效率底,就是被系統屏蔽了。通過不斷篩選,我認爲使用aidl進行雙進程守護實際上是效率很好的一個解決方案。android

其實這個原理也很簡單,簡單的說就是建立兩個service,其中一個再程序主進程,另一個在其餘進程,這兩個進程經過aidl通訊,一旦其中一個進程斷開鏈接,那麼就重啓該服務,兩個程序互相監聽,就可以作到一方被殺死,另外一方被啓動了。固然,若是使用 adb shell force-stop packageName的方法殺死程序,確定是不可以重啓的。這種方式僅僅是爲了不ndk層崩潰,java抓不到從而不能使用java層重啓應用的一種補充方式。要想作到徹底不被殺死,那就太流氓了。git

說了這麼多,看代碼吧github

兩個service,localservice和remoteserviceshell

LocalService.javaapp

 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.javaide

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是在主進程中啓動的,因此要作一下進程的判斷,以下:gradle

 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

相關文章
相關標籤/搜索