配色: 字号:
Android子线程更新UI的方法总结
2016-12-02 | 阅:  转:  |  分享 
  
Android子线程更新UI的方法总结



消息机制,对于Android开发者来说,应该是非常熟悉。对于处理有着大量交互的场景,采用消息机制,是再好不过了。有些特殊的场景,比如我们都知道,在Android开发中,子线程不能更新UI,而主线程又不能进行耗时操作,一种常用的处理方法就是,在子线程中进行耗时操作,完成之后发送消息,通知主线程更新UI。或者使用异步任务,异步任务的实质也是对消息机制的封装。



关于子线程到底能不能更新UI这个问题,之前看到一篇文章很有趣,让我对这个问题也有了新的认识,那么我也来写个简单例子测试下,布局文件如下:



复制代码




xmlns:tools="http://schemas.android.com/tools"

android:layout_width="match_parent"

android:layout_height="match_parent"

android:paddingBottom="@dimen/activity_vertical_margin"

android:paddingLeft="@dimen/activity_horizontal_margin"

android:paddingRight="@dimen/activity_horizontal_margin"

android:paddingTop="@dimen/activity_vertical_margin"

tools:context="com.example.joy.messagetest.MainActivity">




android:id="@+id/tv_test"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_centerHorizontal="true"

android:text="HelloWorld!"/>





复制代码

布局中只有一个TextView,java代码如下:



复制代码

packagecom.example.joy.messagetest;



importandroid.support.v7.app.AppCompatActivity;

importandroid.os.Bundle;

importandroid.view.View;

importandroid.widget.TextView;



publicclassMainActivityextendsAppCompatActivity{



privateTextViewmTvTest;



@Override

protectedvoidonCreate(BundlesavedInstanceState){

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);



initView();



newThread(newRunnable(){

@Override

publicvoidrun(){

mTvTest.setText("子线程可以更新UI");

}

}).start();

}



privatevoidinitView(){

mTvTest=(TextView)findViewById(R.id.tv_test);

}

}

复制代码

代码也很简单,我开启子线程,在子线程中,将TextView内容设置为“子线程可以更新UI”,而在布局文件中,TextView的text为“Helloworld!”,那么现在运行程序,可能会出现的结果有三种:



程序崩了,抛异常了:说明子线程不能更新UI

程序正常运行,textview上面显示“HelloWorld!”:说明子线程不能更新UI

程序正常运行,textview上面显示“子线程可以更新UI”:说明子线程可以更新UI

运行程序,结果如下:







这说明什么?从结果看,子线程更新UI成功了。真的是这样吗?我自己也不相信,赶紧再验证一遍。这次我在布局文件中添加一个Button,修改后的布局文件如下:



复制代码




xmlns:tools="http://schemas.android.com/tools"

android:layout_width="match_parent"

android:layout_height="match_parent"

android:paddingBottom="@dimen/activity_vertical_margin"

android:paddingLeft="@dimen/activity_horizontal_margin"

android:paddingRight="@dimen/activity_horizontal_margin"

android:paddingTop="@dimen/activity_vertical_margin"

tools:context="com.example.joy.messagetest.MainActivity">




android:id="@+id/tv_test"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_centerHorizontal="true"

android:text="HelloWorld!"/>




android:id="@+id/btn_test1"

android:layout_below="@id/tv_test"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_centerHorizontal="true"

android:text="子线程更新UI测试"/>





复制代码

同时修改java代码:



复制代码

packagecom.example.joy.messagetest;



importandroid.support.v7.app.AppCompatActivity;

importandroid.os.Bundle;

importandroid.view.View;

importandroid.widget.Button;

importandroid.widget.TextView;



publicclassMainActivityextendsAppCompatActivityimplementsView.OnClickListener{



privateTextViewmTvTest;

privateButtonmBtnTest1;



@Override

protectedvoidonCreate(BundlesavedInstanceState){

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);



initView();



newThread(newRunnable(){

@Override

publicvoidrun(){

mTvTest.setText("子线程可以更新UI");

}

}).start();

}



