zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    668. Kth Smallest Number in Multiplication Table
    658. Find K Closest Elements
    483. Smallest Good Base
    475. Heaters
    454. 4Sum II
    441. Arranging Coins
    436. Find Right Interval
    410. Split Array Largest Sum
    392. Is Subsequence
    378. Kth Smallest Element in a Sorted Matrix
  • 原文地址:https://www.cnblogs.com/dongweiq/p/10896027.html
Copyright © 2011-2022 走看看