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

 

posted @ 2019-05-20 19:33  dongweiq  阅读(5514)  评论(0编辑  收藏  举报