最近出现一个bug,这个bug还是很常见的很容易实现,在设计代码的时候还是需要注意的,就是在app授权location定位权限之后,将app放在后台,在设置里对app的权限关闭操作,再从后台打开app,app会crash。

这里的问题原因就是当settings里关闭了权限之后,安卓系统会主动关闭app进程,使得重新打开app时,之前保存的app的activity页面是被异常关闭的,会触发 onCreate方法的参数savedInstanceState: Bundle不为空

同时之前内存中的累的对象不会保证数据的完整和正确被索引到,在我的例子里就是cache的对象被销毁了,重新打开app没有经过正常的初始化流程,导致crash。

搜集了资料发现,即使是微信也不可避免系统异常杀死当前process的行为,只能使用重启的操作重启app来规范这样的现象。

下面是总结的几个常用的app重启的方案:

/**
     * 使用 AlarmManager 来帮助重启
     *
     * @param context
     * @param cls
     */
    public static void restartByAlarm(Context context, Class<?> cls)
    {
        Intent mStartActivity = new Intent(context, cls);
        int mPendingIntentId = 123456;
        PendingIntent pIntent = PendingIntent.getActivity(context, mPendingIntentId, mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT);

        AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
        alarmManager.set(AlarmManager.RTC, System.currentTimeMillis() + 500, pIntent);

        System.exit(0);
    }
	
	 /**
     * 使用 killProcess 杀死自身,系统会恢复应用
     *
     * @param context
     * @param cls
     */
    public static void restartByKillProcess(Context context, Class<?> cls)
    {
        Intent intent = new Intent(context, cls);
        intent.addFlags(FLAG_ACTIVITY_NEW_TASK);
        context.startActivity(intent);
        android.os.Process.killProcess(android.os.Process.myPid());
    }

    /**
     * 通过清栈触发应用重启。但不会重启 application ,与应用相关的静态变量也会更重启前一样。
     *
     * @param context
     */
    public static void restartByClearTop(Context context)
    {
        Intent intent = context.getPackageManager().getLaunchIntentForPackage(context.getPackageName());
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        context.startActivity(intent);
    }

    /**
     * 利用系统重启api触发应用重启
     *
     * @param context
     */
    public static void restartBySystemApi(Context context)
    {
        ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
        manager.restartPackage(context.getPackageName());
    }

    /**
     * 通过 Intent.makeRestartActivityTask 来触发应用重启,跟 restartByClearTop 类似。
     * 但不会重启 application ,与应用相关的静态变量也会更重启前一样。
     *
     * @param context
     */
    public static void restartByCompatApi(Context context, Class<?> cls)
    {
        Intent intent = new Intent(context, cls);
        Intent restartIntent = Intent.makeRestartActivityTask(intent.getComponent());
        context.startActivity(restartIntent);
        System.exit(0);
    }

    /**
     * 5.1 版本以后可以借助 JobScheduler 来重启应用
     *
     * @param context
     */
    public static void restartByJobScheduler(Context context, Class<?> cls)
    {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
        {
            int delayTimeMin = 1000;
            int delayTimeMax = 2000;

            MyJobSchedulerService.setMainIntent(new Intent(context, cls));

            JobInfo.Builder jobInfoBuild = new JobInfo.Builder(0, new ComponentName(context, MyJobSchedulerService.class));
            jobInfoBuild.setMinimumLatency(delayTimeMin);
            jobInfoBuild.setOverrideDeadline(delayTimeMax);
            JobScheduler jobScheduler = (JobScheduler) context.getSystemService(JOB_SCHEDULER_SERVICE);
            jobScheduler.schedule(jobInfoBuild.build());

            System.exit(0);
        }
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    static class MyJobSchedulerService extends JobService
    {
        private static Intent mIntent;

        public static void setMainIntent(Intent intent)
        {
            mIntent = intent;
        }

        @Override
        public boolean onStartJob(JobParameters params)
        {
            startActivity(mIntent);
            jobFinished(params, false);
            return false;
        }

        @Override
        public boolean onStopJob(JobParameters params)
        {
            return false;
        }
    }

但是在使用第二种方案的时候,发现在加了判断savedInstanceState不为空的时候,重启之后savedInstanceState还是不为空,进入死循环。是因为单纯的重启app还是会保留上一次的页面的参数,就是之前系统异常杀死的时候的参数
    

解决办法就是在重启之前调用一次finish()方法,只有这样,下次启动页面savedInstanceState的值就重置为null了。
    

最终kotlin的写法是:

if (savedInstanceState != null) {
   finish()
   val intent = Intent(this, MainActivity::class.java)
   intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
   startActivity(intent)
   exitProcess(0)
}

但是这样还是有个问题,就是点击后台app重启,现象是会先杀掉,再重新打开app,会有个弹出的动画,给人一种启动两次的样子。

        问题保留,以后深究。