privatevoidinitView(){

mTvTest=(TextView)findViewById(R.id.tv_test);

mBtnTest1=(Button)findViewById(R.id.btn_test1);

mBtnTest1.setOnClickListener(this);

}



@Override

publicvoidonClick(Viewv){

switch(v.getId()){

caseR.id.btn_test1:

newThread(newRunnable(){

@Override

publicvoidrun(){

mTvTest.setText("子线程真的可以更新UI吗?");

}

}).start();

break;

default:

break;

}

}

}

复制代码

我们增加了一个button,点击button,启动一个子线程,在子线程中将textview的显示内容改为“子线程真的可以更新UI吗?”。同样按照前面的分析,我们再来验证一下。重新运行程序,textview显示“子线程可以更新UI”,然后我们点击button。结果如下:







怎么回事?程序崩了。仔细看,你会发现,点击button后textview的内容其实是发生了更改的,然后程序崩溃了。查看日志,抛出如下异常:



AndroidRuntime:FATALEXCEPTION:Thread-176

Process:com.example.joy.messagetest,PID:11201

android.view.ViewRootImpl$CalledFromWrongThreadException:Onlytheoriginalthreadthatcreatedaviewhierarchycantouchitsviews.

atandroid.view.ViewRootImpl.checkTwww.wang027.comhread(ViewRootImpl.java:6357)

atandroid.view.ViewRootImpl.requestLayout(ViewRootImpl.java:874)

这次终于看到了熟悉的错误日志,只有初始创建视图的线程才能触碰这些视图,也就是说只有主线程才能更新UI。通过下面一行



atandroid.view.ViewRootImpl.checkThread(ViewRootImpl.java:6357)

我们能发现点端倪:在framework/base/core/java/android/view/ViewRootImpl.java中有一个方法checkThread,源码如下:



voidcheckThread(){

if(mThread!=Thread.currentThread()){

thrownewCalledFromWrongThreadException(

"Onlytheoriginalthreadthatcreatedaviewhierarchycantouchitsviews.");

}

}

该异常就是在这里触发的。对于这个问题,如果你还想深入下去探究清楚,可以跟进去RTFSC!这里推荐一篇文章,Android中子线程真的不能更新UI吗?



说了这么多,其实子线程是不能直接更新UI的。Android实现View更新有两组方法,分别是invalidate和postInvalidate。前者在UI线程中使用,后者在非UI线程即子线程中使用。换句话说,在子线程调用invalidate方法会导致线程不安全。熟悉View工作原理的人都知道,invalidate方法会通知view立即重绘,刷新界面。作一个假设,现在我用invalidate在子线程中刷新界面,同时UI线程也在用invalidate刷新界面,这样会不会导致界面的刷新不能同步?这就是invalidate不能在子线程中使用的原因。



但是我们可以在子线程执行某段代码,需要更新UI的时候去通知主线程,让主线程来更新。如何做呢?常见的方法,除了前面提到的在UI线程创建Handler,在子线程发送消息到UI线程,通知UI线程更新UI,还有handler.post(Runnabler)、view.post(Runnabler)、activity.runOnUIThread(Runnabler)等方法。跟进去看源码,发现其实它们的实现原理都还是一样,最终都是通过Handler发送消息来实现的。下面分别用这几种方法实现一下在子线程更新UI。



修改后的布局文件代码如下:



复制代码




xmlns:tools="http://schemas.android.com/tools"

android:layout_width="match_parent"

android:layout_height="match_parent"

android:paddingBottom="@dimen/activity_vertical_margin"

android:paddingLeft="@dimen/activity_horizontal_margin"

android:paddingRight="@dimen/activity_horizontal_margin"

android:paddingTop="@dimen/activity_vertical_margin"

tools:context="com.example.joy.messagetest.MainActivity">




android:id="@+id/tv_test"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_centerHorizontal="true"

android:text="HelloWorld!"/>




android:id="@+id/btn_test1"

