Android系列-应用程序的生命周期

294 阅读15分钟

应用程序生存周期的管理

在Android中,应用程序的生存周期不是由应用程序本身来控制,而是由系统决定。当先需要释放资源之时,Android系统便会终止运行的程序,或者回收Android组件(Android的四类组件: Activity, Service, Content providers BroadcastReceiver )。

优先级

优先级反映进程的重要性,当需要终止进程的时候,Android系统遵循如下优先级规则(值越小,越重要,越不会被kill):

Priority=1, Foreground

最重要的是前台进程,表示当前正在与用户交互的进程。主要有如下情况:

  1. 进程运行了一个在屏幕最前面的 Activity ,用户正与此 Activity 进行交互( AcitvityonResume 函数被调用)。
  2. 进程执行一个 BroadcastReceiverBroadcastReceiver.onReceive() 方法正在执行)。
  3. 当前在进程调用栈中,有一个 Service 正在运行(运行了 Service.onCreate(), Service.onStart(), 或 Service.onDestroy() )。

系统中只有少数进程处于此状况,只有当系统内存少到无法运行这些进程的时候,它们才会被杀掉。这个时候一般设备正处于页面换置状态,为保持用户界面有响应,才需要杀死这些进程。

Priority=2, Visible

其次重要的是可见进程,意思是这时候进程有一个可见的 Activity , 但是 Activity 部分可见没在最前端,与用户并无交互( onPause() 已被调用)。

例如: 前台进程被一个对话框挡住,但是其界面仍可见。

这样的进程也很重要,不应该被杀死。除非到了只能保留 Foreground 进程的情况。

Priority=3, Service

然后是服务进程,是指进程持有一个 Service ( startService 被调用后产生),进程不可见但是仍在做有用的工作。

例如:mp3背景音乐播放、或网络数据上传下载等。

系统应当尽量保持这样的进程,除非内存不足以运行 前台进程和可见进程。

Priority=4, Background

然后是后台进程,是指进程持有一个不可见的 ActivityonStop 方法已被调用),并且没有 Service 或者正在执行的 BroadcastReceiver

这种进程对用户体验没有什么影响(当然,前提是要保证 Activity 的生命周期控制正确),系统可以在任何时候,在需要前面更重要的三种进程回收内存之时,杀死它们。

一般系统中这样的进程有很多,Android会将他们保存在LRU列表中,需要之时会将最少使用者终止。

Priority=5, Empty

最不重要的是空进程,是指没有任何活动应用组件的进程。

保持空进程的唯一场景是,为了在下一次再次启动该应用程序的一个应用组件缓存之用,使之启动时间更短。

因此,系统会经常杀死这些进程,以保证在这些缓存空进程和内核缓存的整体系统资源平衡。

注意

LRU列表

所有在 "Empty" 列表中的进程都会被添加到一个最近最少使用列表(LRU)中,其中在其最前端的进程会在需要之时被 out-of-memory 杀掉。如果一个应用进程被用户重新启动,它将会被移动到这个队列的尾端,具有最低的优先级别。 Background的 进程也有它的 Background LRU 列表。

LRU 如下图所示:

img

进程重要性的分类

系统会根据进程中所有应用组件中最重要者,来为该进程进行分类(优先级),所以应用组件会对进程优先级有很大的影响,不同组件(如: Activity, Service, BroadcastReceiver )对应用程序的生命周期都有影响,如果使用不当,会导致预料外的现象。

例如: 一个 BroadcastReceiver 进程在 BroadcastReceiver.onReceive() 中接收 Intent 的时后启动了一个线程, 当函数( onReceive )返回的时候,系统便认为 BroadcastReceiver 进程不处于活动状态了,因此它的主进程便无存在的意义,这时候,系统可能随时都会杀死该进程,以释放资源,进而这个启动 BroadcastReceiver 的线程便被终止了。这个现象显然很可能不是我们期望的(可能接收消息之后,我们还需要那个线程运行一会),我们可以为 BroadcastReceiver 启动一个 Service , 这样系统便认为进程内始终是活动的。

进程优先级可能因其他依赖而提升。

