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]  view plain copy
  1. package com.example.thread;  
  2.   
  3. import org.apache.http.HttpResponse;  
  4. import org.apache.http.client.HttpClient;  
  5. import org.apache.http.client.methods.HttpGet;  
  6. import org.apache.http.impl.client.DefaultHttpClient;  
  7.   
  8. import com.example.test.R;  
  9.   
  10. import android.annotation.SuppressLint;  
  11. import android.app.Activity;  
  12. import android.graphics.Bitmap;  
  13. import android.graphics.BitmapFactory;  
  14. import android.os.Bundle;  
  15. import android.os.Handler;  
  16. import android.os.Message;  
  17. import android.view.View;  
  18. import android.view.View.OnClickListener;  
  19. import android.widget.Button;  
  20. import android.widget.ImageView;  
  21. import android.widget.Toast;  
  22.   
  23. public class ThreadHandlerActivity extends Activity{  
  24.   
  25.     private static final int MSG_SUCCESS = 0;  
  26.     private static final int MSG_FAILURE = 1;  
  27.       
  28.     private ImageView mImageView;  
  29.     private Button mButton;  
  30.       
  31.     private Thread mThread;  
  32.       
  33.     @SuppressLint("HandlerLeak")  
  34.     private Handler mHandler = new Handler(){  
  35.         @Override  
  36.         public void handleMessage(Message msg) {  
  37.             switch (msg.what) {  
  38.             case MSG_SUCCESS:  
  39.                 mImageView.setImageBitmap((Bitmap)msg.obj);  
  40.                 Toast.makeText(getApplication(), "成功获取图片", Toast.LENGTH_LONG).show();    
  41.                 break;   
  42.   
  43.             case MSG_FAILURE:    
  44.                 Toast.makeText(getApplication(), "获取图片失败", Toast.LENGTH_LONG).show();    
  45.                 break;  
  46.             }  
  47.             super.handleMessage(msg);  
  48.         }  
  49.           
  50.     };  
  51.   
  52.     @Override  
  53.     protected void onCreate(Bundle savedInstanceState) {  
  54.         super.onCreate(savedInstanceState);  
  55.         setContentView(R.layout.thread_layout);  
  56.         mImageView= (ImageView) findViewById(R.id.logo);//显示图片的ImageView    
  57.         mButton = (Button) findViewById(R.id.click);  
  58.         mButton.setOnClickListener(new OnClickListener() {  
  59.             @Override  
  60.             public void onClick(View v) {  
  61.                 if (mThread == null) {  
  62.                     mThread = new Thread(runnable);  
  63.                     mThread.start();  
  64.                 }else {    
  65.                     Toast.makeText(getApplication(), "线程已经运行", Toast.LENGTH_LONG).show();    
  66.                 }   
  67.             }  
  68.         });  
  69.     }  
  70.       
  71.     Runnable runnable = new Runnable() {  
  72.         @Override  
  73.         public void run() {  
  74.             HttpClient hc = new DefaultHttpClient();  
  75.             HttpGet hg = new HttpGet("http://csdnimg.cn/www/images/csdnindex_logo.gif");  
  76.             final Bitmap bm;  
  77.             try {  
  78.                 HttpResponse hr = hc.execute(hg);  
  79.                 bm = BitmapFactory.decodeStream(hr.getEntity().getContent());  
  80.             } catch (Exception e) {  
  81.                 e.printStackTrace();  
  82.                 mHandler.obtainMessage(MSG_FAILURE).sendToTarget();  
  83.                 return;  
  84.             }  
  85.             mHandler.obtainMessage(MSG_SUCCESS, bm).sendToTarget();  
  86. //          mImageView.setImageBitmap(bm); //出错!不能在非ui线程操作ui元素  
  87. //           mImageView.post(new Runnable() {//另外一种更简洁的发送消息给ui线程的方法。    
  88. //             @Override    
  89. //             public void run() {//run()方法会在ui线程执行    
  90. //                 mImageView.setImageBitmap(bm);    
  91. //             }    
  92. //         });    
  93.         }  
  94.     };  
  95. }  

对于上面的方法,我们使用的是handler+Thread来实现更新UI,在里面也有一条注意的就是

[java]  view plain copy
  1. mImageView.setImageBitmap(bm); //出错!不能在非ui线程操作ui元素  
其实我们上面提到一个方法 Activity.runOnUiThread( Runnable ),将这个Runnable以UI线程的方式启动
[java]  view plain copy
  1. /** 
  2.      * Runs the specified action on the UI thread. If the current thread is the UI 
  3.      * thread, then the action is executed immediately. If the current thread is 
  4.      * not the UI thread, the action is posted to the event queue of the UI thread. 
  5.      * 
  6.      * @param action the action to run on the UI thread 
  7.      */  
  8.     public final void runOnUiThread(Runnable action) {  
  9.         if (Thread.currentThread() != mUiThread) {  
  10.             mHandler.post(action);  
  11.         } else {  
  12.             action.run();  
  13.         }  
  14.     }  

