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("http://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("http://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>