例如:如果进程A有一个 Service, ServiceContext.BIND_AUTO_CREATE 标记,或者它正使用一个在进程B中的 ContentProvider, 这个时候进程B的优先级将会变得至少和进程A一样重要。

应用程序对象

当启动一个Android组件的时候,应用程序对象将被创建,这个对象具有如下生命周期函数。

  • onCreate() 在应用程序第一个组件启动之前被调用。

  • onLowMemory() Android系统请求应用程序清理内存的时候调用。

    一般所有 background 进程被杀掉之后,才会调用到这个函数。

  • onTrimMemory() Android系统请求应用程序清理内存之时被调用。

    一般用于当 background 进程太多的时候,对相对不重要的 background 进程进行清理。这个消息包含一个指示应用程序位置的标记。例如,常量 TRIM_MEMORY_MODERATE 指明进程在 Background LRU 列表的中间;释放内存有助于系统在列表中保持其他进程之后的运行,便于获得更好的整体性能。

  • onTerminate() 只用于测试,不会在产品中调用到(进程会被直接杀掉,执行不到包括该函数在内的代码)。

  • onConfigurationChanged() 当设备配置发生变化的时候被调用。

    配置变化之时,除 Activity 之外的其它组件不会重启, 它们自己处理配置变化的相关内容,例如重新分配资源。

Content provider 生存周期

一旦进入到一个 Content provider 中,那么它将不会被单独停止。由整个应用程序决定它是否停止。

Broadcast receiver 生存周期

Broadcast receiver 只有在 onReceive(Context, Intent) 被调用的时候才是合法存在的。当你的代码从这个函数返回后,系统便认为 BroadcastReceiver 对象结束,没有存在的意义了。

onReceive 中,切记你不能使用异步调用(例如前面提到的创建线程)。因为这个函数返回后,系统便将 BroadcastReceiver 杀掉,这样有可能是在异步操作完成之前,该对象就已经被销毁了。

另外你也不能在 BroadcastReceiver 中显示对话框,或者绑定一个 Service 。可以使用 NotificationManager 来通知,使用 Context.startService() 发送命令给 Service

对进程生存周期的影响

前面我们可知,进程调用 onReceive 时,进程便成为前台进程。我们需要注意,进程结束后,该进程便可能不是前台的了(取决于其他应用组件),可能会被杀死。所以如果是一个长期运行的前台进程,你可能需要将 ServiceBroadcast receiver 结合起来使用,保证进程在你操作的整个生命周期都是活跃的。

Service 生存周期

Service 可以被 started ,也可以拥有绑定到它身上的连接。这个时候,只要有 ServiceStarted ,或者有一个或多个连接绑定到 Service 上(通过 Cpontext.BIND_AUTO_CREATE 标记),系统就会保持 Service 的运行。当 Service 没有被 started 以及没有绑定到它身上的连接的时候,其 onDestroy() 函数将被调用,并且 Service 会被终止。所有清理工作(线程的停止,反注册 receivers 等)都应该在 onDestroy() 返回之前完成。

系统启动 Service 有两个方式。

通过 Context.startService()

如果调用了 Context.startService(), 系统将会获取 Service (需要时会调用 onCreate() 创建它),然后调用 onStartCommand(Intent, int, int) 方法(客户端提供参数)。至此, Service 将会持续运行,直到 Context.stopService() 或者 stopSelf() 被调用。

多次调用 Context.startService() 会导致多次调用 onStartCommand() 但是并不会嵌套,无论你调用多少次进行 started, 也只通过一次 Context.stopService() 或者 stopSelf() 就将 Service 终止,需要注意的是, Serivices 可以使用 stopSelf(int) 函数来确保 Service 在被 startedintents 处理完之前不会被停止。

对于已经启动的 Services ,可能会处于两种额外的操作模式,究竟处于哪种模式取决于 onStartCommand() 的返回值。 返回 START_STICKY 的表示用于需要时显式启动和停止的 Services; 返回 START_NOT_STICKY 或者 START_REDELIVER_INTENT 的服务,其只需要当处理任何发送给它们的命令的时候保持运行。

通过 Context.bindService()

