如何优雅的退出应用和处理崩溃异常并重启

写在前面

这是最近一些朋友问个人问题,我把它整理成了一个库,供你们享用,GitHub 地址:https://github.com/nanchen2251/AppManagerjava

从四个应用场景提及

  • 退出应用
    相信各位朋友或多或少都会有遇到过须要在某个特定的地方退出应用的需求,这个场景必定很是广泛。android

  • 崩溃后重启
    程序老是没法作到尽善尽美,有时候你也不知道由于什么缘由致使了 APP 的崩溃,这无疑是很是糟糕的用户体验。这时候咱们能够采用重启机制来加强用户温馨体验感。git

  • 莫名其妙重启
    然而心细的小伙伴确定会发现,在部分手机上会出现莫名其妙的崩溃后重启(后面会讲缘由),并且最要命的是,假设你有三个 Activity,他们分别是 Act1, Act2, Act3,它们的启动顺序是 Act1 -> Act2 -> Act3,而若是在 Act3 发生了崩溃,这时候极有可能应用重启后进入的是 Act2,而 Act2 中须要某个来源于 Act1 (或者在 Act1 中经过接口获取) 的参数,当没有这个参数的时候会引起崩溃(或者数据不全)。这时候你可能最直观的想法就是禁止应用重启,但或许这并非最佳的方式。github

  • 崩溃时弹出一个对话框
    在部分手机上,当崩溃的时候,会弹出一个提示对话框。在这种状况下,用户只有点击 “强行关闭” 来结束程序。当该对话框出现,对用户来讲是至关不友好的。或许咱们能够经过某种方式拦截掉系统的处理,让应用出错时再也不显示它。maven

退出应用的几种方式

Andorid 退出应用的方式不少,常见的也就下面四种。ide

  • System.exit(0) 使用系统的方法,强制退出
    System.exit(0) 表示的是终止程序,终止当前正在运行的 Java 虚拟机,在 Java 中咱们也使用这种方式来关闭整个应用,在前期不少开发人员都是使用这种方式,我本身在开发项目过程当中也用过这种方式来退出,可是有时候会在部分机型中,当退出应用后弹出应用程序崩溃的对话框,有时退出后还会再次启动,少部分的用户体验不太好。但如今也依旧还会有少部分的开发人员会使用这种方式,由于使用方式很简单,只须要在须要退出的地方加上这句代码就行。函数

  • 抛出异常,强制退出
    这种方式如今基本上已经看不到了,用户体验比第一种方式更差,就是让抛出异常、是系统崩溃、从而达到退出应用的效果oop

  • 使用 Application 退出
    目前比较经常使用方法之一,咱们都知道 ApplicationAndroid 的系统组件,当应用程序启动时,会自动帮咱们建立一个 Application,并且一个应用程序只能存在一个 Application ,它的生命周期也是最长的,若是须要使用本身建立的 Application 时,这个时候咱们只须要在 Androidmanifest.xml 中的 <Application> 标签中添加 name 属性:把建立的 Application 完整的包名 + 类名放进了就好了。测试

  • 使用广播退出
    使用广播来实现退出应用程序,其实实现的思路相对于第三种更简单,咱们编写一个 BaseActivity,让其余的 Activity 都继承于它,当我须要退出时,咱们就销毁 BaseActivity,那么其余继承与它的 Activity 都会销毁。gradle

四种方式的代码也就很少提,须要的本身去Android:销毁全部的Activity退出应用程序几种方式

莫名其妙重启?

上面的场景中说到了,再部分手机上会出现崩溃后自动重启的状况,这让咱们很很差控制。经本人测试,在 Android 的 API 21 ( Android 5.0 ) 如下,Crash 会直接退出应用,可是在 API 21 ( Android 5.0 ) 以上,系统会遵循如下原则进行重启:

  • 包含 Service,若是应用 Crash 的时候,运行着 Service,那么系统会从新启动 Service。

  • 不包含 Service,只有一个 Activity,那么系统不会从新启动该 Activity。

  • 不包含 Service,但当前堆栈中存在两个 Activity:Act1 -> Act2,若是 Act2 发生了 Crash ,那么系统会重启 Act1。

  • 不包含 Service,可是当前堆栈中存在三个 Activity:Act1 -> Act2 -> Act3,若是 Act3 崩溃,那么系统会重启 Act2,而且 Act1 依然存在,便可以从重启的 Act2 回到 Act1。

在这样的状况下,咱们或许会有两种需求:

  • 崩溃后不容许重启

  • 崩溃后须要重启

怎么办