上面Activity的runOnUiThread(Runnable)方法实现。
利用Activity.runOnUiThread(Runnable)把更新ui的代码创建在Runnable中,然后在需要更新ui时,把这个Runnable对象传给Activity.runOnUiThread(Runnable)。 这样Runnable对像就能在ui程序中被调用。如果当前线程是UI线程,那么行动是立即执行。如果当前线程不是UI线程,操作是发布到事件队列的UI线程。
使用示例:

[java]  view plain copy
  1. current_activity.this. runOnUiThread(new Runnable()   
  2.                     @Override  
  3.                    public void run() {   
  4.                           // refresh ui 的操作代码  
  5.                    }  
  6. });  
这里需要注意的是runOnUiThread是Activity中的方法,在线程中我们需要告诉系统是哪个activity调用,所以前面显示的指明了activity .

所以我们修改一下上面的代码:

[java]  view plain copy
  1. package com.example.thread;  
  2.   
  3. import org.apache.http.HttpResponse;  
  4. import org.apache.http.client.HttpClient;  
  5. import org.apache.http.client.methods.HttpGet;  
  6. import org.apache.http.impl.client.DefaultHttpClient;  
  7.   
  8. import com.example.test.R;  
  9.   
  10. import android.annotation.SuppressLint;  
  11. import android.app.Activity;  
  12. import android.graphics.Bitmap;  
  13. import android.graphics.BitmapFactory;  
  14. import android.os.Bundle;  
  15. import android.os.Handler;  
  16. import android.os.Message;  
  17. import android.view.View;  
  18. import android.view.View.OnClickListener;  
  19. import android.widget.Button;  
  20. import android.widget.ImageView;  
  21. import android.widget.Toast;  
  22.   
  23. public class ThreadHandlerActivity extends Activity{  
  24.   
  25.     private static final int MSG_SUCCESS = 0;  
  26.     private static final int MSG_FAILURE = 1;  
  27.       
  28.     private ImageView mImageView;  
  29.     private Button mButton;  
  30.       
  31.     @SuppressLint("HandlerLeak")  
  32.     private Handler mHandler = new Handler(){  
  33.         @Override  
  34.         public void handleMessage(Message msg) {  
  35.             switch (msg.what) {  
  36.             case MSG_SUCCESS:  
  37.                 mImageView.setImageBitmap((Bitmap)msg.obj);  
  38.                 Toast.makeText(getApplication(), "成功获取图片", Toast.LENGTH_LONG).show();    
  39.                 break;   
  40.   
  41.             case MSG_FAILURE:    
  42.                 Toast.makeText(getApplication(), "获取图片失败", Toast.LENGTH_LONG).show();    
  43.                 break;  
  44.             }  
  45.             super.handleMessage(msg);  
  46.         }  
  47.           
  48.     };  
  49.   
  50.     @Override  
  51.     protected void onCreate(Bundle savedInstanceState) {  
  52.         super.onCreate(savedInstanceState);  
  53.         setContentView(R.layout.thread_layout);  
  54.         mImageView= (ImageView) findViewById(R.id.logo);//显示图片的ImageView    
  55.         mButton = (Button) findViewById(R.id.click);  
  56.         mButton.setOnClickListener(new OnClickListener() {  
  57.             @Override  
  58.             public void onClick(View v) {  
  59.                 ThreadHandlerActivity.this.runOnUiThread(runnable);  
  60.             }  
  61.         });  
  62.     }  
  63.       
  64.     Runnable runnable = new Runnable() {  
  65.         @Override  
  66.         public void run() {  
  67.             HttpClient hc = new DefaultHttpClient();  
  68.             HttpGet hg = new HttpGet("http://csdnimg.cn/www/images/csdnindex_logo.gif");  
  69.             final Bitmap bm;  
  70.             try {  
  71.                 HttpResponse hr = hc.execute(hg);  
  72.                 bm = BitmapFactory.decodeStream(hr.getEntity().getContent());  
  73.             } catch (Exception e) {  
  74.                 e.printStackTrace();  
  75.                 mHandler.obtainMessage(MSG_FAILURE).sendToTarget();  
  76.                 return;  
  77.             }  
  78.             mImageView.setImageBitmap(bm);  
  79.         }  
  80.     };  
  81.       
  82. }  
也可以在线程里面直接更新UI。

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