应用程序启动时,Android系统会给应用程序创建一个叫做“main”的执行线程。这个线程很重要,因为它负责给适当的用户界面窗口调度事件,包括描画事件。它也是你的应用程序与Android UI工具中的组件交互的线程(这些组件来自android.widget和android.view包)。如,“main”线程有时也叫UI线程。
系统不会给每个组件的实例创建一个单独的线程。运行在同一个进程的所有的组件都被实例化在UI线程,并且系统会调用每个由线程调度的组件。因此响应系统回调的方法(如报告用户动作的onKeyDown()方法或一个生命周期的回调方法)始终运行在这个进程的UI线程中。
例如,当用户触摸屏幕上的一个按钮时,你的应用程序的UI线程会调度对应窗口的touch事件,它会依次设置按钮的按下状态,并且把一个有效的请求发送给这个事件队列。UI线程会让请求出队并通知对应的窗口应该重绘自己。
当你的应用程序要在响应用户的交互中执行密集的工作时,除非你正确的实现了应用程序,否则这种单线程模式会降低执行效率。尤其是把每件正在发生的事情都放在UI线程中,如执行像网络访问、数据库查询这样的长时操作将会使整个UI被阻塞。线程被阻塞时,任何事件都不能被调度,包括描画事件。从用户的角度,应用程序似乎被挂起了。甚至更糟,如果UI线程被阻塞几秒钟(大约是5秒钟),用户就会看到臭名昭著的“应用程序没有响应”(ANR)对话框。那么用户就可能决定退出你的应用程序,并且如果他们感觉不好,还会卸载这个应用程序。
另外,Android UI 工具集不是线程安全的。因此你不能由一个工作线程来控制你的UI---你必须用UI线程来对你用户界面进行所有的控制。这样对于Android单线程模式,有以下两个简单的规则:
1. 不要阻塞UI线程;
2. 不要从UI线程的外部访问Android UI工具集。
工作线程
因为以上介绍的单线程模式的缘故,你的应用程序UI的响应不阻塞UI线程是至关重要的。如果你执行的不是瞬时操作,就应该用一个单独的线程(后台或工作线程)来工作。
例如,一下click监听器的代码就是从一个单独的线程中下载图片,并在一个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线程访问Android UI工具集---这个示例在工作线程中修改了ImageView对象而不是在UI线程中。这样会导致未知的和不确定的异常行为,这对于查找问题是困难和费时的。
要修正这个问题,Android提供了以下几个方法用于从其他线程访问UI线程:
1. Activity.runOnUiThread(Runnable )
2. View.post(Runnable )
3. 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交互的任务。
使用AnsyncTask类
AsyncTask类允许你在用户界面上执行异步的工作。它在工作线程中执行一个阻塞操作,然后在UI线程上发布执行结果,不要求你自己来处理线程和/或处理器。
要使用这个类,必须继承AsyncTask类并且实现doInBackground()回调方法,它在后台的线程池中运行。要更新UI,你应该实现onPostExecute()方法,它提供了来自doInBackground()方法的结果,并且在UI线程中运行,因此你能安全的更新你的UI。然后你能够通过execute()方法从UI线程中运行任务。
例如,你能够使用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参考,但是以下可以快速浏览一下这个类的工作方式:
1. 你能够指定常用类型的参数,如进度值、任务结束的值;
2. doInBackground()方法在工作线程上自动的执行;
3. onPreExecute()、onPostExecute()、和onProgressUpdate()方法都在UI线程上调用;
4. 由doInBackground()方法返回值被发送给onPostExecute()方法
5. 你能够在任何时候调用doInBackground()方法中的publishProgress()方法在UI线程上执行onProgressUpdate()方法;
6. 你能够在任何时候取消来自任何线程的任务。
警告:当你使用工作线程的Activity由于运行时配置的改变而导致异常重启(如用户改变了屏幕的方向)时,你可能遇到另外一个问题:可能销毁你的工作线程。要了解在Activity重启期间如何保持你的后台任务和在Activity被销毁时如何正确的取消任务等信息,请看Shelves示例应用程序的源代码。(http://code.google.com/p/shelves/)
线程安全的方法
在某些情况中,你实现的方法可能可能被多个线程调用,因此,必须要写线程安全的方法。
线程安全主要是针对能够被远程调用的方法---如绑定类型Service中的方法。当IBinder实现类中的一个方法调用源于与IBinder对象运行那个进程时,这个方法就会在调用者的线程中执行。但是,当调用源于另一个进程时,就会从系统维护的与IBinder对象相同的进程那个线程池中选择执行这个方法的线程(不会在进程的UI线程中执行)。如,一个Service的onBind()方法将会被这个Service进程的主线程调用,由onBind()方法返回的对象中实现的方法(如实现RPC方法的一个子类)将会从线程池中调用。因为一个服务能够有多个客户端,多个线程能够同时调用同一个IBinder对象方法,因此IBinder方法必须实现线程安全。
类似地,一个内容提供器能够接受源自其他进程的数据申请。尽管ContentResolver类和ContentProvider类隐藏了如何管理进程进程间通信的细节,但是响应那些请求的ContentProvider方法(query()、insert()、delete()、update()和getType())是从内容提供器进程中的线程池调用的,而不是进程的UI线程。因为这些方法可能同时被多个线程调用,因此它们也必须实现线程安全。
进程间通信
Android使用远程过程调用(RPCs)给进程间通信(IPC)提供了一种机制,在这种机制中一个被Activity或其他应用程序组件调用的方法,不是在本地执行,而是在远程(其他进程)执行,它会给调用者返回一个结果。这就必需把一个方法分解为操作系统层面上能够理解的调用和数据,并且把它从本地进程和地址空间中传递给远程进程和它的地址空间,然后再在调用的地方重新组装和执行。然后把返回值反向传输回来。Android提供了所有的执行这些IPC传输的代码,因此你能够关注RPC编程接口的定义和实现。
要执行IPC,你的应用程序必须使用bindService()方法绑定一个Sevice。