- android
- android.accessibilityservice
- android.accounts
- android.content
- android.database.sqlite
- android.graphics
- android.location
- android.media
- android.net
- android.os
- android.text
- android.view
- android.view.inputmethod
- android.widget
- AbsListView
- AbsoluteLayout
- AbsSeekBar
- AbsSpinner
- AdapterView
- AnalogClock
- BaseAdapter
- BaseExpandableListAdapter
- Button
- CheckBox
- CheckedTextView
- Checkable
- Chronometer
- CompoundButton
- CursorAdapter
- CursorTreeAdapter
- DatePicker
- DialerFilter
- DigitalClock
- EditText
- Filter
- Filter.FilterListener
- Filter.FilterResults
- ExpandableListAdapter
- Filterable
- Gallery
- Gallery.LayoutParams
- GridView
- GridLayout
- RadioGroup
- ImageView
- HorizontalScrollView
- ImageButton
- ImageSwitcher
- FilterQueryProvider
- ListAdapter
- ListView
- MediaController
- QuickContactBadge
- RadioButton
- RatingBar
- RelativeLayout
- RemoteViews
- ResourceCursorAdapter
- ResourceCursorTreeAdapter
- Scroller
- ScrollView
- SearchView
- SeekBar
- SeekBar.OnSeekBarChangeListener
- SimpleAdapter
- SimpleCursorAdapter
- SimpleCursorTreeAdapter
- SimpleExpandableListAdapter
- SlidingDrawer
- Spinner
- SpinnerAdapter
- WrapperListAdapter
- TabHost
- TabHost.TabSpec
- TextView
- TimePicker
- Toast
- TableLayout
- TableRow
- TableRow.LayoutParams
- TabWidget
- TextSwitcher
- ToggleButton
- TwoLineListItem
- VideoView
- ViewAnimator
- ViewFlipper
- ViewSwitcher
- ZoomButtonsController
- ZoomButton
- ZoomControls
- dalvik.system
进程和线程
译者微博: http://weibo.com/popapa
版本:Android 3.2 r1
原文
http://developer.android.com/guide/topics/fundamentals/processes-and-threads.html
快速查看 · 默认情况下,每个应用程序运行在各自的进程中,应用程序中的所有组件也都运行在其中。 · activity 中所有运行缓慢的、阻塞的操作都应该运行在新建的线程中,以免减缓用户界面运行速度。 在本文中 |
进程和线程
如果某个应用程序组件是第一次被启动,且这时应用程序也没有其他组件在运行,则 Android 系统会为应用程序创建一个包含单个线程的 linux 进程。默认情况下,同一个应用程序的所有组件都运行在同一个进程和线程里(叫做“main”主线程)。如果组件启动时,已经存在应用程序的进程了(因为应用程序的其它组件已经在运行了),则此组件会在已有的进程和线程中启动运行。不过,可以指定组件运行在其他进程里,也可以为任何进程创建额外的线程。
本文讨论进程和线程是如何在 Android 应用程序中发挥作用的。
默认情况下,同一个应用程序内的所有组件都是运行在同一个进程中的,大部分应用程序也不会去改变它。不过,如果需要指定某个特定组件所属的进程,则可以利用 manifest 文件来达到目的。
manifest 文件中的每种组件元素—— <activity> 、 <service> 、 <receiver> 和 <provider> ——都支持定义 android:process 属性,用于指定组件运行的进程。设置此属性即可实现每个组件在各自的进程中运行,或者某几个组件共享一个进程而其它组件运行于独立的进程。设置此属性也可以让不同应用程序的组件运行在同一个进程中——实现多个应用程序共享同一个 Linux 用户 ID、赋予同样的权限。
<application> 元素也支持 android:process 属性,用于指定所有组件的默认进程。
如果内存不足,可又有其它为用户提供更紧急服务的进程需要更多内存,Android 可能会决定关闭一个进程。在此进程中运行着的应用程序组件也会因此被销毁。当需要再次工作时,会为这些组件重新创建一个进程。
在决定关闭哪个进程的时候,Android 系统会权衡它们相对用户的重要程度。比如,相对于一个拥有可见 activity 的进程,更有可能去关闭一个 activity 已经在屏幕上看不见的进程。也就是说,是否终止一个进程,取决于运行在此进程中组件的状态。终止进程的判定规则将在后续内容中讨论。
Android 系统试图尽可能长时间地保持应用程序进程,但为了新建或者运行更加重要的进程,总是需要清除过时进程来回收内存。为了决定保留或终止哪个进程,根据进程内运行的组件及这些组件的状态,系统把每个进程都划入一个“重要性层次结构”中。重要性最低的进程首先会被清除,然后是下一个最低的,依此类推,这都是回收系统资源所必需的。
重要性层次结构共有 5 级,以下列表按照重要程度列出了各类进程(第一类进程是 最重要 的,将最后一个被终止):
1. 前台进程
用户当前操作所必须的进程。满足以下任一条件时,进程被视作处于前台:
o 其中运行着正与用户交互的 Activity (Activity 对象的 onResume() 方法已被调用)。
o 其中运行着被正与用户交互的 activity 绑定的服务 Service 。
o 其中运行着“前台”服务 Service ——服务以 startForeground() 方式被调用。
o 其中运行着正在执行生命周期回调方法( onCreate() 、 onStart() 或 onDestroy() )的服务 Service 。
o 其中运行着正在执行 onReceive() 方法的 BroadcastReceiver 。
一般而言,任何时刻前台进程都是为数不多的,只有作为最后的策略——当内存不足以维持它们同时运行时——才会被终止。通常,设备这时候已经到了内存分页状态(memory paging state) 的地步,终止一些前台进程是为了保证用户界面的及时响应。
2. 可见进程
没有前台组件、但仍会影响用户在屏幕上所见内容的进程。满足以下任一条件时,进程被认为是可见的:
o 其中运行着不在前台的 Activity ,但用户仍然可见到此 activity( onPause() 方法被调用了)。比如以下场合就可能发生这种情况:前台 activity 打开了一个对话框,而之前的 activity 还允许显示在后面。
o 其中运行着被可见(或前台)activity 绑定的服务 Service 。
可见进程被认为是非常重要的进程,除非无法维持所有前台进程同时运行了,它们是不会被终止的。
3. 服务进程
此进程运行着由 startService() 方法启动的服务,它不会升级为上述两级别。尽管服务进程不直接和用户所见内容关联,但他们通常在执行一些用户关心的操作(比如在后台播放音乐或从网络下载数据)。因此,除非内存不足以维持所有前台、可见进程同时运行,系统会保持服务进程的运行。
4. 后台进程
包含目前用户不可见 activity(Activity 对象的 onStop() 方法已被调用)的进程。这些进程对用户体验没有直接的影响,系统可能在任意时间终止它们,以回收内存供前台进程、可见进程及服务进程使用。通常会有很多后台进程在运行,所以它们被保存在一个 LRU(最近最少使用)列表中,以确保最近被用户使用的 activity 最后一个被终止。如果一个 activity 正确实现了生命周期方法,并保存了当前的状态,则终止此类进程不会对用户体验产生可见的影响。因为在用户返回时,activity 会恢复所有可见的状态。关于保存和恢复状态的详细信息,请参阅 Activities 文档。
5. 空进程
不含任何活动应用程序组件的进程。保留这种进程的唯一目的就是用作缓存,以改善下次在此进程中运行组件的启动时间。为了在进程缓存和内核缓存间平衡系统整体资源,系统经常会终止这种进程。
依据进程中目前活跃组件的重要程度,Android 会给进程评估一个尽可能高的级别。例如:如果一个进程中运行着一个服务和一个用户可见的 activity,则此进程会被评定为可见进程,而不是服务进程。
此外,一个进程的级别可能会由于其它进程的依赖而被提高——为其它进程提供服务的进程级别永远不会低于使用此服务的进程。比如:如果 A 进程中的 content provider 为进程 B 中的客户端提供服务,或进程 A 中的服务被进程 B 中的组件所调用,则 A 进程至少被视为与进程 B 同样重要。
因为运行服务的进程级别是高于后台 activity 进程的,所以,如果 activity 需要启动一个长时间运行的操作,则为其启动一个服务 service 会比简单地创建一个工作线程更好些——尤其是在此操作时间比 activity 本身存在时间还要长久的情况下。比如,一个 activity 要把图片上传至 Web 网站,就应该创建一个服务来执行之,即使用户离开了此 activity,上传还是会在后台继续运行。不论 activity 发生什么情况,使用服务可以保证操作至少拥有“服务进程”的优先级。同理,上一篇中的广播接收器 broadcast receiver 也是使用服务而非线程来处理耗时任务的。
应用程序启动时,系统会为它创建一个名为“main”的主线程。主线程非常重要,因为它负责把事件分发给相应的用户界面 widget——包括屏幕绘图事件。它也是应用程序与 Android UI 组件包(来自 android.widget 和 android.view 包)进行交互的线程。因此,主线程有时也被叫做 UI 线程。
系统并不会为每个组件的实例都创建单独的线程。运行于同一个进程中的所有组件都是在 UI 线程中实例化的,对每个组件的系统调用也都是由 UI 线程分发的。因此,对系统回调进行响应的方法(比如报告用户操作的 onKeyDown() 或生命周期回调方法)总是运行在 UI 线程中。
举个例子,当用户触摸屏幕上的按钮时,应用程序的 UI 线程把触摸事件分发给 widget,widget 先把自己置为按下状态,再发送一个显示区域已失效(invalidate)的请求到事件队列中。UI 线程从队列中取出此请求,并通知 widget 重绘自己。
如果应用程序在与用户交互的同时需要执行繁重的任务,单线程模式可能会导致运行性能很低下,除非应用程序的执行时机刚好很合适。如果 UI 线程需要处理每一件事情,那些耗时很长的操作——诸如访问网络或查询数据库等——将会阻塞整个 UI(线程)。一旦线程被阻塞,所有事件都不能被分发,包括屏幕绘图事件。从用户的角度看来,应用程序看上去像是挂起了。更糟糕的是,如果 UI 线程被阻塞超过一定时间(目前大约是 5 秒钟),用户就会被提示那个可恶的“应用程序没有响应”(ANR) 对话框。如果引起用户不满,他可能就会决定退出并删除这个应用程序。
此外,Andoid 的 UI 组件包并 不是 线程安全的。因此不允许从工作线程中操作 UI——只能从 UI 线程中操作用户界面。于是,Andoid 的单线程模式必须遵守两个规则:
1. 不要阻塞 UI 线程。
2. 不要在 UI 线程之外访问 Andoid 的 UI 组件包。
根据对以上单线程模式的描述,要想保证程序界面的响应能力,关键是不能阻塞 UI 线程。如果操作不能很快完成,应该让它们在单独的线程中运行(“后台”或“工作”线程)。
例如:以下响应鼠标点击的代码实现了在单独线程中下载图片并在 ImageView 显示:
public void onClick(View v) {
new Thread(new Runnable() {
public void run() {
Bitmap b = loadImageFromNetwork("http://example.com/image.png");
mImageView.setImageBitmap(b);
}
}).start();
}
乍看起来,这段代码似乎能运行得很好,因为创建了一个新的线程来处理访问网络的操作。可是它违反了单线程模式的第二条规则: 不要 在 UI 线程之外访问 Andoid 的 UI 组件包——以上例子在工作线程里而不是 UI 线程里修改了 ImageView 。这可能导致不明确、不可预见的后果,要跟踪这种情况也是很困难很耗时间的。
为了解决以上问题,Android 提供了几种途径来从其它线程中访问 UI 线程。下面列出了有助于解决问题的几种方法:
· Activity.runOnUiThread(Runnable)
· View.postDelayed(Runnable, long)
比如说,可以使用 View.post(Runnable) 方法来修正上面的代码:
public void onClick(View v) {
new Thread(new Runnable() {
public void run() {
final Bitmap bitmap = loadImageFromNetwork("http://example.com/image.png");
mImageView.post(new Runnable() {
public void run() {
mImageView.setImageBitmap(bitmap);
}
});
}
}).start();
}
以上代码的执行现在是线程安全的了:网络相关的操作在单独的线程里完成,而 ImageView 是在 UI 线程里操纵的。
不过,随着操作变得越来越复杂,这类代码也会变得很复杂很难维护。为了用工作线程完成更加复杂的交互处理,可以考虑在工作线程中用 Handler 来处理 UI 线程分发过来的消息。当然,最好的解决方案也许就是继承使用异步任务类 AsyncTask ,此类简化了一些工作线程和 UI 交互的操作。
使用异步任务
异步任务 AsyncTask 允许以异步的方式对用户界面进行操作。它先阻塞工作线程,再在 UI 线程中呈现结果,在此过程中不需要对线程和 handler 进行人工干预。
要使用异步任务,必须继承 AsyncTask 类并实现 doInBackground() 回调方法,该对象将运行于一个后台线程池中。要更新 UI 时,须实现 onPostExecute() 方法来分发 doInBackground() 返回的结果,由于此方法运行在 UI 线程中,所以就能安全地更新 UI 了。然后就可以在 UI 线程中调用 execute() 来执行任务了。
例如,可以利用 AsyncTask 来实现上面的那个例子:
public void onClick(View v) {
new DownloadImageTask().execute("http://example.com/image.png");
}
private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
/** The system calls this to perform work in a worker thread and
* delivers it the parameters given to AsyncTask.execute() */
protected Bitmap doInBackground(String... urls) {
return loadImageFromNetwork(urls[0]);
}
/** The system calls this to perform work in the UI thread and delivers
* the result from doInBackground() */
protected void onPostExecute(Bitmap result) {
mImageView.setImageBitmap(result);
}
}
现在 UI 是安全的,代码也得到简化,因为任务分解成了工作线程内完成的部分和 UI 线程内完成的部分。
要全面理解这个类的使用,须阅读 AsyncTask 的参考文档。以下是关于其工作方式的概述:
· 可以用 generics 来指定参数、进度值和任务最终值的类型。
· 工作线程中的 doInBackground() 方法会自动执行。
· onPreExecute() 、 onPostExecute() 和 onProgressUpdate() 方法都在 UI 线程中调用。
· doInBackground() 的返回值会传给 onPostExecute() 。
· 在 doInBackground() 内的任何时刻,都可以调用 publishProgress() 来执行 UI 线程中的 onProgressUpdate() 。
· 可以在任何时刻、任何线程内取消任务。
注意: 在使用工作线程时,可能遇到的另一个问题是由于 运行配置的改变 (比如用户改变了屏幕方向)导致 activity 意外重启,这可能会销毁该工作线程。要了解如何在这种情况下维持任务执行、以及如何在 activity 被销毁时正确地取消任务,请参见 Shelves 例程的源代码。
线程安全的方法
在某些场合,方法可能会从不止一个线程中被调用,因此这些方法必须是写成线程安全的。
对于能被远程调用的方法——比如绑定服务( bound service )中的方法,这是理所当然的。如果对 IBinder 所实现方法的调用发起于 IBinder 所在进程的内部,那么这个方法是执行在调用者的线程中的。但是,如果调用发起于其他进程,那么这个方法将运行于线程池中选出的某个线程中(而不是运行于进程的 UI 线程中),该线程池由系统维护且位于 IBinder 所在的进程中。例如,即使一个服务的 onBind() 方法是从服务所在进程的 UI 线程中调用的,实现了 onBind() 的方法对象(比如,实现了 RPC 方法的一个子类)仍会从线程池中的线程被调用。因为一个服务可以有不止一个客户端,所以同时可以有多个线程池与同一个 IBinder 方法相关联。因此 IBinder 方法必须实现为线程安全的。
类似地,内容提供者(content provider)也能接收来自其它进程的数据请求。尽管 ContentResolver 类、 ContentProvider 类隐藏了进程间通讯管理的细节, ContentProvider 中响应请求的方法—— query() 、 insert() 、 delete() 、 update() 和 getType() 方法——是从 ContentProvider 所在进程的线程池中调用的,而不是进程的 UI 线程。因为这些方法可能会从很多线程同时调用,它们也必须实现为线程安全的。
Android 利用远程过程调用(remote procedure call,RPC)提供了一种进程间通信(IPC)机制,通过这种机制,被 activity 或其他应用程序组件调用的方法将(在其他进程中)被远程执行,而所有的结果将被返回给调用者。这就要求把方法调用及其数据分解到操作系统可以理解的程度,并将其从本地的进程和地址空间传输至远程的进程和地址空间,然后在远程进程中重新组装并执行这个调用。执行后的返回值将被反向传输回来。Android 提供了执行 IPC 事务所需的全部代码,因此只要把注意力放在定义和实现 RPC 编程接口上即可。
要执行 IPC,应用程序必须用 bindService() 绑定到服务上。详情请参阅服务 Services 开发指南。
补充说明
本文参考了 http://leybreeze.com/?p=532 ,修正一些重大错误,增加新版本的内容。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论