Android 異步更新UI—-handler+thread

android應用是單線程模式的。

 

 

單線程模式需要記住兩條:

 

一、防止UI線程阻塞

 

二、確保隻在UI線程中訪問Android UI工具包

 

在開發Android應用時必須遵守單線程模型的原則:Android UI操作並不是線程安全的並且這些操作必須在UI線程中執行。

每個Android應用程序都運行在一個dalvik虛擬機進程中,進程開始的時候會啟動一個主線程(MainThread),主線程負責處理和ui相關的事件,因此主線程通常又叫UI線程。而由於Android采用UI單線程模型,所以隻能在主線程中對UI元素進行操作。

 

開一個線程或者在後臺線程中來執行耗時的操作,如下面的例子:

 

public void onClick( View v ) {  

 

new Thread( new Runnable() {   

 

    public void run() {  

 

Bitmap b = loadImageFromNetwork();   //從網絡上下載圖片

 

mImageView.setImageBitmap( b );  //把圖片設置給ImageView

 

}

 

    }).start()

 

 }

 

上面的代碼會報錯,你可能會說邏輯很正確啊,但是它違背瞭Android單線程模型:Android UI操作並不是線程安全的並且這些操作必須在UI線程中執行.

 

例如: 如果在非UI線程直接對UI進行瞭操作,則會報錯:

 

CalledFromWrongThreadException:only the original thread that created a view hierarchy can touch its views

 

Android為我息循環們提供瞭消的機制,我們可以利用這個機制來實現線程間的通信。那麼,我們就可以在非UI線程發送消息到UI線程,最終讓Ui線程來進行ui的操作。

 

Andriod提供瞭幾種在其他線程中訪問UI線程的方法:

 

 

Activity.runOnUiThread( Runnable )

 

View.post( Runnable )   

 

View.postDelayed( Runnable, long )   

 

Hanlder

 

對於運算量較大的操作和IO操作,我們需要新開線程來處理這些繁重的工作,以免阻塞ui線程。

 

例子:下面我們以獲取CSDN logo的例子,演示如何使用Thread+Handler的方式實現在非UI線程發送消息通知UI線程更新界面

 

ThradHandlerActivity.java:

[java]  

<span style="font-size:18px">package com.example.thread;  

  

import org.apache.http.HttpResponse;  

import org.apache.http.client.HttpClient;  

import org.apache.http.client.methods.HttpGet;  

import org.apache.http.impl.client.DefaultHttpClient;  

  

import com.example.test.R;  

  

import android.annotation.SuppressLint;  

import android.app.Activity;  

import android.graphics.Bitmap;  

import android.graphics.BitmapFactory;  

import android.os.Bundle;  

import android.os.Handler;  

import android.os.Message;  

import android.view.View;  

import android.view.View.OnClickListener;  

import android.widget.Button;  

import android.widget.ImageView;  

import android.widget.Toast;  

  

public class ThreadHandlerActivity extends Activity{  

  

    private static final int MSG_SUCCESS = 0;  

    private static final int MSG_FAILURE = 1;  

      

    private ImageView mImageView;  

    private Button mButton;  

      

    private Thread mThread;  

      

    @SuppressLint("HandlerLeak")  

    private Handler mHandler = new Handler(){  

        @Override  

        public void handleMessage(Message msg) {  

            switch (msg.what) {  

            case MSG_SUCCESS:  

                mImageView.setImageBitmap((Bitmap)msg.obj);  

                Toast.makeText(getApplication(), "成功獲取圖片", Toast.LENGTH_LONG).show();    

                break;   

  

            case MSG_FAILURE:    

                Toast.makeText(getApplication(), "獲取圖片失敗", Toast.LENGTH_LONG).show();    

                break;  

            }  

            super.handleMessage(msg);  

        }  

          

    };  

  

    @Override  

    protected void onCreate(Bundle savedInstanceState) {  

        super.onCreate(savedInstanceState);  

        setContentView(R.layout.thread_layout);  

        mImageView= (ImageView) findViewById(R.id.logo);//顯示圖片的ImageView    

        mButton = (Button) findViewById(R.id.click);  

        mButton.setOnClickListener(new OnClickListener() {  

            @Override  

            public void onClick(View v) {  

                if (mThread == null) {  

                    mThread = new Thread(runnable);  

                    mThread.start();  

                }else {    

                    Toast.makeText(getApplication(), "線程已經運行", Toast.LENGTH_LONG).show();    

                }   

            }  

        });  

    }  

      

    Runnable runnable = new Runnable() {  

        @Override  

        public void run() {  

            HttpClient hc = new DefaultHttpClient();  

            HttpGet hg = new HttpGet("https://csdnimg.cn/www/images/csdnindex_logo.gif");  

            final Bitmap bm;  

            try {  

                HttpResponse hr = hc.execute(hg);  

                bm = BitmapFactory.decodeStream(hr.getEntity().getContent());  

            } catch (Exception e) {  

                e.printStackTrace();  

                mHandler.obtainMessage(MSG_FAILURE).sendToTarget();  

                return;  

            }  

            mHandler.obtainMessage(MSG_SUCCESS, bm).sendToTarget();  

//          mImageView.setImageBitmap(bm); //出錯!不能在非ui線程操作ui元素  

//           mImageView.post(new Runnable() {//另外一種更簡潔的發送消息給ui線程的方法。    

//             @Override    

//             public void run() {//run()方法會在ui線程執行    

//                 mImageView.setImageBitmap(bm);    

//             }    

//         });    

        }  

    };  

}  

