前端开发入门到精通的在线学习网站

网站首页 > 资源文章 正文

Android 开发中文引导-进程和线程

qiguaw 2025-04-26 20:28:36 资源文章 4 ℃ 0 评论

当应用组件启动并没有任何其他组件运行时,安卓系统会为应用启动一个带有单个的执行线程的新Linux进程。默认,相同应用的所有组件运行在相同的进程和线程中(叫“主”线程)。如果应用组件启动时,那里已经存在该应用的进程(因为存在另一个该应用的组件),那么该组件在那个进程中启动并使用相同的执行线程。但是,你可以在让应用中的不同组件运行在单独的进程中,为任何进程创建额外的线程。

本文档讨论了进程和线程是如何在安卓应用中工作的。

进程

默认,相同应用的所有组件运行在相同的进程,大多应用不该更改这个。但是,如果你发现需要控制某个组件属于哪个进程,可以在清单文件中做到这点。

每种组件元素类型的清单项——<activity>,<service>,<receiver>,和<provider>——都支持可以指定那个组件应该运行所在进程的android:process属性。你可以设置此属性让每个组件都运行在自己的进程中,或一些组件共享同一个进程而其他的不共享。你还可以设置这个属性使不同应用的组件运行在相同的进程中——在那些应用都享有相同的Linux用户ID,使用相同的证书签名的条件下。

<application>元素也支持android:process属性,用以设置应用于所有组件的默认值。

当内存过低且被直接服务于用户的进程所需的时候,安卓系统在某些时候或许会决定关闭进程。被关闭进程中运行的应用组件因此被删除。当那些组件再有工作要做的时候,进程被再次启动。

当决定了那个进程要关闭的时候,安卓系统根据他们对用户的相对重要性评估权重。例如,它更容易关闭运行着不在屏幕上显示的活动的进程,相比运行着可见活动的进程。因此,是否终止进程的决定依赖于进程中运行的组件的状态。用于决定终止哪个进程的规则会在下面讨论。

进程生命周期

安卓系统试图尽可能长久的保持应用进程,但是最终还是需要移除旧的进程以为新进程和更重要的进程回收内存。要决定哪个进程保留,哪个进程关闭,系统将每个进程放入基于进程中运行的组件和那些组件的状态的“重要层级”中。为了恢复系统资源在必要时,最不重要的进程将首先被淘汰,然后是下一个最不重要的进程,然后如此类推。

这个重要度层次有5个级别。下面的列表按照重要度顺序显示了不同类型的进程(第一个进程是最重要的,最后一个会被关掉):

1. 前台进程

用户当前操作所需的进程。如何任何下面的条件为真,那么进程被认为是在前台的:

一般来说,在任何给定的时间仅会有少数的进程存在。关闭他们仅作为最后的手段——如果内存太低以至于他们不能继续一起运行。通常,在这时设备已经到了分页状态,因此需要关闭一些前台进程以保持用户界面的响应。

2. 可见进程

没有任何前台组件,但仍然可以影响用户在屏幕所见的进程。如果下列任一条件为真,那么进程被认为是可见的:

可见进程被认为是非常重要的,且不会被关闭,除非为了保持所有前台进程运行需要如此做。

  • 它运行着不在前台但仍然对用户可见的活动(它的onPause()方法已经被调用)。这可能会发生,例如,如果前台活动启动一个对话框,它允许之前的活动可以在后面被看到。

  • 它运行着绑定到可见(或前台)活动的服务。

3. 服务进程

运行着已经由startService启动的服务并没被分到2个更高分类中的任何一种的进程。 尽管服务进程没有直接与任何用户看到的东西有关,但他们通常执行用户关心的事情(比如在后台播放音乐或从网络下载数据),因此系统保持他们的运行除非没有足够的内存来与所有前台和可见进程一起保留他们。

4. 后台进程

持有当前不对用户可见的活动的进程(该活动的onStop()方法已被调用)。这些进程没有对用户体验直接的影响,系统可以在任何时候关掉他们以为后台,可见,服务进程回收内存。通常会有很多后台进程运行,因此他们被保存在一个LRU(最后最近使用)列表以保证用户最近所见的进程最后被关闭。如果活动正确的实现了它的生命周期方法,并正确的保存了当前状态,那么关闭进程将不会对用户体验有可见的影响,因为当用户浏览回到该活动时,该活动会恢复它所有的可见状态。参考活动文档以了解更多关于保存和恢复状态的信息。

5. 空进程

未持有任何工作中的应用组件的进程。保持这种进程存在的唯一理由就是为了缓存的目的,用以提高组件下次运行其中所需的启动速度。系统通常为了平衡进程缓存和底层内核缓存而关闭这些进程。

安卓系统根据根据进程中的组件活跃的重要成度,来将进程排在尽可能最高的级别上。例如,进程运行着一个服务和一个可见活动能,那么被排在可见进程级别上,而不是服务进程级别。

另外,进程的排名可能增加,因为其他的进程也许正依赖它——正服务于另一进程的进程永远不会比它所服务进程的排名低。例如,进程A中的内容提供者正服务于进程B中的客户端,或进程A的服务被绑定到进程B的组件上,进程A总被认为至少比进程B重要。

因为运行服务的进程比带有后台活动的进程排名更高,启动长久运行操作的活动最好启动一个服务来指定操作,而不是简单地创建一个工作线程——尤其该操作很可能会比该活动持续长久。例如,正上传图片到网页的活动应该启动服务来执行上传以便上传可以在后台继续运行即便用户离开该活动。使用服务保证了该操作之至少有“服务进程”优先级,无论活动发生了什么情况。同样的原因,广播接收者应该使用服务而不是简单的将耗时的操作放到线程中。

线程

