• 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

  • 相关阅读:
    调用外部程序主窗体做子窗体
    查看window编码
    c# 数据库更新和界面刷新的问题
    c# datagridview代码(网上的)
    winform DataGridView控件的打印
    西电ubuntu更新软件源
    C++ primer 学习笔记(2):函数
    C++ Primer 学习笔记(1)——迭代器,数组
    查询表属于哪个数据
    oracle 实用语句
  • 原文地址:https://www.cnblogs.com/dongweiq/p/10896027.html
Copyright © 2020-2023  润新知