欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

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 中说到的结论。

修改实例

在原来实例的基础上,我们进行一个修改