由于我们做的是机器人上的软件,而机器人是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