Clients可以通过 Context.bindService() 获取一个到 Service 的持久性的连接。这个同样会在没有运行 Service 之时,创建一个 Service (若创建,则这时会调用 onCreate() ), 但是不会调用 onStartCommand() 。 这个client将会从 onBind(Intent) 中收到 Service 返回的 IBinder 对象,让client可以之后向 Service 发起 callback 。只要连接建立,无论client是否保持 ServiceIBinder 的引用,服务都将会保持运行。一般返回的 IBinder 用于(aidl写的)复杂的接口。

对进程生存周期的影响

当拥有 Service 的进程,其 Servicestarted ,或者有clients绑定与其 Service 之上时, Android会一直保持该进程。当内存不足需要杀掉已有进程的时候,其进程的优先级如下:

  1. 如果 Service 当前正在执行 onCreate(), onStartCommand() 或者 onDestroy() 之时,进程被做为前台进程,以确保这些代码会被执行。

  2. Service 已经被 started 后,其拥有进程的重要性会降低,会比任何当前屏幕上用户可见进程的优先级低,但是比任何非可见进程优先级高。

    因为用户可见的进程一般很少,所以除了内存非常低的情况外,一般 Service 进程不会被杀死。而用户并无法直接了解这样的后台服务,所以这样的进程也是一个合法的、将被kill掉的候选(在适当的时候会再 restarted ),这点我们需要注意。有些时候,已经运行了很久的、长期运行的 Services 也会更可能被杀掉,并且保证被杀掉(必要的时候,可以 restarted 它们)。

  3. 如果有clients绑定到这个 Service 上了,那么这个 Service 的所属进程的重要性(优先级)不会比其中最重要的client进程重要性低。

    也就是说,如果有一个client是可见的进程,那么这个 Service 也会被视作可见。client影响 Service 的重要性的方式,可以通过 BIND_ABOVE_CLIENT, BIND_ALLOW_OOM_MANAGEMENT, BIND_WAIVE_PRIORITY, BIND_IMPORTANT, 以及 BIND_ADJUST_WITH_ACTIVITY 来调整。

  4. 一个已经被 startedService 可以使用 startForeground(int,Notification) 接口将其变成前台状态,这样系统将认为它是一个与用户互动的进程,将不做为内存低时被杀死的候选进程(理论上,极端的内存少的压力情况下,相对前台进程它还是会被优先杀死,实际这点不用多考虑)。

这也意味着,在你的 Service 运行的大多数时间中,如果内存很少之时,它都可能会被系统杀死。如果发生了这个情况,系统过后会尝试重新启动 Service 。这样一个比较重要的影响便是,如果你实现 onStartCommand() 的时候异步在另外一个线程执行了一些工作,那么你可能会使用 START_FLAG_REDELIVERY ,这样系统会为你 re-deliver 一个 intent ,以便不会丢失在你的 Service 时,这个一步线程处理的工作。

当然,其他运行在 Service 一样的进程中(如 Activity )的应用程序组件,也能提升整个进程的重要性(优先级)至比 Service 的优先级更高。

Activity 生存周期

Activity 状态

  • Running

    也叫resumed状态。 Activity 可见并且与用户进行交互(获取用户焦点)。

  • Paused

    Activity 可见,但是部分可见(用户焦点在其他 Activity 上),实例在运行中但是可能会被系统kill掉。

  • Stopped

    Activity 不可见,实例在运行中但是可能会被系统kill掉。

  • Killed

    Activity 通过调用它的 finish() 函数被系统终止掉。

其实还有许多其它状态,上面的 Running, Paused, Stopped 是三个状态是应用程序能够保持的状态,其他状态都是过度。下图展示了状态的切换:

img

Activity 生存周期函数

这里只列出重要的函数:

  • onCreate()

    调用后 Activity 会被创建。用来初始化 Activity ,比如用户界面。

  • onResume()

    Activity 重现可见或者重新被用户用来交互之时被调用。常用来初始化域、注册帧听函数、绑定 Service 等等。

  • onPause()

    当另外一个 Activity 被调用到前台的时候调用。一般在 Activity 变得不可见之前会被调用。可以用来释放资源或者保存应用程序数据。比如注销帧听函数、 intent receiver ,解绑服务,或者移除系统服务帧听函数。

  • onStop()

    一旦 Activity 变的不可见的时候就会调用这个函数。 时间到或者Cpu的关闭操作,例如写信息到一个数据库中将会导致 onStop 函数被调用。

