Android 开发之子线程中更新UI的实例讲解
程序员文章站
2022-05-27 08:13:34
前言:
一般都会说android 的ui 只能在主线程中更新,最新在自启动开发的过程中也碰到这样的问题,这里结合source code详细分析一下。
实例引路:
public class sho...
前言:
一般都会说android 的ui 只能在主线程中更新,最新在自启动开发的过程中也碰到这样的问题,这里结合source code详细分析一下。
实例引路:
public class showthreadui extends activity implements onclicklistener { private static final string tag = "showthreadui"; private button mtestbutton; @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.show_thread_ui); mtestbutton = (button) findviewbyid(r.id.show_1); inittestbutton(); } private void inittestbutton() { mtestbutton.setonclicklistener(this); button show_2 = (button) findviewbyid(r.id.show_2); show_2.setonclicklistener(this); button show_3 = (button) findviewbyid(r.id.show_3); show_3.setonclicklistener(this); } private void testshow1() { new thread(new runnable() { @override public void run() { windowmanager windowmanager = getwindowmanager(); textview textview = new textview(getapplicationcontext()); textview.settext("test 1"); textview.settextcolor(0x54ff9f); windowmanager.addview(textview, new windowmanager.layoutparams()); } }).start(); } private void testshow2() { new thread(new runnable() { @override public void run() { mtestbutton.settext("button text changed"); } }).start(); } private void testshow3() { new testthread().start(); } class testthread extends thread{ @override public void run() { looper.prepare(); textview tx = new textview(showthreadui.this); tx.settext("show me, show me"); tx.settextcolor(0x0000ee); tx.setgravity(gravity.center); windowmanager wm = showthreadui.this.getwindowmanager(); windowmanager.layoutparams params = new windowmanager.layoutparams( 250, 150, 200, 200, windowmanager.layoutparams.first_sub_window, windowmanager.layoutparams.type_toast, pixelformat.opaque); wm.addview(tx, params); looper.loop(); } } @override public void onclick(view view) { int id = view.getid(); switch (id) { case r.id.show_1: testshow1(); break; case r.id.show_2: testshow2(); break; case r.id.show_3: testshow3(); break; default: break; } } }
1、点击button 1
这个时候会报错:
--------- beginning of crash 11-07 06:11:24.118 2296 2398 e androidruntime: fatal exception: thread-2 11-07 06:11:24.118 2296 2398 e androidruntime: process: com.shift.testapp, pid: 2296 11-07 06:11:24.118 2296 2398 e androidruntime: java.lang.runtimeexception: can't create handler inside thread that has not called looper.prepare() 11-07 06:11:24.118 2296 2398 e androidruntime: at android.os.handler.(handler.java:204) 11-07 06:11:24.118 2296 2398 e androidruntime: at android.os.handler.(handler.java:118) 11-07 06:11:24.118 2296 2398 e androidruntime: at android.view.viewrootimpl$viewroothandler.(viewrootimpl.java:3679) 11-07 06:11:24.118 2296 2398 e androidruntime: at android.view.viewrootimpl.(viewrootimpl.java:4012) 11-07 06:11:24.118 2296 2398 e androidruntime: at android.view.windowmanagerglobal.addview(windowmanagerglobal.java:346) 11-07 06:11:24.118 2296 2398 e androidruntime: at android.view.windowmanagerimpl.addview(windowmanagerimpl.java:94) 11-07 06:11:24.118 2296 2398 e androidruntime: at com.shift.testapp.showthreadui$1.run(showthreadui.java:46) 11-07 06:11:24.118 2296 2398 e androidruntime: at java.lang.thread.run(thread.java:764)
从堆栈信息来看最终会在 viewrootimpl 中的viewroothandler 触发错误,先来看下viewroothandler:
final class viewroothandler extends handler { @override public string getmessagename(message message) {
在构造的时候出错的,来看下handler 的204行:
public handler(callback callback, boolean async) { if (find_potential_leaks) { final class klass = getclass(); if ((klass.isanonymousclass() || klass.ismemberclass() || klass.islocalclass()) && (klass.getmodifiers() & modifier.static) == 0) { log.w(tag, "the following handler class should be static or leaks might occur: " + klass.getcanonicalname()); } } mlooper = looper.mylooper(); if (mlooper == null) { throw new runtimeexception( "can't create handler inside thread that has not called looper.prepare()"); } mqueue = mlooper.mqueue; mcallback = callback; masynchronous = async; }
可以看到最终原因是handler 中获取looper 为null。
重新梳理流程,button 1 点击的时候会新开一个线程,在这个线程里创建了ui,windowmanager在addview 的时候会创建viewrootimpl,其中的handler 必须要依赖线程中的looper,android异步消息处理线程之----looper+messagequeue+handler?中提到handler 是运行在创建它的线程中,而每个handler 中的looper 需要跟thread 一一对应,换句话说,就是thread中的handler 必须有个跟thread对应的looper,而这里显然是为null。
结论,通过windowmanger addview 方式创建ui 的时候,需要伴随着创建looper,handler 需要。
2、点击button 2
这个时候会报错:
--------- beginning of crash 11-07 06:12:01.827 2422 2471 e androidruntime: fatal exception: thread-2 11-07 06:12:01.827 2422 2471 e androidruntime: process: com.shift.testapp, pid: 2422 11-07 06:12:01.827 2422 2471 e androidruntime: android.view.viewrootimpl$calledfromwrongthreadexception: only the original thread that created a view hierarchy can touch its views. 11-07 06:12:01.827 2422 2471 e androidruntime: at android.view.viewrootimpl.checkthread(viewrootimpl.java:7334) 11-07 06:12:01.827 2422 2471 e androidruntime: at android.view.viewrootimpl.requestlayout(viewrootimpl.java:1165) 11-07 06:12:01.827 2422 2471 e androidruntime: at android.view.view.requestlayout(view.java:21999) 11-07 06:12:01.827 2422 2471 e androidruntime: at android.view.view.requestlayout(view.java:21999) 11-07 06:12:01.827 2422 2471 e androidruntime: at android.view.view.requestlayout(view.java:21999) 11-07 06:12:01.827 2422 2471 e androidruntime: at android.view.view.requestlayout(view.java:21999) 11-07 06:12:01.827 2422 2471 e androidruntime: at android.view.view.requestlayout(view.java:21999) 11-07 06:12:01.827 2422 2471 e androidruntime: at android.widget.textview.checkforrelayout(textview.java:8531) 11-07 06:12:01.827 2422 2471 e androidruntime: at android.widget.textview.settext(textview.java:5394) 11-07 06:12:01.827 2422 2471 e androidruntime: at android.widget.textview.settext(textview.java:5250) 11-07 06:12:01.827 2422 2471 e androidruntime: at android.widget.textview.settext(textview.java:5207) 11-07 06:12:01.827 2422 2471 e androidruntime: at com.shift.testapp.showthreadui$2.run(showthreadui.java:55) 11-07 06:12:01.827 2422 2471 e androidruntime: at java.lang.thread.run(thread.java:764)
在viewrootimpl requestlayout 的时候会调用checkthread:
void checkthread() { if (mthread != thread.currentthread()) { throw new calledfromwrongthreadexception( "only the original thread that created a view hierarchy can touch its views."); } }mthread 现在是创建viewrootimpl 时候的thread,而这里thread.currentthread 现在是当前运行的thread,上面的button 1 中再windowmanger addview 的时候会创建viewrootimpl,那在activity 中正常运行情况下是什么时候呢?下面会继续说明的。
结论,线程之前创建的view或者ui,在线程中是无法更新的,只有在创建ui的线程中更新该ui。
3、点击button 3
顺利运行,跟button 1 中流程唯一区别就是添加了looper,证明了button 1 中说到的结论。
修改实例
在原来实例的基础上,我们进行一个修改