翻看 API 咱们发现,Java 中存在一个 UncaughtExceotionHandler 的接口,而在 Android 中咱们沿用了它,咱们能够采用这个接口实现咱们想要的功能。
(为了方便,我把它作成了库,传送门:https://github.com/nanchen2251/AppManager

讲一些核心

CrashApplication

首先是咱们的 CrashApplication 类,由于咱们崩溃的时候须要结束程序后再重启,因此咱们须要退出应用,这里咱们采用上面的第三种方式。

public class CrashApplication extends Application {
    private List<Activity> mActivityList;


    @Override
    public void onCreate() {
        super.onCreate();
        mActivityList = new ArrayList<>();
    }

    /**
     * 添加单个Activity
     */
    public void addActivity(Activity activity) {
        // 为了不重复添加,须要判断当前集合是否知足不存在该Activity
        if (!mActivityList.contains(activity)) {
            mActivityList.add(activity); // 把当前Activity添加到集合中
        }
    }

    /**
     * 销毁单个Activity
     */
    public void removeActivity(Activity activity) {
        // 判断当前集合是否存在该Activity
        if (mActivityList.contains(activity)) {
            mActivityList.remove(activity); // 从集合中移除
            if (activity != null){
                activity.finish(); // 销毁当前Activity
            }
        }
    }

    /**
     * 销毁全部的Activity
     */
    public void removeAllActivity() {
        // 经过循环,把集合中的全部Activity销毁
        for (Activity activity : mActivityList) {
            if (activity != null){
                activity.finish();
            }
        }
        //杀死该应用进程
        android.os.Process.killProcess(android.os.Process.myPid());
    }

}

UncaughtExceptionHandlerImpl

咱们固然少不了新建一个 UncaughtExceptionHandlerImpl 类去实现咱们的 UncaughtExceptionHandler 接口,它必须实现咱们的 uncaughtException(thread, throwable) 方法,咱们接下来能够在这中间做文章。须要特别注意的是:重启必须清除堆栈内的 Activity。

/**
     * 当 UncaughtException 发生时会转入该函数来处理
     */
    @SuppressWarnings("WrongConstant")
    @Override
    public void uncaughtException(Thread thread, Throwable ex) {
        if (!handleException(ex) && mDefaultHandler != null) {
            // 若是用户没有处理则让系统默认的异常处理器来处理
            mDefaultHandler.uncaughtException(thread, ex);
        } else {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                Log.e(TAG, "error : ", e);
            }
            if (mIsRestartApp) { // 若是须要重启
                Intent intent = new Intent(mContext.getApplicationContext(), mRestartActivity);
                AlarmManager mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
                //重启应用,得使用PendingIntent
                PendingIntent restartIntent = PendingIntent.getActivity(
                        mContext.getApplicationContext(), 0, intent,
                        Intent.FLAG_ACTIVITY_NEW_TASK);
                mAlarmManager.set(AlarmManager.RTC, System.currentTimeMillis() + mRestartTime,
                        restartIntent); // 重启应用
            }
            // 结束应用
            ((CrashApplication) mContext.getApplicationContext()).removeAllActivity();
        }
    }

咱们的 handleException(throwable) 方法用于弹出 Toast 和收集 Crash 信息。

/**
     * 自定义错误处理,收集错误信息,发送错误报告等操做均在此完成
     *
     * @param ex
     * @return true:若是处理了该异常信息;不然返回 false
     */
    private boolean handleException(final Throwable ex) {
        if (ex == null) {
            return false;
        }

        // 使用 Toast 来显示异常信息
        new Thread() {
            @Override
            public void run() {
                Looper.prepare();
                Toast.makeText(mContext, getTips(ex), Toast.LENGTH_LONG).show();
                Looper.loop();
            }
        }.start();


        //  若是用户不赋予外部存储卡的写权限致使的崩溃,会形成循环崩溃
//        if (mIsDebug) {
//            // 收集设备参数信息
//            collectDeviceInfo(mContext);
//            // 保存日志文件
//            saveCrashInfo2File(ex);
//        }
        return true;
    }

封装好的使用

一、添加依赖

Step 1. Add it in your root build.gradle at the end of repositories:
allprojects {
        repositories {
            ...
            maven { url 'https://jitpack.io' }
        }
    }
Step 2. Add the dependency
dependencies {
            compile 'com.github.nanchen2251:AppManager:1.0.1'
    }

二、在须要使用的地方使用

// 设置崩溃后自动重启 APP
UncaughtExceptionHandlerImpl.getInstance().init(this, BuildConfig.DEBUG, true, 0, MainActivity.class);

三、你也能够禁止重启

// 禁止重启UncaughtExceptionHandlerImpl.getInstance().init(this,BuildConfig.DEBUG);

欢迎关注个人技术公众号(公众号搜索nanchen),天天一篇 Android 资源分享。

效果图

image

相关文章
相关标签/搜索