</span>  

對於上面的方法,我們使用的是handler+Thread來實現更新UI,在裡面也有一條註意的就是

 

[java] 

<span style="font-size:18px">mImageView.setImageBitmap(bm); //出錯!不能在非ui線程操作ui元素</span>  

其實我們上面提到一個方法Activity.runOnUiThread( Runnable ),將這個Runnable以UI線程的方式啟動

[java]  

<span style="font-size:18px">/** 

     * Runs the specified action on the UI thread. If the current thread is the UI 

     * thread, then the action is executed immediately. If the current thread is 

     * not the UI thread, the action is posted to the event queue of the UI thread. 

     * 

     * @param action the action to run on the UI thread 

     */  

    public final void runOnUiThread(Runnable action) {  

        if (Thread.currentThread() != mUiThread) {  

            mHandler.post(action);  

        } else {  

            action.run();  

        }  

    }</span>  

 

上面Activity的runOnUiThread(Runnable)方法實現。

利用Activity.runOnUiThread(Runnable)把更新ui的代碼創建在Runnable中,然後在需要更新ui時,把這個Runnable對象傳給Activity.runOnUiThread(Runnable)。 這樣Runnable對像就能在ui程序中被調用。如果當前線程是UI線程,那麼行動是立即執行。如果當前線程不是UI線程,操作是發佈到事件隊列的UI線程。

使用示例:

 

[java]  

<span style="font-size:18px">current_activity.this. runOnUiThread(new Runnable()   

                    @Override  

                   public void run() {   

                          // refresh ui 的操作代碼  

                   }  

});</span>  

這裡需要註意的是runOnUiThread是Activity中的方法,在線程中我們需要告訴系統是哪個activity調用,所以前面顯示的指明瞭activity.

所以我們修改一下上面的代碼:

 

 

[java]  

<span style="font-size:18px">package com.example.thread;  

  

import org.apache.http.HttpResponse;  

import org.apache.http.client.HttpClient;  

import org.apache.http.client.methods.HttpGet;  

import org.apache.http.impl.client.DefaultHttpClient;  

  

import com.example.test.R;  

  

import android.annotation.SuppressLint;  

import android.app.Activity;  

import android.graphics.Bitmap;  

import android.graphics.BitmapFactory;  

import android.os.Bundle;  

import android.os.Handler;  

import android.os.Message;  

import android.view.View;  

import android.view.View.OnClickListener;  

import android.widget.Button;  

import android.widget.ImageView;  

import android.widget.Toast;  

  

public class ThreadHandlerActivity extends Activity{  

  

    private static final int MSG_SUCCESS = 0;  

    private static final int MSG_FAILURE = 1;  

      

    private ImageView mImageView;  

    private Button mButton;  

      

    @SuppressLint("HandlerLeak")  

    private Handler mHandler = new Handler(){  

        @Override  

        public void handleMessage(Message msg) {  

            switch (msg.what) {  

            case MSG_SUCCESS:  

                mImageView.setImageBitmap((Bitmap)msg.obj);  

                Toast.makeText(getApplication(), "成功獲取圖片", Toast.LENGTH_LONG).show();    

                break;   

  

            case MSG_FAILURE:    

                Toast.makeText(getApplication(), "獲取圖片失敗", Toast.LENGTH_LONG).show();    

                break;  

            }  

            super.handleMessage(msg);  

        }  

          

    };  

  

    @Override  

    protected void onCreate(Bundle savedInstanceState) {  

        super.onCreate(savedInstanceState);  

        setContentView(R.layout.thread_layout);  

        mImageView= (ImageView) findViewById(R.id.logo);//顯示圖片的ImageView    

        mButton = (Button) findViewById(R.id.click);  

        mButton.setOnClickListener(new OnClickListener() {  

            @Override  

            public void onClick(View v) {  

                ThreadHandlerActivity.this.runOnUiThread(runnable);  

            }  

        });  

    }  

      

    Runnable runnable = new Runnable() {  

        @Override  

        public void run() {  

            HttpClient hc = new DefaultHttpClient();  

            HttpGet hg = new HttpGet("https://csdnimg.cn/www/images/csdnindex_logo.gif");  

            final Bitmap bm;  

            try {  

                HttpResponse hr = hc.execute(hg);  

                bm = BitmapFactory.decodeStream(hr.getEntity().getContent());  

            } catch (Exception e) {  

                e.printStackTrace();  

                mHandler.obtainMessage(MSG_FAILURE).sendToTarget();  

                return;  

            }  

            mImageView.setImageBitmap(bm);  

        }  

    };  

      

}  

</span>  

也可以在線程裡面直接更新UI。

有人會說我傳遞一個當前的Activity到一個線程中,然後實現UI更新,那我就是調用的當前的Activity的內容,其實這個也是不對的也會提示

 

[java] 

<span style="font-size:18px">android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.</span>  

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *