请选择 进入手机版 | 继续访问电脑版

Qt for Android(九) ——APP 崩溃卡死拉起保活实战

[复制链接]
阿峻 发表于 2021-1-1 18:32:03 | 显示全部楼层 |阅读模式 打印 上一主题 下一主题
这篇文章要基于前面的根本,我们才能继续下面的内容,发起阅读。
Qt for Android(一) —— QT 中如何调用android方法
Qt for Android(二) —— QT 中调用自界说Android方法详细教程(获取Android设备的SN号)
配景

首先,本文的案例情况基于一些特殊的 android 设备,好比瑞星微的RK系列,在该设备上不会熄屏,没有锁屏键,运行的应用也仅限于几个 APP,大部分不会存在应用被系统杀死的大概。
应用拉起说白了就是历程保活,关于Android 的历程保活文章有许多,但是本文是基于 QT for Android 的开辟,因此过程大概有些许差别,同时针对的场景也差别,因此在操纵上大概更有针对性。
由于我们的应用属于广告播放类 APP, 需要长时间的稳定运行,但不可制止的由于某种原因 APP 发生崩溃大概界面卡死,为了尽大概的减小损失,因此我们需要在发生上述情况时重新启动我们的APP。
分析

假设我们的主应用称为A,而为了做到历程保活,我们需要另一个历程B,称之为Monitor,即监督历程,也可以称为守护历程(“守护”,这个词在2020年显得很特别),这决定了我们的方案需要安装两个应用。
方法和思路:

  • A启动后向B发送登录请求,创建通信,B开启定时器,开始监测A的数据,通信的实现方式不限,可以是socket,大概广播
  • 通信创建后A立即开始向B发送心跳,每1s一个心跳包。
  • 假如发生崩溃,B没有收到A的心跳包,则重新拉起A。
  • 假如发生卡死,B没有收到A的心跳包,则重新拉起A。
  • 正常退出A的时候向B发送登出请求,停止心跳,防止B误以为A死亡而被拉起。
实在思路很简单,但是实在在开辟的时候遇到一个问题,QT的事件循环和Android的事件循环互不干扰,即QT的卡死不会影响到Android层的事件。为了管理这个问题,就往下看详细的代码。
代码详述

