Android基础理解:进程与线程
Android中,当一个应用程序启动并且应用程序没有组件在运行时,系统会为应用程序创建一个新额单线程的进程。默认情况下,一个应用程序的所有组件都运行在同一个进程中,以及同一个 main 线程。
在系统中已经有应用程序正在运行时,被启动的组件会在这个应用程序的同一进程中运行。然而,组件的运行也可以被设计安排在一个不同的进程中执行,并且在任一进程中创建不同的线程。
进程
默认的情形下,一个应用程序的所有组件都运行在相同的进程,并且大多数的应用程序不会改变这种情形。但是,可以通过manifest的修改实现将某个组件运行的进程进行修改,使其运行在一个不同的进程中。
manifest文件中为每个组件(<activity>
<service>
<receiver>
<provider>
)提供了 android:process
属性,修改这个属性值可以表示对应组件所述的运行进程。
manifest的 application
元素同样有 android:process
属性可以设置应用程序的所有组件运行的进程。
Android会为用户更加频繁交互的应用程序需求资源时,关闭其他进程。在关闭的进程中运行的组件就会被销毁(destroyed)。
要关闭哪个进程取决于Android系统评估的应用程序对于用户的重要性。
- 相比于正在运行 可见activity 的进程,activity组件不可见进程更轻而易举地被关闭。
是否终止一个进程取决于进程中运行的组件状态。
进程和App生命周期
多数情况下,每个应用程序都运行在自己的Linux进程中。一个进程创建的取决于应用程序需要内存运行,并且保持运行,直到系统要求取回内存给其他应用程序运行,并且被回收内存的进程不再需要。
Android的不同寻常的基本特征,一个应用程序进程额生命周期不直接由进程自身决定。相反,由系统根据
- 系统所知的运行中的应用程序,
- 这些事务对用户的重要性,
- 系统中总体内存的可用性,
来决定进程的生命周期。
应用开发过程中,理解应用程序组件(尤其Activity
, Service
, BroadcastReceiver
)对应用程序进程的影响很重要。无法正确使用这些组件可能导致系统直接查杀应用进程。
进程类别
系统因为内存低要求回收内存时,需要查杀进程。Android中进程都依据进程中组件的运行状态被赋予重要性值。按重要性顺序,进程类型被分为:
-
前台进程。 前台进程 是用户当前正在操作所需要的程序。应用进程内的各种组件可以以不同的方式使应用被视为 前台进程。一个进程被视为 前台进程 只要符合以下任一条:
- 进程有一个可见的且用户正在与之交互的 Activity 正在运行( Activity 的
onResume()
已经被调用)。 - 进程有一个 BroadcastReceiver 正在运行( BroadcastReceiver 的
onReceive()
方法正在执行)。 - 进程有一个 Service 正在执行( Service 的
onCreate()
onStart()
或onDestroy()
方法被调用)。
系统中只有少数一些进程是这样的,且只有当系统内存太低,以至于这些进程都无法继续运行时,才会被作为最后的手段被终止。通常,如果发生这种情况,设备已达到内存分页状态,因此需要此操作来保持用户界面的响应。
- 进程有一个可见的且用户正在与之交互的 Activity 正在运行( Activity 的
-
可见进程。 可见进程 是用户当前可感知到的进程。杀死 可见进程 对于用户体验只有负面影响。一个进程被视为 可见进程 符合以下任一条件:
- 进程有一个 Activity 对用户可见,但不是 foreground 状态( Activity 的
onPause()
方法被调用)。这中场景是可能发生的,当前的 foreground Activity 显示一个dialog,导致前一个 Activity 被遮挡在后面。 - 进程有一个 Service 通过
Service.startForeground()
调用而运行(通过这种方式启动的 Service 要求系统将其看做是 用户可感知 的或者基本上就好像它是可见的)。 - 系统使用的,可以可感知的 Service ,例如 输入法服务。
种类进程在系统中的数量比 前台进程 要少,但仍然相对受控。这些进程被视为非常重要,并且不会被杀掉除非这些进程的运行会保持所有 前台进程 的运行。
- 进程有一个 Activity 对用户可见,但不是 foreground 状态( Activity 的
-
服务进程。 服务进程 是调用
startService()
启动一个 Service 的进程。尽管这类进程对用户不直接可见,但这类进程执行的是用户关注的任务(例如 后台进程的数据上传和下载),因此系统总是会保留这些运行的进程,直到系统声明回收内存给 前台进程 和 可见进程 执行。长时间运行(超过30mins)的服务进程在重要性上会被降级,进程会被加入到 缓存进程 列表中。
需要长时间运行的进程可以通过
setForeground
创建。如果若果是一个严格时间执行的周期性进程,可以通过AlarmManager
执行。 了解更多耗时工作。 这有利于避免耗时 service 使用过多资源,例如,泄漏内存,有碍于用户体验。 -
缓存进程。 缓存进程 是当前不再需要的进程,系统可以根据需要,在其他进程需要更多内存时对 缓存进程 进行查杀。在一个正常运行的系统中,这类进程只在资源管理中存在。
一个正常运行的系统总是有若干个 缓存进程 ,用以高效地切换应用程序,并且在需要时查杀最老的 缓存进程。只有在紧急的情况下,系统需要查杀所有的 缓存进程 并且开始查杀 服务进程。
缓存进程 通常保持有一个或多个当前对用户不可见的
Activity
实例(他们的onStop()
方法已经被调用并返回)。假设系统查杀这类进程时,Activity
正确实现了生命周期,在回到应用程序时用户体验不会受到影响。在Activity实例在新进程中重新创建时,之前保存的状态会被恢复。要注意的是 系统查杀一个进程时,onDestroy()
方法不能保证被调用。缓存进程 被保存在一个列表内。列表的排队策略依据平台的实现。通常,列表中保留更多有用进程(例如 用户home应用程序,或用户最后看见Activity的进程) 优于其他类型进程。
在对进程进行分类时,系统基于在存活的进程内所有组件中找到做中重要的级别作出决策。一个进程的优先级可以基于其他进程对它的依赖被提升。
耗时工作
WorkerManager 内置了对长期工作任务的支持。在这些情况下,WorkManager 会向系统提供一个信号,表明当前进程在执行时应该尽可能被保活。这些工作任务可以运行超过10mins。
在表面之下,WorkManager 管理并运行一个前台服务(foreground service)代表你执行一个 WorkRequest
,同时显示可配置的通知。
线程
在一个应用程序启动之后,系统会伴随着创建一个 main 线程(也叫 UI线程)。
Main线程非常重要:
- 它负责 UI与 用户交互时的事件分发工作,包含 绘制。
- 它是Android UI组件包
android.view
android.widget
内组件运行所属的线程,也因此叫做 UI线程。
有时,特殊情形下,主线程也可能不是UI线程。
线程标注
线程标注检查一个方法是否在对应类型的线程中被调用。以下是支持的线程标注:
@MainThread
@UiThread
@WorkerThread
@BinderThread
@AnyThread
编译工具对待 @MainThread
和 @UiThread
标注时,认为两者是可互换的。但在系统app有时在不同线程中有不同的界面,这种情况下 @UiThread
就有别于 @MainThread
。因此,与app界面结构有关联的方法应该标注 UiThread
,@MainThread
只被用以标注与app生命周期有关的方法。
假设一个类中所有的方法均要求在同一个线程中执行,那么可以在类声明上标注一个线程类型。
使用 WorkerThread
线程标注注解的方法或类用以检查是否在一个恰当的后台线程中被调用。
单UI线程缺点
应用程序运行过程时,系统不会为运行的组件创建一个单独的线程。应用程序所有组件都在 UI线程 中创建并初始化,系统回调到组件的事件分发也在 UI线程 中完成。
UI线程负责很重要,负责的工作在系统中已经确认好。应用程序负责的工作冗杂,在单线程工作模式下,很容易造成性能缺失。
场景:
- 用户交互时主线程处理负责工作。
- 在主线程中执行耗时操作,例如:网络访问,查询DB会阻塞UI。
线程阻塞,会阻止所有的事件分发,包含UI绘制事件。在界面上看到的现象是,在没有响应后一会儿,ANR弹窗会显示。
Android组件是非线程安全的。 所有的UI组件只能在UI线程中被更新,被操作。在一个工作线程中直接访问组件是非法操作,会造成应用程序crash。
在单UI线程操作时,谨记:
- 不要阻塞UI线程。
- 不要再UI线程外访问UI组件。
工作线程
上述说明,单UI线程模式下,应用程序很容易有性能问题,甚至ANR问题。因此应用程序的UI响应能力很重要。若应用程序执行的是非即时工作,确保这些工作在一个单独的后台/工作线程中被执行。
Android提供了在非UI线程中访问UI新程的方式:
Activity.runOnUiThread(Runnable)
View.post(Runnable)
View.postDelayed(Runanble, long)
上述的方式只适用于简单的场景中,随项目越复杂,推荐在工作线程中使用 Handler
处理 UI线程 的消息。Background Work Overview