android进程重启及activity恢复

来源:互联网 发布:小甲鱼python视频教程 编辑:程序博客网 时间:2024/05/29 15:29

这篇文章主要讲述下,android进程死亡之后,会恢复到什么状态,经历哪些流程,包括onSaveInstanceState相关知识。

内存不足怎么办

android在运行程序的过程中发现内存不足,他会去杀一些后台进程,来获取内存,这个过程我们简单称为回收进程。如果后台进程都杀光了,内存还不够,此时可能有2种表现,1,跳出OOM崩溃 2,杀死前台进程  并没有回收某个activity或者回收某些activity的行为。

经常听到有人说android内存不足时会回收activity,这是不对的,android内存不足时会去查后台进程,杀死某些进程来获取内存。杀activity的说法是不对的,可能是受了官方某些文档的误导。

android官方文档上有这么一段话,有点误导的成分。

If an activity is paused or stopped, the system can drop the activity from memory by either asking it to finish, or simply killing its process. When it is displayed again to the user, it must be completely restarted and restored to its previous state.

但是android之母Dianne hackbod在http://stackoverflow.com/questions/7536988/上是这么说的

 The only memory management that impacts activity lifecycle is the global memory across all processes, as Android decides that it is running low on memory and so need to kill background processes to get some back.

对此,我们必须选择相信Dianne hackbod

有人还做了个android内存不足的一个实验,挺有意思的 http://lab.tigerpenguin.com/2014/03/android-activity-and-low-memory.html

所以结论是,android在运行程序的过程中发现内存不足,他会去杀一些后台进程,来获取内存。如果后台进程都杀光了,内存还不够,此时可能有2种表现,1,跳出OOM崩溃 2,杀死前台进程  并没有回收某个activity或者回收某些activity的行为

onSaveInstanceState与activity恢复

很多人都知道,可以在onSaveInstanceState里面保存数据,而在onRestoreInstanceState恢复数据。但是这个具体是怎么样的呢?
这个场景是在进程被系统回收之后,我们在最近使用程序列表里面点击那个应用时,系统会重启进程并且帮我们恢复activity
比如我们的activity内部有个EditText,里面填上了“tt”,进程被回收之后,我们在最近使用程序列表里点击应用,进程会重启,然后恢复activity。此EditText会恢复"tt",这是如何做到的?不需要我们写一行代码,activity本身就能够恢复EditText的值,简单的说就是在回收进程之前会通过onSaveInstanceState来保存数据,然后进程启动,activity重新启动的过程中调用onRestoreInstanceState来恢复数据。
来看一看这个过程中activity的生命周期,是怎么样的。整个过程是这样,首先我们启动了一个activity,然后我们按home,使得进程变为后台进程,接着系统由于内存不足,杀死我们的进程,最后我们从最近程序列表中打开刚才的进程,activity会重新启动。
第一步,启动activity,此时onCreate的参数savedInstanceState为null
2 onCreate hasSaveInstance false2 onStart2 onResume
第二步,我们点home,使得我们的进程变为后台进程,可以看到此时onSaveInstanceState就被调用了,这里有些人有误区,有些人认为在杀进程的时候才调用onSaveInstanceState,实际上任何使得activity变为StoppedState,大部分会调用onSaveInstanceState,为什么说大部分呢? 因为按了back或者调用finish导致的StoppedState,是不掉onSaveInstanceState,因为activity马上要销毁了,根本不需要恢复,其他情况导致activity变为StoppedState都会调用 onSaveInstanceState,比如切换到其他activity,home,锁屏,旋转等等。onSaveInstanceState的调用频率远远高于onRestoreInstanceState我曾经在onSaveInstanceState存储过几十M的东西,现在想想似乎不是很合理,但是也没办法。还有一点需要注意,onSaveInstanceState和onPause前后关系不定(在android HONEYCOMB(Api11)之前:onsaveinstance回调是在onpause之前,在Api11之后调整到了opause之后onstop之前,android为何做这个调整,后边会说)
2 onPause2 onSaveInstanceState2 onStop

