Android 进程和线程(二)

线程

应用程序启动时,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。

你可能感兴趣的:(android)