1、子线程更新UI的方法:
1.1、子线程调用Handler的sendMessage(message)或者post(runnable)发送事件:
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
//操作界面
myText.setText( 来自网络的信息);
super.handleMessage(msg);
}
};
public class MyThread extends Thread {
public void run() {
// 耗时操作
loadNetWork();
Message msg = new Message();
mHandler.sendMessage(msg);//向Handler发送消息,
}
}
注意:主线程中定义Handler,子线程发消息,通知Handler完成UI更新。
1.2、Activity.runOnUiThread(Runnable ):
- 如果当前线程是UI线程,那么行动是立即执行。如果当前线程不是UI线程,操作是发布到事件队列的UI线程。
- 如果Thread定义在其他地方,需要传递Activity对象(通过构造函数传递)。
new Thread() {
public void run() {
//这儿是耗时操作,完成之后更新UI;
runOnUiThread(new Runnable(){
@Override
public void run() {
//更新UI
imageView.setImageBitmap(bitmap);
}
});
}
}.start();
1.3、View.post(Runnable ):
- 从Runnable派生你的子类,重载run()方法。然后调用View.post(myRunnableObj)即可把你的Runnable对象增加到UI线程中运行。
- 注意:post函数,里面传递的是一个runnable 接口(你懂得 runnable 可不是一个线程这个你一定要和thread 区分开) 。
public void onClick( View v ) {
new Thread( new Runnable() {
public void run() {
// 耗时操作
loadNetWork();
myText.post( new Runnable() {
myText.setText( 来自网络的信息);
});
}
}).start();
}
1.4、使用异步任务AsyncTask:
//UI线程中执行
new DownloadImageTask().execute( "www.91dota.com" );
private class DownloadImageTask extends AsyncTask {
protected String doInBackground( String... url ) {
return loadDataFormNetwork( url[0] );//后台耗时操作
}
protected void onPostExecute( String result ) {
myText.setText( result ); //得到来自网络的信息刷新页面
}
}
1.5、应用场合:
- 如果是后台任务,像是下载任务等,就需要使用AsyncTask。
- 如果需要传递状态值等信息,像是蓝牙编程中的socket连接,就需要利用状态值来提示连接状态以及做相应的处理,就需要使用Handler + Thread的方式;
- 需要另开线程处理数据以免阻塞UI线程,像是IO操作或者是循环,可以使用Activity.runOnUiThread();
- 如果只是单纯的想要更新UI而不涉及到多线程的话,使用View.post()就可以了;
2、子线程为啥不能更新UI:
2.1、场景:
- 我们布局里有一个TextView、一个button,点击button,启动一个子线程,在子线程中更改textview 的显示内容,发现程序崩溃,报如下错误:
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6357)
at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:874) - 在 framework/base/core/java/android/view/ViewRootImpl.java 中有一个方法 checkThread ,源码如下:
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
mThread是主线程,在应用程序启动的时候,就已经被初始化了。
2.2、原理:
- ViewRootImpl的创建在onResume方法回调之后。
- 因为ViewRootImpl在主线程中创建,所以我们要在主线程中更新UI。同理,如果ViewRootImpl在子线程中创建的话,那么也可以在子线程中更新UI,也就是说在哪里更新UI和ViewRootImpl在哪里创建是关联的。默认ViewRootImpl在主线程中创建。
2.3、笼统说法:
- Android的UI访问是没有加锁的,这样在多个线程访问UI是不安全的。所以Android中规定只能在UI线程中访问UI。
- Android实现View更新有两组方法,分别是invalidate和postInvalidate。前者在UI线程中使用,后者在非UI线程即子线程中使用。换句话说,在子线程调用 invalidate 方法会导致线程不安全。熟悉View工作原理的人都知道,invalidate 方法会通知 view 立即重绘,刷新界面。作一个假设,现在我用 invalidate 在子线程中刷新界面,同时UI线程也在用 invalidate 刷新界面,这样会不会导致界面的刷新不能同步?这就是invalidate不能在子线程中使用的原因。