第三步,进程被杀死,很暴力的,不会有任何生命周期函数被调用
第四步,从最近程序列表中打开刚才的进程,进程会再次启动,activity会恢复,此时的启动和第一次启动主要有2点不一样,第一,onCreate的参数savedInstanceState不再是null,而是有数据了,savedInstanceState是个bundle里面会序列化的数据,在onCreate的时候就会去读取数据。第二,在onStart之后,onResume之前会调用一个onRestoreInstanceState,在这个onRestoreInstanceState里面,真正的把控件的相关数据给恢复(比如EditText的值)。这个位置是符合我们的习惯的,我们都是onPause和onResume是一堆,onStart和onStop是一堆,而onSaveInstanceState发送在onPause和onStop之间,同理onRestoreInstanceState发送在onStart和onResume之间,显得很恰当。
2 onCreate hasSaveInstance true2 onStart2 onRestoreInstanceState2 onResume
刚才说了onCreate里会去读取savedInstanceState这个Bundle的数据,具体怎么读取的呢?
这里暂时不说了,另一篇文章里会说。

在http://blog.csdn.net/litefish/article/details/51675367这篇文章里,我们说过,2个activity切换的时候,是第二个activity 掉onResume之后,才掉第一个activity的onStop,那这里多了个onSaveInstanceState,这个过程是怎么样的呢?如下所示,这是activity2切换到activity3的过程,onSaveInstanceState跟onStop一样,在activity3出来之后才调用。所以我刚才说过,我在onSaveInstanceState里面存储了数十M甚至上百M的数据,但是这并不会让界面切换变卡。android系统这么设计也是用心良苦,因为这个存储的过程必然是比较慢的,如果放在下一个activity的onResume之前,那必然会影响切换流程性。
2 onPause3 onCreate3 onStart3 onResume2 onSaveInstanceState2 onStop

在使用onSaveInstanceState还需要注意几点
1、序列化尽量使用Parcelable而不是Serializable,后者速度不如前者,而且可能会导致ClassNotFound
2、onSaveInstanceState建议只用来恢复activity用,别把数据存储什么的放到这里来,数据存储可以放到onPause里,最好开线程保存。

activity恢复原则

刚才我们介绍了,单个activity的进程回收后重启的过程,那么多个activity的进程呢?多个activity会被一起恢复吗?
不会的,只会恢复栈顶的activity,但是栈是恢复了的,在按backspace之后,会创建activity实例,
举个例子,我们现在有MainActivity、SecondActivity、ThirdActivity三个activity,然后进程被回收,然后用户从最近列表点击,导致进程重启,activity恢复,第一步是恢复activity ThirdActivity(栈顶的)
 3 onCreate hasSaveInstance true 3 onStart 3 onRestoreInstanceState 3 onResume