android:layout_below="@id/tv_test"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_centerHorizontal="true"

android:text="子线程更新UI测试"/>




android:id="@+id/btn_test2"

android:layout_below="@id/btn_test1"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_centerHorizontal="true"

android:text="Handler发送消息"/>




android:id="@+id/btn_test3"

android:layout_below="@id/btn_test2"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_centerHorizontal="true"

android:text="Handler.Post"/>




android:id="@+id/btn_test4"

android:layout_below="@id/btn_test3"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_centerHorizontal="true"

android:text="View.Post"/>




android:id="@+id/btn_test5"

android:layout_below="@id/btn_test4"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_centerHorizontal="true"

android:text="Activity.RunOnUIThread"/>





复制代码

java代码如下:



复制代码

packagecom.example.joy.messagetest;



importandroid.os.AsyncTask;

importandroid.os.Handler;

importandroid.os.Looper;

importandroid.os.Message;

importandroid.support.v7.app.AppCompatActivity;

importandroid.os.Bundle;

importandroid.view.View;

importandroid.widget.Button;

importandroid.widget.TextView;

importandroid.widget.Toast;



publicclassMainActivityextendsAppCompatActivityimplementsView.OnClickListener{



privateTextViewmTvTest;

privateButtonmBtnTest1;

privateButtonmBtnTest2;

privateButtonmBtnTest3;

privateButtonmBtnTest4;

privateButtonmBtnTest5;





privateHandlermHandler=newHandler(){

@Override

publicvoidhandleMessage(Messagemsg){

Super.handlewww.baiyuewang.netMessage(msg);

if(msg.what==100){

mTvTest.setText("由Handler发送消息");

}

}

};





@Override

protectedvoidonCreate(BundlesavedInstanceState){

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);



initView();



newThread(newRunnable(){

@Override

publicvoidrun(){

mTvTest.setText("子线程可以更新UI");

}

}).start();

}



privatevoidinitView(){

mTvTest=(TextView)findViewById(R.id.tv_test);

mBtnTest1=(Button)findViewById(R.id.btn_test1);

mBtnTest2=(Button)findViewById(R.id.btn_test2);

mBtnTest3=(Button)findViewById(R.id.btn_test3);

mBtnTest4=(Button)findViewById(R.id.btn_test4);

mBtnTest5=(Button)findViewById(R.id.btn_test5);

mBtnTest1.setOnClickListener(this);

mBtnTest2.setOnClickListener(this);

mBtnTest2.setOnClickListener(this);

mBtnTest3.setOnClickListener(this);

mBtnTest4.setOnClickListener(this);

mBtnTest5.setOnClickListener(this);

}



@Override

publicvoidonClick(Viewv){

switch(v.getId()){

caseR.id.btn_test1:

newThread(newRunnable(){

@Override

publicvoidrun(){

mTvTest.setText("子线程真的可以更新UI吗?");

}

}).start();

break;

caseR.id.btn_test2://通过发送消息

newThread(newRunnable(){

@Override

publicvoidrun(){

mHandler.sendEmptyMessage(100);

}

}).start();

break;

caseR.id.btn_test3://通过Handler.post方法

newThread(newRunnable(){

@Override

publicvoidrun(){

mHandler.post(newRunnable(){

@Override

publicvoidrun(){

mTvTest.setText("handler.post");

}

});

}

}).start();

break;

caseR.id.btn_test4://通过view.post方法

newThread(newRunnable(){

@Override

publicvoidrun(){

mTvTest.post(newRunnable(){

@Override

publicvoidrun(){

mTvTest.setText("view.post");

}

});

}

}).start();

break;

caseR.id.btn_test5://通过activity的runOnUiThread方法

newThread(newRunnable(){

@Override

publicvoidrun(){

runOnUiThread(newRunnable(){

@Override

publicvoidrun(){

mTvTest.setText("runOnUIThread");

}

});

}

}).start();

break;

default:

break;

}

}

}

献花(0)
+1
(本文系thedust79首藏)