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: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);
// }
// });
}
};
}
对于上面的方法,我们使用的是handler+Thread来实现更新UI,在里面也有一条注意的就是
mImageView.setImageBitmap(bm); //出错!不能在非ui线程操作ui元素
其实我们上面提到一个方法Activity.runOnUiThread( Runnable ),将这个Runnable以UI线程的方式启动/**
* 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();
}
}
current_activity.this. runOnUiThread(new Runnable()
@Override
public void run() {
// refresh ui 的操作代码
}
});
这里需要注意的是runOnUiThread是Activity中的方法,在线程中我们需要告诉系统是哪个activity调用,所以前面显示的指明了activity.
所以我们修改一下上面的代码:
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);
}
};
}
也可以在线程里面直接更新UI。有人会说我传递一个当前的Activity到一个线程中,然后实现UI更新,那我就是调用的当前的Activity的内容,其实这个也是不对的也会提示
android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.