此时只有activity3,   1,2都没有。但是activity record里是有记录的,用adb shell dumpsys activity activities可以看到相关记录。
ACTIVITY MANAGER ACTIVITIES (dumpsys activity activities)Display #0 (activities from top to bottom):  Stack #5:    Task id #277    * TaskRecord{f101c60 #277 A=com.fish.activitylife U=0 sz=3}      userId=0 effectiveUid=u0a81 mCallingUid=2000 mCallingPackage=null      affinity=com.fish.activitylife      intent={act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.fish.activitylife/.MainActivity}      realActivity=com.fish.activitylife/.MainActivity      autoRemoveRecents=false isPersistable=true numFullscreen=3 taskType=0 mTaskToReturnTo=1      rootWasReset=false mNeverRelinquishIdentity=true mReuseTask=false mLockTaskAuth=LOCK_TASK_AUTH_PINNABLE      Activities=[ActivityRecord{b45a429 u0 com.fish.activitylife/.MainActivity t277}, ActivityRecord{2f57ac2 u0 com.fish.activitylife/.SecondActivity t277}, ActivityRecord{4846528 u0 com.fish.activitylife/.ThirdActivity t277}]      askedCompatMode=false inRecents=true isAvailable=true      lastThumbnail=null lastThumbnailFile=/data/system/recent_images/277_task_thumbnail.png      stackId=5      hasBeenVisible=true mResizeable=false firstActiveTime=1466260184521 lastActiveTime=1466260184521 (inactive for 173s)      * Hist #2: ActivityRecord{4846528 u0 com.fish.activitylife/.ThirdActivity t277}          packageName=com.fish.activitylife processName=com.fish.activitylife          launchedFromUid=10081 launchedFromPackage=com.fish.activitylife userId=0          app=ProcessRecord{4c18519 26051:com.fish.activitylife/u0a81}          Intent { cmp=com.fish.activitylife/.ThirdActivity }          frontOfTask=false task=TaskRecord{f101c60 #277 A=com.fish.activitylife U=0 sz=3}          taskAffinity=com.fish.activitylife          realActivity=com.fish.activitylife/.ThirdActivity          baseDir=/data/app/com.fish.activitylife-1/base.apk          dataDir=/data/user/0/com.fish.activitylife          stateNotNeeded=false componentSpecified=true mActivityType=0          compat={420dpi} labelRes=0x0 icon=0x7f030000 theme=0x7f090037          config={1.0 460mcc1mnc zh_CN ldltr sw411dp w411dp h659dp 420dpi nrml port finger -keyb/v/h -nav/h s.39}          stackConfigOverride={1.0 ?mcc?mnc ?locale ?layoutDir ?swdp ?wdp ?hdp ?density ?lsize ?long ?orien ?uimode ?night ?touch ?keyb/?/? ?nav/?}          taskDescription: iconFilename=null label="null" color=ff3f51b5          launchFailed=false launchCount=1 lastLaunchTime=-2m53s527ms          haveState=false icicle=null          state=<strong>RESUMED</strong> stopped=false delayedResume=false finishing=false          keysPaused=false inHistory=true visible=true sleeping=false idle=true          fullscreen=true noDisplay=false immersive=false launchMode=0          frozenBeforeDestroy=false forceNewConfig=false          mActivityType=APPLICATION_ACTIVITY_TYPE          waitingVisible=false nowVisible=true lastVisibleTime=-2m53s102ms      * Hist #1: ActivityRecord{2f57ac2 u0 com.fish.activitylife/.SecondActivity t277}          packageName=com.fish.activitylife processName=com.fish.activitylife          launchedFromUid=10081 launchedFromPackage=com.fish.activitylife userId=0          app=null          Intent { cmp=com.fish.activitylife/.SecondActivity }          frontOfTask=false task=TaskRecord{f101c60 #277 A=com.fish.activitylife U=0 sz=3}          taskAffinity=com.fish.activitylife          realActivity=com.fish.activitylife/.SecondActivity          baseDir=/data/app/com.fish.activitylife-1/base.apk          dataDir=/data/user/0/com.fish.activitylife          stateNotNeeded=false componentSpecified=true mActivityType=0          compat={420dpi} labelRes=0x0 icon=0x7f030000 theme=0x7f090037          config={1.0 460mcc1mnc zh_CN ldltr sw411dp w411dp h659dp 420dpi nrml port finger -keyb/v/h -nav/h s.39}          stackConfigOverride={1.0 ?mcc?mnc ?locale ?layoutDir ?swdp ?wdp ?hdp ?density ?lsize ?long ?orien ?uimode ?night ?touch ?keyb/?/? ?nav/?}          taskDescription: iconFilename=null label="null" color=ff3f51b5          launchFailed=false launchCount=0 lastLaunchTime=-3m5s390ms          haveState=true icicle=Bundle[mParcelledData.dataSize=760]          state=<strong>DESTROYED</strong> stopped=true delayedResume=false finishing=false          keysPaused=false inHistory=true visible=false sleeping=false idle=true          fullscreen=true noDisplay=false immersive=false launchMode=0          frozenBeforeDestroy=false forceNewConfig=false          mActivityType=APPLICATION_ACTIVITY_TYPE          waitingVisible=false nowVisible=false lastVisibleTime=-3m4s931ms      * Hist #0: ActivityRecord{b45a429 u0 com.fish.activitylife/.MainActivity t277}          packageName=com.fish.activitylife processName=com.fish.activitylife          launchedFromUid=2000 launchedFromPackage=null userId=0          app=null          Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.fish.activitylife/.MainActivity }          frontOfTask=true task=TaskRecord{f101c60 #277 A=com.fish.activitylife U=0 sz=3}          taskAffinity=com.fish.activitylife          realActivity=com.fish.activitylife/.MainActivity          baseDir=/data/app/com.fish.activitylife-1/base.apk          dataDir=/data/user/0/com.fish.activitylife          stateNotNeeded=false componentSpecified=true mActivityType=0          compat={420dpi} labelRes=0x7f060015 icon=0x7f030000 theme=0x7f090037          config={1.0 460mcc1mnc zh_CN ldltr sw411dp w411dp h659dp 420dpi nrml port finger -keyb/v/h -nav/h s.39}          stackConfigOverride={1.0 ?mcc?mnc ?locale ?layoutDir ?swdp ?wdp ?hdp ?density ?lsize ?long ?orien ?uimode ?night ?touch ?keyb/?/? ?nav/?}          taskDescription: iconFilename=null label="null" color=ff3f51b5          launchFailed=false launchCount=0 lastLaunchTime=-3m9s411ms          haveState=true icicle=Bundle[mParcelledData.dataSize=760]          state=<strong>DESTROYED</strong> stopped=true delayedResume=false finishing=false          keysPaused=false inHistory=true visible=false sleeping=false idle=true          fullscreen=true noDisplay=false immersive=false launchMode=0          frozenBeforeDestroy=false forceNewConfig=false          mActivityType=APPLICATION_ACTIVITY_TYPE          waitingVisible=false nowVisible=false lastVisibleTime=-3m8s633ms