当应用启动的时候,系统为该应用创建了一个执行线程,叫“主”线程。这个线程非常重要,因为它负责发送事件给相应的用户界面小部件,包括绘制事件。它也是应用与安卓界面工具包(android.widget和android.view里的组件)交互的所在线程。所以,主线程有时也叫界面线程。

系统不会为组件的每个实例创建单独的线程。所有运行在相同进程的组件都在界面线程中创建,系统对每个组件的调用也都从你那个线程中发出。因此,相应系统回调(例如通知用户操作的onKeyDown()或生命周期回调方法)的方法总运行在进程的界面线程中。

例如,当用户点击屏幕的按钮时,应用的界面线程发送这个点击事件给部件,它依次设置按下的状态并发送使其无效的请求到事件队列。界面线程从队列中取出请求并通知该部件应该重新绘制。

当你的应用执行密集的工作响应用户的交互时,单线程模型可以提供糟糕的性能除非你正确地实现应用。具体来说,如果一切都发生在界面线程,执行如网络访问或数据库查询这样的长时间的操作会阻塞整个界面。当线程被阻塞,没有事件可被发送,包括绘制事件。从用户的角度,应用看起来好像挂起了。更糟的是,如果界面线程被阻塞超过几秒钟(目前约5秒)用户会看到臭名昭著的“应用无响应”(ANR)对话框。用户那时如果不高兴,可能会决定退出应用,卸载掉它。

此外,安卓界面工具包是非线程安全的。因此,你一定不能在工作线程中处理界面——必须在界面线程中执行所有的用户界面处理。因此,安卓系统的单线程模型有两个简单的原则:

工作线程

由于上面描述的单线程模型,不阻塞界面线程对应用界面的响应能力是至关重要的。如果你有不是立刻要执行的操作,应该确保在单独的线程执行他们(“后台”或“工作”线程)。

例如,下面是点击监听器的一些代码,它在单独线程中下载图片并在ImageView中显示它:

首先,这样似乎工作良好,因为它创建了一个新的线程处理网络操作。但是,它违背了单线程模型的第2个原则:不在界面线程外访问安卓界面工具包——这个例子在工作线程修改了ImageView而不是在界面线程。这可能导致未定义和未知的行为,这可能会难以追踪且相当耗时。

要修正这个错误,安卓系统提供了几种从其他线程访问界面线程的方法。下面是可会有帮助的方法的列表:

例如,你可以使用View.post(Runnable)修正上面的代码:

现在这个实现是线程安全的:网络操作是在单独线程完成的,而ImageView是在界面线程里处理的。

但是,随着操作复杂度的增长,这种代码可能会变的复杂且难以维护。要处理与工作线程更复杂的交互,可以考虑在工作线程中使用Handler以处理从界面线程发来的消息。因此,或许最好的方案是扩展AsyncTask类,它简化了需要与界面交互的工作线程任务的执行。

使用AsyncTask

AsyncTask允许你对用户界面执行异步操作。它在工作线程执行阻塞操作,然后在界面线程中发布结果,而无需自己处理线程 和/或 handlers。

要使用它,你必须添加AsyncTask的子类并实现doInBackground回调方法,它运行在后台线程池中。要更新界面,你应该实现onPostExecute,它传递doInBackground中的结果并在界面线程中运行,因此你可以安全的更新你的界面。那时你可以在界面线程中调用execute运行这个任务。

例如,你可以这样使用AsyncTask来实现前面的例子:

现在界面是安全的且代码简单,因为它将工作分成要在工作线程完成的部分和要在界面线程完成的部分。

你应该阅读AsyncTask参考全面的了解如何使用这个类,不过下面是它如何工作的快速预览:

警告:当使用工作线程时可能遇到的另一个问题是由于运行时配置变更(例如用户更改朝向的时候)引起的意外重启,它可能会删除你的工作线程。要了解在这些重启中你可以如何维持任务和如何在活动删除时正确的取消任务,参考Shelves示例应用的源代码。

线程安全的方法

某些情况下,你实现的方法可能会从多个线程调用,因此必须写为线程安全的。

这主要适用于可以远程调用的方法——例如已绑定服务的方法。当对在IBinder中实现的方法的调用源于该IBinder运行所在相同的进程时,该方法在调用者的线程中执行。但是,当这个调用源于另一个进程,那么该方法在系统在进程中为IBinder(它不在进程的界面线程中执行)维护的线程池里选择的线程中执行。例如,尽管服务的onBind方法会在服务进程的界面线程中调用,但是onBind返回(例如,实现了RPC方法的子类)对象中实现的方法会从池中的线程调用。因为服务可能会有多个客户端,多个池线程可以在相同的时间调用同一IBinder方法。IBinder因此必须被实现为线程安全的。

内容提供者可以简单的接收源于其他进程的数据请求。尽管ContentResolver和ContentProvider类隐藏了进程间通信是如何管理的细节,响应那些请求的ContentProvider方法——query,insert,delete,update,和getType方法——从内容提供者的进程中的线程池里调用,而不是从进程的界面线程。因为这些方法可能在任何时间从任何数量的线程中调用,他们也必须实现为线程安全的。

进程间通信

安卓系统使用远程处理为进程间通信(IPC)提供了一种机制叫(RPC),其中方法被活动或其他应用组件调用,但带着返回给调用者的结果在远程执行(在另一个进程)。这需要将方法的调用和数据分解到系统可以理解的操作程度,将它从本地进程和访问空间传递到远端的进程和访问空间,然后在哪里重新组装并重新执行这个操作。然后返回值按相反的方向传递。安卓系统提供执行这些IPC传输的代码,因此你可以集中定义和实现RPC编程接口。

要执行IPC,你的应用必须使用bindService绑定到服务。要了解更多信息,参考服务开发者向导。

百度首发地址:《android中文开发向导》

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表