这个错误很常见,基本上写线程操作都遇到过这个错误。根本原因是view控件的线程安全问题,通俗点讲就是所有的更新UI操作都需要在主线程(也就是UI线程中完成),而不能在新开的子线程中操作。
基本思路:既然子线程需要更新UI,但子线程自身又不能完成任务,所以只能通过建立一个通信机制,当子线程需要更新UI时,发消息通知主线程并将更新UI的任务post给主线程,让主线程来完成分内的UI更新操作。这个机制是什么呢?就是Handler。Handler 从属于谁?当然是主线程。每个线程都有自己的handler,来处理自己的消息队列,只不过平时写单线程操作,系统会缺省调用一个handler,对开发者透明。当多线程操作需要线程间通信时,handler才会被程序猿们显示调用。
下面这两个例子是更新UI时主线程和子线程通信的例子,因为控件不是线程安全的,所以子线程中涉及到的更新UI操作全都写入runnable对象、通过主线程的handler来post给UI。
第一个例子,从网上找的,总结的比较到位。谢谢原作者的辛勤总结,转载地址标注于下。
原文转自 http://blog.csdn.net/djx123456/article/details/6325983
今天写了一个更新UI的小例子,没想到出了log打印了这样一个错误:Only the original thread that created a view hierarchy can touch its views。goolgle了一下找到了原因。
原来android中相关的view和控件不是线程安全的,我们必须单独做处理。这里借此引出Handler的使用。
Handler的官方描述:
A Handler allows you to send and process Message
and Runnable objects associated with a thread's MessageQueue
. Each Handler instance is associated with a single thread and that thread's message queue. When you create a new Handler, it is bound to the thread / message queue of the thread that is creating it -- from that point on, it will deliver messages and runnables to that message queue and execute them as they come out of the message queue
.Handler的使用场合:
1、 to schedule messages and runnables to be executed as some point in the future;
安排messages和runnables在将来的某个时间点执行。
2、 to enqueue an action to be performed on a different thread than your own.
将action入队以备在一个不同的线程中执行。即可以实现线程间通信。比如当你创建子线程时,你可以再你的子线程中拿到父线程中创建的Handler对象,就可以通过该对象向父线程的消息队列发送消息了。由于Android要求在UI线程中更新界面,因此,可以通过该方法在其它线程中更新界面。
通过Handler更新UI实例:
步骤:
1、创建Handler对象(此处创建于主线程中便于更新UI)。
2、构建Runnable对象,在Runnable中更新界面。
3、在子线程的run方法中向UI线程post,runnable对象来更新UI。
详细代码如下:
- package djx.android;
- import djx.downLoad.DownFiles;
- import android.app.Activity;
- import android.os.Bundle;
- import android.os.Handler;
- import android.view.View;
- import android.view.View.OnClickListener;
- import android.widget.Button;
- import android.widget.TextView;
- public class downLoadPractice extends Activity {
- private Button button_submit=null;
- private TextView textView=null;
- private String content=null;
- private Handler handler=null;
- /** Called when the activity is first created. */
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- //创建属于主线程的handler
- handler=new Handler();
- button_submit=(Button)findViewById(R.id.button_submit);
- textView=(TextView)findViewById(R.id.textView);
- button_submit.setOnClickListener(new submitOnClieckListener());
- }
- //为按钮添加监听器
- class submitOnClieckListener implements OnClickListener{
- @Override
- public void onClick(View v) {
- //本地机器部署为服务器,从本地下载a.txt文件内容在textView上显示
- final DownFiles df=new DownFiles("http://192.168.75.1:8080/downLoadServer/a.txt");
- textView.setText("正在加载......");
- new Thread(){
- public void run(){
- content=df.downLoadFiles();
- handler.post(runnableUi);
- }
- }.start();
- }
- }
- // 构建Runnable对象,在runnable中更新界面
- Runnable runnableUi=new Runnable(){
- @Override
- public void run() {
- //更新界面
- textView.setText("the Content is:"+content);
- }
- };
- }
第二个例子,这两天在实验室写的人防工程的应用程序,包括动态新闻、巡查信息等是需要更新UI的,以下把动态新闻更新UI的代码贴出来。
/**
* 动态新闻
*
* @author GloryZSG
*/
public class NewsDetail extends Activity {
private TextView bar;
private TextView noticename;
private TextView noticeauthor;
private TextView noticetime;
private TextView noticeinfo;
private TextView newsImageText;
private ImageView newsImage;
private String imgUrl;
private Bitmap bm;
private Handler handler;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(R.layout.news_details);
handler = new Handler();
HashMap<String, Object> map = new HashMap<String, Object>();
try {
Bundle bundle = getIntent().getExtras();
Serializable data = bundle.getSerializable("taskinfo");
if (data != null) {
map = (HashMap<String, Object>) data;
} else {
return;
}
} catch (Exception e) {
e.printStackTrace();
}
// (2014.5.7第二种方法)通过服务器返回的图片url,再次向服务器请求,添加动态新闻图片
noticename = (TextView) findViewById(R.id.noticename);
noticename.setText("标题:" + map.get("title").toString());
noticeauthor = (TextView) findViewById(R.id.noticeauthor);
noticeauthor.setText("作者:" + map.get("reporterUser").toString());
noticetime = (TextView) findViewById(R.id.noticetime);
noticetime.setText("时间:" + map.get("reportTime").toString());
noticeinfo = (TextView) findViewById(R.id.noticeinfo);
noticeinfo.setText("动态新闻详情:" + map.get("detail").toString());
newsImageText = (TextView) findViewById(R.id.imgLoadingText);
// 获取图片url
imgUrl = map.get("activityPhoto").toString();
new Thread() {
public void run() {
try {
URL url;
url = new URL(imgUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
InputStream is = conn.getInputStream();
bm = BitmapFactory.decodeStream(is);
} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
handler.post(runnableUI);
}
}.start();
}
/**
* 读取图片的子线程post给主线程的runnable对象,内含各种更新UI的操作
*
* @author GloryZSG
*/
Runnable runnableUI = new Runnable() {
public void run() {
// (2014.5.1第一种方法)通过服务器返回的图片url,再次向服务器请求,添加动态新闻图片
// 读取Bitmap图片
// 加载到布局文件中
newsImageText.setVisibility(View.GONE);
newsImage = (ImageView) findViewById(R.id.imageView);
newsImage.setImageBitmap(bm);
}
};
}