应用B之MonitorServices:
  1. package com.qht.b;import android.app.Service;import android.content.Context;import android.content.Intent;import android.content.pm.PackageManager;import android.net.Uri;import android.os.Build;import android.os.IBinder;import android.util.Log;import java.io.IOException;import java.net.DatagramPacket;import java.net.DatagramSocket;import java.net.SocketException;import java.net.SocketTimeoutException;import java.util.Calendar;import java.util.Timer;import java.util.TimerTask;public class MonitorService extends Service {    public static final String CLASS_NAME = "MonitorService";    private Thread thread;    private DatagramSocket socket = null;    private Context m_context;    private long lastTimeMillis = 0; //代表了最后一次收到A应用心跳包的时间戳    Timer timer = null;    TimerTask task;    public MonitorService() {    }    @Override    public IBinder onBind(Intent intent) {        Log.d(CLASS_NAME, "onBind !!");        return null;    } @Override    public void onCreate() {        super.onCreate();        m_context = this;        Log.d(CLASS_NAME, "onCreate !!");        lastTimeMillis = 0;        thread=new Thread(new Runnable()        {            @Override           public void run()            {                try {                    System.out.println("监听端口16667");                    socket = new DatagramSocket(16667);                    socket.setSoTimeout(5000);                } catch (Exception e) {                    e.printStackTrace();                }                while (true) {                  byte data[] = new byte[1024];                    DatagramPacket packet = new DatagramPacket(data, data.length);                    try {                        socket.receive(packet);                    } catch (SocketTimeoutException e) {                        System.out.println("socket 10s 超时:" + e.getMessage());                    } catch (SocketException e) {                        System.out.println("socket SocketException:" + e.getMessage());                       e.printStackTrace();                    } catch (IOException e) {                        System.out.println("socket IOException:" + e.getMessage());                        e.printStackTrace();                    }                    String result = new String(packet.getData(), packet.getOffset(), packet.getLength());                    //校验包                    if (result.equals("hertbeat"))                   {                        lastTimeMillis =  System.currentTimeMillis();                        System.out.println("rec : hertbeat");                    }else if (result.equals("login"))                    {                      //login                        lastTimeMillis =  System.currentTimeMillis();                        startTimer();                        System.out.println("rec : login");                    } else if (result.equals("logout"))                    {                                           //退出取消,等候login再开启                        System.out.println("rec : logout timer.cancel()");                        stopTimer();                    } else if (result.equals("anr"))                    {                        //退出取消,等候login再开启                        System.out.println("rec : anr restartApp");                        restartApp();                    }                }            }        });        thread.start();    }  @Override    public void onStart(Intent intent, int startId) {        super.onStart(intent, startId);        Log.d(CLASS_NAME, "onStart !!");    }   private void startTimer(){        if (timer == null) {            timer = new Timer();        }        if (task == null) {            task = new TimerTask() {                @Override                public void run() {                    System.out.println("run TimerTask");                    if (lastTimeMillis != 0 &&  System.currentTimeMillis()- lastTimeMillis > 2000)                    {                        System.out.println("失去心跳,拉起APPlastTimeMillis :" + lastTimeMillis + ":" + (Calendar.getInstance().getTimeInMillis() - lastTimeMillis) );                        //  心跳超时,杀死并拉起                        restartApp();                    }                                    }            };        }        if(timer != null && task != null )            timer.schedule(task,0,1000);    }   private void restartApp() {        killProcess(ConstantUtil.PACKAGE_NAME);        try {            Thread.sleep(300);        } catch (InterruptedException e) {            e.printStackTrace();        }        PackageManager localObject = m_context.getPackageManager();        if (PackageUtil.checkPackInfo(m_context, ConstantUtil.PACKAGE_NAME)) {            Log.i(CLASS_NAME, "find package, ready to lanunch! "+ConstantUtil.PACKAGE_NAME);            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.CUPCAKE) {                Log.i(CLASS_NAME, "Build.VERSION.SDK_INT >= Build.VERSION_CODES.CUPCAKE "+ (int)Build.VERSION.SDK_INT);                m_context.startActivity((localObject).getLaunchIntentForPackage(ConstantUtil.PACKAGE_NAME));          }        } else {            Log.i(CLASS_NAME, "not find package!" + ConstantUtil.PACKAGE_NAME);        }        lastTimeMillis = 0;        stopTimer();    }   private void stopTimer(){        if (timer != null) {            timer.cancel();            timer = null;        }        if (task != null) {            task.cancel();            task = null;        }    }    @Override    public void onDestroy() {        super.onDestroy();        Log.d(CLASS_NAME, "onDestroy !!");        stopTimer();    }        /**     * 竣事历程     */    private void killProcess(String packageName) {        Process process = Runtime.getRuntime().exec("su");        OutputStream out = process.getOutputStream();        String cmd = "am force-stop " + packageName + " \n";        try {            out.write(cmd.getBytes());            out.flush();        } catch (IOException e) {            e.printStackTrace();        }    }}
复制代码
在 Monitor内部,我们维护了一个定时器Timer,需要不绝的检测A应用的心跳数据。44行我们首先开启一个工作线程去监听一个udp端口,我这边接纳的是udp通信,因为我只需要收到A应用的心跳即可。由于socket的receive函数是阻塞式的,因此我们在线程内部开启while循环继承数据,收到的数据范例分为4种:
login:
代表A应用上线,这个时候我们开启定时器即可。
logout:
代表A应用下线,这个时候我们关闭定时器即可。
hertbeat:
代表A应用发送的心跳数据,这个时候我们主需要不绝的更新 lastTimeMillis (代表了最后一次收到A应用心跳包的时间戳)这个值即可。
anr:
代表A应用发生卡死,这个时候我们需要调用restartApp方法强制杀死A应用并重启它。
固然,services不能自己启动,需要一个activity去启动它,同时也要注册到manifest文件中。
  1.         //启动        Intent intent = new Intent(MainActivity.this, MonitorService.class);        startService(intent);
复制代码
  1.                
复制代码
上面就是我们MonitorServices的全部内容,再来梳理下它的工作:

  • 监听login请求,并开启心跳检测。
  • 随时注意心跳是否断开,断开则拉起A应用。
  • 监听logout请求,制止定时器空跑,包管A的正常退出,而不是当做崩溃处理处罚。
  • 监听anr消息,收到anr消息则重启A应用。
应用A之TestApp:
最开始我是将A应用通信的代码放到Android的Service中的,但是经过测试,在频仍的崩溃拉起后,有时候会出现拉起失败的情况,详细原因和A应用包罗的服务有关。而通过之前的文章我们已经知道了我们的QT步伐都有一个入口Activity,因此我将通信的代码放到了这个入口Activity中。
  1. package com.qht.a;import android.content.Context;import android.content.Intent;import android.os.Build;import android.os.Handler;import android.util.Log;import android.view.WindowManager;import android.view.KeyEvent;import java.io.IOException;import java.lang.reflect.Method;import java.net.DatagramPacket;import java.net.DatagramSocket;import java.net.InetAddress;import java.net.SocketException;import java.net.UnknownHostException;public class MainActivity extends org.qtproject.qt5.android.bindings.QtActivity {    DatagramSocket socket= null;    InetAddress serverAddress = null;    private boolean isStop = false;//logout,停止心跳    private int lasttick, mTick;//两次计数器的值    private Handler mHandler = new Handler();    private boolean isNotAnr = true;//是否anr标识    @Override    public void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        anrDetection();        loginAndHert();    }private  void loginAndHert() {  System.out.println("开始 loginAndHert");        try {            new Thread(new Runnable() {                @Override                public void run() {                    try {                        Thread.sleep(300);                        if (socket == null)                        {                            System.out.println("绑定 16666 端口");                            socket = new DatagramSocket(16666);                        }                                                  System.out.println("udp 使用回环地点 : 127.0.0.1");                        serverAddress = InetAddress.getByName("127.0.0.1");                    } catch (UnknownHostException e) {                        e.printStackTrace();                    } catch (SocketException e) {                        e.printStackTrace();                    }catch (Exception e) {                        e.printStackTrace();                    }                                         String sendData = "login";                                         byte data[] = sendData.getBytes();                    DatagramPacket packet = new DatagramPacket(data, data.length, serverAddress, 16667);                    System.out.println("发送给 16667 端口,被monitor服务监听");                    try {                        socket.send(packet);                        System.out.println("socket.send:" + sendData + ",登录后300ms,每隔1s发送一次心跳包");                        Thread.sleep(300);                    } catch (IOException e) {                        e.printStackTrace();                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                                         //上线后发送心跳                    while (!isStop) {                        try {                            String sendData2 = "hertbeat";                            byte data2[] = sendData2.getBytes();                            DatagramPacket packet2 = new DatagramPacket(data2, data2.length, serverAddress, 16667);                            socket.send(packet2);                            System.out.println("socket.send:" + sendData2);                            Thread.sleep(1000);                                                         } catch (Exception e) {                            e.printStackTrace();                        }                    }                }            }).start();        } catch (Exception e) {            e.printStackTrace();        }    }     @Override    public boolean onKeyDown(int keyCode, KeyEvent event) {        if ((keyCode == KeyEvent.KEYCODE_BACK)) {            System.out.println("按下了back键   onKeyDown() send logout,500ms after System.exit(0)");            logout();            return false;        }else {            return super.onKeyDown(keyCode, event);        }    }  private  void logout(){        System.out.println("退出 MainActivity");        new Thread(new Runnable() {            @Override            public void run() {                try {                                         isStop = true;                    String sendData = "logout";                    byte data[] = sendData.getBytes();                    DatagramPacket packet = new DatagramPacket(data, data.length, serverAddress, 16667);                    socket.send(packet);                    System.out.println("socket.send:" + sendData);                                          Thread.sleep(500);                    System.exit(0);                } catch (Exception e) {                    e.printStackTrace();                }            }        }).start();    } /*    * 卡死监测原理描述:使用service中的线程向主线程发送mTick+1,然后线程睡眠5s后,再去检测这个值是否被改变,没改变的话说明主线程卡死了,主线程卡死后直接退出历程,等候最多2s后monitor拉起    * */    private void anrDetection() {        new Thread(new Runnable() {            @Override            public void run() {                while (isNotAnr) {                    lasttick = mTick;                    mHandler.post(tickerRunnable);//向主线程发送消息 计数器值+1                                 try {                        Thread.sleep(8000);                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                    System.out.println(" mTick :" + mTick + "lasttick:" + lasttick);                    if (mTick == lasttick) {                                                 isNotAnr = false;                        Log.e("QHT", "anr happned in here");                        try {                            handleAnrError();                        } catch (SocketException e) {                            e.printStackTrace();                        }                    }                }            }        }).start();    } //发生anr的时候,在此处写逻辑    private void handleAnrError() throws SocketException {        System.out.println("ANR exit ,wait monitor 拉起");        System.exit(0);    }    private final Runnable tickerRunnable= new Runnable() {        @Override        public void run() {            mTick = (mTick + 1) % 10;        }    };}
复制代码
在上面30行的时候我们从Activity的onCreate方法开始,也就是从应用A启动那一刻开始,就调用loginAndHert方法向应用B发送login请求,因为应用A不需要继承数据,因此无法确认login是否发送乐成,但是使用回环地点不会存在失败的情况,因此我们延迟300ms后再去每一秒发送一次心跳。
在MainActivity中我们也监听了返回键,当收到返回键时我们认为应用被正常退出,因此我们调用了logout方法,告诉MonitorServices步伐是正常退出的。
到这儿,实在就已经完成了应用A和MonitorServices的根本通信了,假如此时应用A突发崩溃,则自然而然的没有心跳包了,MonitorServices就会拉起应用A。
没错,关于崩溃拉起的工作算是完了,但是Android Activity ANR呢? QT步伐block呢?
重点
在oncreate()方法中,我们还调用了一个anrDetection()方法,这便是我们Android层的ANR检测方法。它的原理是这样的:
  在应用一开始UI线程中初始化两个变量tick1和tick2为同一个值,然后开启一个工作线程,并向UI线程post一个tick1的+1请求,tick2稳定。然后工作线程sleep几秒钟,模仿anr的发生,sleep竣事后,再去判断这两个值是否相等,如果相等,则说明tick1没有被+1,也就是说主线程没有处理处罚这个+1请求,那一定是主线程卡住了,则我们认为此时应用发生了ANR;若这两个值不相等,大概说tick1=tick2+1,则说明主线程处理处罚了这个+1请求,主线程工作正常,,步伐继续运行。
在上面的代码中我偷懒了,当发生anr时我强制通过system.exit函数退出历程,然后MonitorServices检测不到心跳了就会拉起应用A,实在在这儿也可以向MonitorServices发送一个"anr"消息,让MonitorServices主动去处理处罚。
上面的代码管理了我们QT步伐 Android 层的卡死问题,但往往这是不多见的,因为这个Activity没有什么高负荷的工作,一般是不会卡死的。出问题总是会出在我们的QT步伐内部。可巧的是,QT步伐内部卡死,MainActivity却不会卡死,即呼应了我上面提到的两者的事件循环是独立的。
但我认为,这个检测卡死的思想是想通的。因此我实验将anrDetection()方法移植到QT步伐中,发现完全可行。
[code]void AndroidDaemonMonitor::start(){     qDebug()
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

发布主题

专注素材教程免费分享
全国免费热线电话

18768367769

周一至周日9:00-23:00

反馈建议

27428564@qq.com 在线QQ咨询

扫描二维码关注我们

Powered by Discuz! X3.4© 2001-2013 Comsenz Inc.( 蜀ICP备2021001884号-1 )