Android中的UI线程详解

(一)什么是UI线程?

  Android在启动应用程序的时候,会为应用创建一个Main线程,这个线程负责将事件分派给相应用户接口的widget,其中包括drawing事件。除了事件分派之外,Main线程还负责应用与Android UI组件(例如, android.widget 和android.view 包)进行交互,因此Main 线程有时候也被称为UI线程。

(二)为什么会出现ANR(Application Not Response)?

  Android不会为每个组件实例创建单独的线程。运行于同一进程的所有组件均在 UI 线程(Main)中实例化,并且对每个组件的系统调用均由该线程进行分派。这样一来,响应系统回调的方法(例如,报告用户操作的 onKeyDown() 或生命周期回调方法)始终在进程的 UI 线程中运行。举个例子来说,当用户触摸屏幕上的按钮时,应用的 UI 线程会将触摸事件分派给Widget,而Widget反过来又设置其按下状态,并将无效请求发布到事件队列中。UI 线程从队列中取消该请求并通知Widget进行重新绘制。
  那么,如果应用在响应用户的操作的时候,在UI线程中执行了大量的耗时操作,比方说网络访问或数据库查询。这样做的后果势必会阻塞整个 UI。一旦UI线程被阻塞,将无法分派任何事件,包括绘图事件。
  如果 UI 线程被阻塞超过几秒钟时间(目前大约是 5 秒钟),就会出现我们常说的ANR。

(三)解决在worker线程中访问UI资源的问题?

  你可能会想,既然ANR是由于在UI线程中执行大量耗时的操作引起的,那么我们在主线程中新建一个worker线程问题不就解决了么?事实上,这种方式的确能够解决一些问题,但是对于UI来说就不是那么灵光了。不要忘了,Android UI toolkit并不是线程安全的,这也就意味着你不能在worker线程中来管理UI,也就是我们平常所说的不能在线程中更新UI。
  至此,我们其实可以总结出两条适用于Android单线程模型的规则:

  • 不要阻塞UI线程;
  • 不要在UI线程之外访问Androd UI toolkit。

  针对不能再线程中更新UI的问题,Android提供了三种在线程中更新UI的方式来解决这一问题:

  • Activity.runOnUiThread(Runnable)
  • View.post(Runnable)
  • View.postDelayed(Runnable, long)

  我们以View.post为例

public void onClick(View v) {
    new Thread(new Runnable() {
        public void run() {
            final Bitmap bitmap = loadImageFromNetwork("http://example.com/image.png");
            //在此处来更新UI
            mImageView.post(new Runnable() {
                public void run() {
                    mImageView.setImageBitmap(bitmap);
                }
            });
        }
    }).start();
}

  但是随着代码复杂度的提升,再用此种方法来解决问题势必要启动更多的线程,从而使代码难以管理。要通过工作线程处理更复杂的交互,可以考虑在工作线程中使用 Handler 处理来自 UI 线程的消息。当然,最好的解决方案或许是扩展 AsyncTask 类,此类简化了与 UI 进行交互所需执行的工作线程任务。下面我们分别来看一下这两种方式的的具体使用形式:

使用Handler来解决线程中更新UI的问题


private BitMap bitmap;
public static final int UPDATE_IMG=1;
private Handler handler=new Handler(){
    public void handleMessage(Message msg){
        switch(msg.what){
            case UPDATE_IMG:
                //在主线程中更新UI
                mImageView.setImageBitmap(bitmap);
                break;
        }
    }
};
public void onClick(View v) {
    new Thread(new Runnable() {
        public void run() {
            bitmap = loadImageFromNetwork("http://example.com/image.png");
            Message message = new Message();
            message.what=UPDATE_IMG;
            handler.sendMessage(message);//将消息发送至主线程
        }
    }).start();
}

使用AsyncTask来解决在线程中更新UI的问题

  AsyncTask的出现就是为了简化在在线程中更新UI,有了这个类就不必显式的调用handler和Treads了。AsyncTask的使用非常简单主要分为四个主要的步骤:

  1. onPreExecute(), 这个方法会在 UI线程 中调用,因此这个方法也通常用于在Task开启之前准备Task需要的启动信息。例如展示一个progress bar。
  2. doInBackground(Params…), 其中Params是异步任务的参数。在onPreExecute()执行完成之后,这个方法会在后台线程中迅速被调用,主要用于进行后台一些比较耗时的计算。doInBackground(Params…)方法返回的计算结果会在最后一步中调用。此外,在这一步中还可以调用publishProgress(Progress…)来展示其中的过程单元,Progress…参数的值会转发到UI线程中的onProgressUpdate(Progress…) 方法。
  3. onProgressUpdate(Progress…), 在 publishProgress(Progress…)之后在UI线程调用. 即使此时后台的运算仍然在执行,这个方法可以用多种UI形式用户界面上展示后台进度。比如,用一个进度条和文本的形式来显示后台的下载进度。
  4. onPostExecute(Result), 其中Result是后台线程的运行结果。这个方法会在后台线程的计算工作完成之后再UI线程中调用。因此此方法也可进行相关的UI操作。比如,提醒后台的执行结果,关闭对话框。
 private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
     protected Long doInBackground(URL... urls) {
         int count = urls.length;
         long totalSize = 0;
         for (int i = 0; i < count; i++) {
             totalSize += Downloader.downloadFile(urls[i]);
             publishProgress((int) ((i / (float) count) * 100));
             // Escape early if cancel() is called
             if (isCancelled()) break;
         }
         return totalSize;
     }

     protected void onProgressUpdate(Integer... progress) {
         setProgressPercent(progress[0]);
     }

     protected void onPostExecute(Long result) {
         showDialog("Downloaded " + result + " bytes");
     }
 }

你可能感兴趣的:(安卓学习之路)