Android中还有一些其他的生存周期函数, 比如 onStart, onRestart, onDestroy 。有些函数并不一定要调用:比如 onDestroy

下面是一个图,展示了生存周期函数的调用:

img

Android对 Activity 的终止

理论上Android可以终止一个独立的 Activities 以释放系统资源,但是实际上,Android并不是回收独立的 Activity 而是根据前面 Application 的生命周期管理规则对整个进程进行决策。

注:在Android官方文档中说Android可以kill一个单独的 Activity ,但是事实上Android核心开发团队的成员 Dianne Hackborn 声明Android只kill整个的进程而不是单独的 Activity

Activity 会在合适的时间保存和回复它的状态。当 Activity 不可见的时候,它会停掉任何不必要的动作,以节省系统资源。

一般来说,我们使用 onPause 来停止系统框架的帧听,以及界面更新;使用 onStop 来保存应用程序数据。这些方法要在 Activity 终止之前调用。 必要的时候, onResume 基于保存的数据重新注册listener以及进行界面的更新。

对于资源管理,Android 会在配置变化之时重新创建 ActivityConfiguration 对象包含当前设备的配置,如果配置发生了变化, Activityrestarted ,以便可能会为这个配置使用不同的资源。

Activity 实例状态

Activity 实例状态用来恢复 Activity 到之前的状态,状态信息保存的是用户对 Activity 的操作变化数据,在配置变化或者恢复用户选择重启 Activity 时相互传递,应用程序负责实例状态的恢复。例如:在一个包含成千表项的列表中滚动选择时,位置信息便是这样的数据。如果 Activity 重新创建后没有保存相应的位置信息,将会很麻烦。

非持久状态信息

这类信息在内存中(例如 Activity 中保存的成员值),当 Activitystopped 或者 paused 的时候,会被保存;当重新 resumed 的时候,会被恢复,使得用户看起来 Activity 和它之前被操作的状态一致。

持久化状态信息

这类信息不会随对象的消失而消失。如果内存低,导致系统为释放内存,而对 Activity 实例对象进行析构,这样当用户再次回访到这个 Activity 的时候,便需要重新创建 Activity, 并且重建的时候,需要用持久化的状态信息恢复之前 Activity 的状态。

在析够对象之前,调用 onSaveInstanceState() 将持久化的信息保存在 Bundle 中, 在 Activityrestart 的时候,将其做为参数传递给 onCreate() 以及 onRestoreInstanceState() 来重建 Activity 实例并恢复之前的状态。

如果你重载了 onSaveInstanceState() 以及 onRestoreInstanceState(), 你应该调用父类实现,因为Anroid会借助这样保存默认视图信息。例如 EditText 会这样保存其中的内容。

注:我们在恢复实例状态的时候,要尽量使用 onRestoreInstanceState() 。因为这个方法会将初始化设置和恢复状态的概念分开对待。

应用注意

如果用户在和 Activity 交互,然后按下了 Back 按键,或者 Activityfinish() 方法被调用了, Activity 会被从当前的 Activity 栈中移走并且回收。这个时候,不会有实例状态,并且 onSaveInstanceState() 也不会被调用。

如果用户在和 Activity 交互,并且按下了 Home 按键,那么 Activate 必须被保存。 onSaveInstanceState() 会被调用。如果用户重启应用程序,那么将会resume或者restart最后运行的 Activity 。如果她 restart 这个 Activity 的时候,他会提供保存数据的bundle给 onRestoreInstanceState()onCreate() 函数。

注:如果用户按下 Back 按键,不会调用 onSaveInstanceState() 函数,所以这时需要获取持久数据的时候,不要用这个方法来保存数据。

总结

这里对状态和生命周期函数的关系进行总结,主要涉及到 Activity 三个方面的生存周期:

  • 整体性的生存周期:介于 onCreate()onDestroy() 之间。

    img

  • 可见性的生存周期:介于 onStart()onStop() 之间。

    img

  • 前台后台的生存周期:介于 onResume()onPause() 之间。

    img