因爲咱們作的是機器人上的軟件,而機器人是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