可以看到此时ThirdActivity的state是RESUMED,而MainActivity、SecondActivity的state为DESTYOYED。
此时点击back,会导致ThirdActivity结束,退回到SecondActivity,但是此时SecondActivity的实例都没有,所以会重新创建并恢复SecondActivity。过程如下所示。
3 onPause2 onCreate hasSaveInstance true2 onStart2 onRestoreInstanceState2 onResume3 onStop3 onDestroy

同理,在点击back,会导致MainActivity被恢复,过程如下所示。
 2 onPause 1 onCreate hasSaveInstance true 1 onStart 1 onRestoreInstanceState 1 onResume 2 onStop 2 onDestroy


总结下,进程回收之后,再从历史程序里点击的时候,进程会重启,然后只恢复栈顶的activity,其他栈内的activity只有在需要的时候被恢复。

其他形式的进程死亡再恢复

刚才我们说的是由于系统内存不足而回收进程,导致进程死亡的,但是实际上导致进程死亡的还有崩溃(比如空指针),ddms杀进程.
这2种方式杀进程之后的恢复和回收进程的不太一样。因为这2种方式导致进程死亡,此时进程一般是前台进程,前台进程死亡,然后恢复并不会恢复栈顶activity,而是恢复栈顶前面的那个activity,为什么呢?
我们来解释下,如果是崩溃导致进程死亡,那崩溃发生在栈顶的那个activity,此activity根本没调用 onSaveInstanceState,那怎么恢复?没法恢复,只能恢复上一个activity。
同样,ddms杀进程也是一样的,只能恢复上一个。
举个例子,当前有activity,A,B,C,D,此时界面上显示的是D,如果这2方式杀了进程,那么进程重启之后,恢复的是activity C。
还有一点需要注意,如果此时D还没显示出来,界面上显示的是C,那用这2方式杀了进程后,重启后,恢复的是activity B,很好理解吧。
那有个问题,在D的onCreate过程中出了崩溃,此时再恢复,是恢复哪个activity?? 恩,D还在onCreate,所以此时界面是C,恢复的应该是前一个界面,所以恢复的是B。
结论,前台进程死亡后恢复,恢复的是当前显示的activity的上一个activity,记住activity要想被恢复,必须是经历过onSaveInstanceState的activity。

再来几个问题
有activity A,B,C
回收进程,重启,会恢复C,然后在C的恢复过程中遇到崩溃,此时会再重启,恢复哪个activity呢??   答案是B。
我们知道DDMS杀了进程之后,会重启并恢复。有activity A,B,C ,用ddms杀,导致重启恢复B,再杀,将恢复A,再杀呢?额,不行了,此时进程就被干掉了,不再恢复

2 0