美文网首页面试程序员Android知识
记一次app主进程被kill的经历

记一次app主进程被kill的经历

作者: dorn19978 | 来源:发表于2017-03-24 10:35 被阅读215次

    出现问题

    最近在为河北电信做定制游戏平台开发的时候,遇到了一个很奇怪的问题,在某些盒子上,从平台打开某些游戏,玩上几分钟,然后该退出游戏,回到公司的游戏平台后,便出现了页面数据丢失的问题。
    排查了几天(在这不得不吐槽电信的官僚主义,好话说尽,给予各种承诺,依然不提供可以进行adb调试的测试盒子)。最终发现是由于盒子内存太小(几年前的标准,1G内存!!!),当运行大型游戏后,盒子系统自动kill了公司的游戏平台,吐血啊。
    该问题如下图所示:


    问题.png

    如何重现

    既然知道了问题出现的原因,重现该问题也就简单了,具体步骤如下:

    • 打开你的app,通过按钮启动其它app

    • 利用adb 命令获取你的app 进程id

        adb shell su
        ps | grep 包名
      

      如下图所示:


      adb kill app.png
    • 利用adb命令kill 掉你的app进程

        kill -9 6796
      
    • 退出你启动的app,返回

    解决

    既然知道了问题出现的原因,也知道了重现的方法,那么解决问题也就简单了。
    但是多次加班的经验告诉我,这想法太天真了!!!!

    onRestoreInstanceState 和 onSaveInstanceState 无法挽救被kill的app的命运

    按照网上的经验,面对内存数据被回收的情况,只要在onSaveInstanceState保存数据,在页面恢复时利用onRestoreInstanceState读取保存的数据,便可以高枕无忧。

    @Override protected void onSaveInstanceState(Bundle outState) {
       //保存数据
       outState.putParcelable("data", mGameDetailEntity);
       //保存当前进程id和当前页面hashCode
       outState.putInt("pid", Process.myPid());
       outState.putInt("hashCode", hashCode());
       super.onSaveInstanceState(outState);
     }
    
     @Override public void onRestoreInstanceState(Bundle savedInstanceState) {
       super.onRestoreInstanceState(savedInstanceState);
       //恢复数据,并重新设置界面
       mGameDetailEntity = savedInstanceState.getParcelable("data");
       int pid = savedInstanceState.getInt("pid");
       int hashCode = savedInstanceState.getInt("hashCode");
       L.d(TAG, "pid ==> " + pid + ", currentPid ==> " + Process.myPid());
       L.d(TAG, "hashCode ==> " + hashCode + ", currentHashCode ==> " + hashCode());
       if (pid != Process.myPid() || hashCode != hashCode()) {
         isKilled = true;
       }
       setGameData(mGameDetailEntity);
       FL.d(TAG, "Activity被kill, 重新调用");
     }
    

    但是多年爬坑的经历告诉我,网上的例子存在的意义只在于参考!!!
    当App被kill掉后,如果只在activity中处理onSaveInstanceStateonRestoreInstanceState;不做其它任何处理,当前界面倒是正常了,当但你返回其它界面时,便会出现下图所示的情况。

    gg.png

    what?我不是保存了数据了吗?怎么还会出现这崩溃的情况?

    继续使用adb命令查看线程,如下图所示


    app 进程 id 变了.png

    我们kill掉的app 的进程id为 6796 ,现在却变成了 7938!!
    这也就意味着,系统重新启动了一个进程,相当于系统重新给app分配了内存,如果你的app有多个activity或者application中有实例化对像(这是不可避免的)。将会遇到我的情况,activity报空指针异常,或者某些数据报空指针异常。

    如何完美解决该问题

    我只能很抱歉的告诉你,对于这问题,目前还没有完美的解决方案,os自动重启进程,这已经不是我们能控制的范围了,现在是不是意味着我们已经不能解决这问题了;
    当然不是,办法是人想的:
    既然系统自动重启进程(鬼知道系统会不会走我的流程操作),干脆一不做二不休,自己重启app了,重新走一遍启动流程

    解决方案:

    • 重新回到onRestoreInstanceState方法,在activity全局变量中,添加一个boolean(isKilled)用来判断app是否被kill掉,如果被kill掉,那么在onRestoreInstanceState给isKilled 赋值为true;
    • 执行最关键的步骤,在onBackPressed方法中,重启整个app
      public void onBackPressed(){
        if (isKilled) {
          reStartApp();
          return;
        }
        super.onBackPressed();
      }
      
      public void reStartApp(){
        Intent intent = new Intent(this, LauncherActivity.class);
         int pId = 123456;
         PendingIntent pi = PendingIntent.getActivity(this, pId, intent, PendingIntent.FLAG_CANCEL_CURRENT);
         AlarmManager mgr = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
         mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 100, pi);
         System.exit(0);
      }
      

    相关文章

      网友评论

      • Jooyer:你好,为何在onback中调用呢?
        dorn19978:@jjdxmashl isKill是当前新进程的变量,旧的isKill已经随app被kill而被销毁了,个人亲身测试过,如果不在onRestoreInstanceState中重新给isKilled赋值,isKilled将永远为false~~
        dorn19978:onBack是项目处理的需要,你也可以在finsh()中处理。
        jjdxmashl: @Jooyer 有一点点疑惑,相当于两个activity间的切换,感觉是在游戏页面退出onback做上一个页面的启动或者返回操作,但是iskill又像在上一个页面的内容

      本文标题:记一次app主进程被kill的经历

      本文链接:https://www.haomeiwen.com/subject/ksvxottx.html