线程
当一个应用程序启动后,系统会创建一个主线程来执行应用程序。这个线程非常重要,因为他能够管理分发线程给合适的用户接口组件,这包括了绘制事件。也是这个线程与安卓UI工具包中的组件进行交互。(来自包android.widget 和包android.view中的组件)。因为这个原因,主线程有时也被叫做UI线程。
系统不会为每一个组件实例创建UI线程。运行在同一进程中的所有组件都是由UI线程进行初始化的,并且,系统对于这些组件的调用也是由UI线程进行分发。因此,相应系统回调的方法也常常运行在进程中的UI线程中。(例如onKeyDown()报告用户操作或者生命周期回调方法)。
举个例子,当用户触摸屏幕上的一个按键时,你的应用程序的UI线程分发触摸事件给小组件(widget),小组件会设置自己成为“按下状态”,并向事件队列发送宣布自身无效(invalidate:也就是请求重绘)的请求。UI线程将请求出列,然后通知组件重新绘制自己。
当你的应用程序紧张的处理用户交互的响应时,这种单一的线程模型可能会表现得很差,除非你的应用程序实现的非常合理。特别的,如果任何事情都发生在UI线程中,进行长时间的操作,例如访问网络或者数据库查询都将阻塞整个UI线程。当线程被阻塞时,没有事件能够被分发,包括了绘制事件。从用户的观点来看,应用程序就表现为挂起的状态。更糟的是,如果UI线程阻塞超过了一定的秒数(例如现在设为5秒)那么臭名昭著的ANR(应用无法响应)对话框就会出现在用户眼前。用户可能会决定退出,甚至如果他们不高兴的话,会卸载你的程序。
额外说一句,安卓的UI工具包不是线程安全的,这就意味着你不要在工作线程中进行UI操作——你必须在UI线程中完成所有用户接口的操作。因此,对于安卓简单线程模型而言两个简单的规则:
1 不要阻塞UI线程
2 不要在UI线程外访问安卓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线程外访问安卓UI工具包—这个例子在工作线程而不是UI线程中修改了图片视图(ImageView)。
为了解决这个问题,安卓提供了几种途径从其他线程中访问UI线程:
· Activity.runOnUiThread(Runnable)
· View.post(Runnable)
· View.postDelayed(Runnable, long)
举个例子,你可以使用 View.post(Runnable) 方法修改上面的代码:
*将mImageView.setImageBitmap(b);放到UI线程中做
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)
AsyncTask 允许你在用户接口中进行异步工作。它在工作线程中进行阻塞操作,然后将结果发布到UI线程中,而不需要你自己提供处理者或者处理线程。
为了使用它,你必须创建继承自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> {
/** 系统调用这个来在工作线程中执行工作,
同时传递AsyncTask.execute()给予的参数。
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]);
}
/** 系统调用这个来执行UI线程中的工作,同时处理doInBackground()的结果。
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线程中(分别对应doInBackground和onPostExecute)。
你应该阅读参阅 AsyncTask 类来获得完整的理解如何使用它,但是你可以阅读下文来获得一个快速的预览:
· 你可以使用泛型来指定包括参数,进度值,任务的最终值。
· 方法doInBackground() 自动在工作线程中执行。
· 方法onPreExecute(), onPostExecute(), 和 onProgressUpdate()都在UI线程中被调用。
· 方法doInBackground()返回的值将送往onPostExecute。
· 你可以在doInBackground的任何时候调用 publishProgress()来在UI线程上执行onProgressUpdate()。
· 你可以在任何时候,任何线程中取消任务。
注意:另一个可能遇到的问题是当你使用工作线程时,由于实时配置改变(例如用户改变屏幕方向)可能导致中的工作线程超乎预期的重启,这可能会摧毁你额工作线程。你可以参阅源码Shelves 来找到”当其中存在重启,如何持续你的任务,或者合适的取消你任务”的办法。
线程安全 方法
在某些情景下,你实现的方法可能被超过一个线程调用,因此它的写入必须是线程安全的。
这点是正确的首先是由于这样的方法可能被远程调用:例如处于绑定服务(bound service)中的方法。当实现自的IBinder 方法调用与IBinder 运行处于同一进程中,方法将在调用者线程中执行。然而,当调用源来自其他进程,调用将从与IBinder处于同一进程的线程池中挑选线程来执行方法。举个例子,当服务的onBind() 方法将被服务进程中的UI线程调用,方法实现onBind()返回的对象将被线程池的线程所调用(举个例子,实现RPC方法的子类) 。因为服务能够拥有超过一个客户,也不止一个线程池能够参与同一个IBinder 方法。因此IBinder方法必须以线程安全的方式实现。
相似的,一个内容提供者能够接受来自其他进程的数据请求。尽管the ContentResolver 和 ContentProvider 类隐藏了关于进程间通信如何管理的细节。ContentProvider 的方法:query(), insert(), delete(),update(), and getType()——将由内容提供者进程中的线程池调用,而不是进程中的UI线程。由于这些方法可能被任意多的线程同时调用,所以他们需要是线程安全的。
进程间通信
安卓提供了一种进程间通信机制(IPC),使用的是远程过程调用(RPC),由activity 或者其他应用程序组件来调用,但是在远端执行(其他进程内),然后结果返回给调用者。这意味着需要将方法调用和它的数据分解到操作系统能够理解的水平,然后从本地进程和地址空间传输到远端进程和地址空间,然后再在那那重组和重演它。然后再返回来重新经历一遍,这次是返回值。安卓提供了全部的代码来进行IPC事务,所以你能够专注于定义和实现RPC过程接口。
为了进行IPC,你的应用程序必须绑定服务,使用bindService().。想知道更多的信息,查阅开发手册 Services。