首页技术教程极客学院eoe

eoe 移动开发者论坛

 找回密码
 加入eoe

QQ登录

只需一步,快速开始

查看: 1907|回复: 23
收起左侧

[Android界面] 不懂子线程更新界面的原理

[复制链接]
发表于 2015-5-5 11:15:28 | 显示全部楼层 |阅读模式
10e币
我看到这样的一个程序:
  1. new Thread(new Runnable() {

  2.                         @Override
  3.                         public void run() {
  4.                                 Looper.prepare();
  5.                                 AlertDialog.Builder dialog = new Builder(MainActivity.this);
  6.                                 dialog.setTitle("test");
  7.                                 dialog.setMessage("hello");
  8.                                 dialog.show();
  9.                                 Looper.loop();
  10.                         }
  11.                 }).start();
复制代码


为啥它这样更新界面不崩溃?
我知道是添加了Looper.prepare();创建消息队列Looper.loop();轮询消息队列的原因,
但是不知道它的原理是怎么样?


AlertDialog.Builder dialog = new Builder(MainActivity.this);
dialog.setTitle("test");
dialog.setMessage("hello");
dialog.show();

上面这段代码明明就是在子线程里面,它到底是怎么把上面那些界面的操作转到UI线程那里执行的?

感觉和handler的机制不太一样,或许自己的理解有错误。

最佳答案

查看完整内容

你把looper.pepare删掉 你报的错不应该是calledfromwrongthreadexception 大概是 RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()"); 这是2个东西..Dialog本身有个私有成员变量Handler 声明是这样的 所以其会在Dialog构造函数执行的时候创建Handler.......它才是和Looper相关的...... ---- Dialog内部创建ViewRootImpl的机制导致其CheckThread可以被 ...
发表于 2015-5-5 11:15:29 | 显示全部楼层
mimixi666 发表于 2015-5-6 03:24
看了半小时你的博客,但依然不是很明白。。
是因为mThread == Thread.currentThread()
所以没有报错吗?

你把looper.pepare删掉
你报的错不应该是calledfromwrongthreadexception

大概是
RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");


这是2个东西..Dialog本身有个私有成员变量Handler
声明是这样的

   
  1. private final Handler mHandler = new Handler()
复制代码



所以其会在Dialog构造函数执行的时候创建Handler.......它才是和Looper相关的......
----

Dialog内部创建ViewRootImpl的机制导致其CheckThread可以被绑定在子线程
而Looper.prepare使得 Dialog的私有成员Handler也被绑定在了子线程上..

Looper和子线程更新UI根本就没有关系
发表于 2015-5-5 11:18:44 | 显示全部楼层
这个方法确实可以更新主线程UI但是!!你代码不会往下走了,貌似陷入了一个死循环中
发表于 2015-5-5 11:37:15 | 显示全部楼层
既然知道了looper是创建消息列队和轮训消息,那么处理消息肯定是在主线程的handle中执行的,这个应该只是把这个任务加载到消息队列中去排队了,执行的话是轮询到这个消息的时候去处理了。handle+message就是用的这种机制
发表于 2015-5-5 11:37:53 | 显示全部楼层
这是不安全的,但是也不一定会报错,详情看这篇文章吧
http://blog.csdn.net/aigestudio/article/details/43449123

13

主题

1349

帖子

1万

e币
发表于 2015-5-5 11:41:59 | 显示全部楼层
其实觉得handler和looper也是有关系的吧?handler发送的消息不就是进入了looper队列么?然后looper依次取出子线程中反馈的结果在handleMessage里面刷新界面,所以觉得既然直接写Looper.prepare();是不是就代表下面的到   Looper.loop();以上的刷新界面的任务是不是也是放在looper队列中的呢?如果是的话,那么不就是和handler本质一样了么?只是少了发送这个步骤了。您认为呢?
 楼主| 发表于 2015-5-5 11:48:25 | 显示全部楼层
zhuodashi 发表于 2015-5-5 11:18
这个方法确实可以更新主线程UI但是!!你代码不会往下走了,貌似陷入了一个死循环中 ...

刚测试了一下,是的,的确是陷入死循环,但是我不明白它的原理。

  1. AlertDialog.Builder dialog = new Builder(MainActivity.this);
  2. dialog.setTitle("test");
  3. dialog.setMessage("hello");
  4. dialog.show();
复制代码

这段函数不是放在子线程里面的吗?
如果说非UI子线程不能操作界面的话,那它是通过怎么样的一个流程转交给ui线程执行的?
 楼主| 发表于 2015-5-5 12:01:53 | 显示全部楼层
下雨天! 发表于 2015-5-5 11:37
既然知道了looper是创建消息列队和轮训消息,那么处理消息肯定是在主线程的handle中执行的,这个应该只是把 ...

如果是handler的机制的话,我还能明白,
要执行ui操作,要重写handler里面的handlemessage方法。
操作步骤就是,
1.handler.sendmessage信息,
2.然后messagequeque消息队列加入你这个信息,
3.looper轮询,有信息就让ui线程执行

但问题的是,我这里没有sendmessage信息,而且没有handler,所以就搞不定它的原理了。
这点我就不是很明白。
而且handler+message的机制,有点属于那种继承重写handlemessage回调的方法来实现的,
但是我上面的那个貌似不是这样,或者说我找不到的。。

发表于 2015-5-5 14:39:44 | 显示全部楼层
Android是单线程的,只有一个UI线程可有修改ui,也叫主线程,子线程想要修改ui可以通过handler在主线程中修改UI
发表于 2015-5-5 15:25:44 | 显示全部楼层
代码中的Looper就是主线程的队列,他这里直接将消息加到了主线程中的队列中了。
发表于 2015-5-5 15:47:05 | 显示全部楼层
mimixi666 发表于 2015-5-5 12:01
如果是handler的机制的话,我还能明白,
要执行ui操作,要重写handler里面的handlemessage方法。
操作步 ...

这个问题很复杂......你可以认为这是Dialog特例..不要管,但是也不要这样去用.
---------
如果你想了解的话

首先. 只有UI线程能修改UI元素这个说法本身是个近似的说法,从实现机制上来讲并没有对其进行强约束.

ViewRoot源码中抛出错误的地方在这里
  1.     void checkThread() {
  2.         if (mThread != Thread.currentThread()) {
  3.             throw new CalledFromWrongThreadException(
  4.                     "Only the original thread that created a view hierarchy can touch its views.");
  5.         }
  6.     }
复制代码


然后mThread在ViewRoot的构造函数中被赋值

  1. public ViewRootImpl(Context context, Display display) {
  2.         mContext = context;
  3.         mWindowSession = WindowManagerGlobal.getWindowSession();
  4.         mDisplay = display;
  5.         mBasePackageName = context.getBasePackageName();

  6.         mDisplayAdjustments = display.getDisplayAdjustments();

  7.         mThread = Thread.currentThread();
复制代码



可以看到..从代码层面...约束只要求 创建ViewRoot的线程和修改UI元素的线程是同一线程
并非要求其一定是UI线程/主线程

只不过ViewRoot对象是附属于WindowMananger的,而这个对象由系统提供,大部分时候都是由系统在UI线程中创建,所以我们总是说  "只有UI线程能修改UI元素"

但是实际上只要能在非UI线程中创建Window对象并将ViewRoot的mThread绑定到非UI线程上,一样可以在非UI线程中操作这个ViewRoot对象及修改里面的元素...

一个特例即是 Dialog的构造函数.
Dialog构造函数中调用了  Window w = PolicyManager.makeNewWindow(mContext); 构造了一个空的Window, 在WindowManangerImpl的addView方法中,恰好会调用

root = new ViewRootImpl(view.getContext(), display);

于是如果你在异步线程中构造Dialog会导致Dialog的rootView被绑定到异步线程上,此时该Dialog的UI元素只能被该异步线程修改,并且不能被主线程/UI线程修改..


总结的话...你当它是个系统漏洞不要管也不要用就好了.
发表于 2015-5-5 15:53:01 | 显示全部楼层
折花公子 发表于 2015-5-5 11:37
这是不安全的,但是也不一定会报错,详情看这篇文章吧
http://blog.csdn.net/aigestudio/article/details/434 ...

这篇文章对ViewRootImpl分析的也很不错,但是我认为讲的是另一个问题........
 楼主| 发表于 2015-5-5 17:01:32 | 显示全部楼层
dkmeteor 发表于 2015-5-5 15:47
这个问题很复杂......你可以认为这是Dialog特例..不要管,但是也不要这样去用.
---------
如果你想了解的 ...

谢谢你的回复,看了你的回复,又学到了不少东西,
但我这不单单是dialog的问题的喔,其它的ui操作也还是能通过的喔。
就是不明白它现在这个界面操作是属于非ui线程操作,还是在ui线程操作了?
如果是在非ui线程操作的话,那它执行looper.pepare是不是把那个你上面所说的检查线程停止了?
不懂
发表于 2015-5-5 17:11:28 | 显示全部楼层
不知道你的"其它UI操作"指什么.
----
以 TextView的setText为例,触发checkThread的调用栈是这样
-> android.widget.TextView.setText
    -> android.widget.TextView.checkForRelayout
      -> android.view.View.invalidate
        -> android.view.ViewGroup.invalidateChild
          -> android.view.ViewRoot.invalidateChildInParent
            -> android.view.ViewRoot.invalidateChild
              -> android.view.ViewRoot.checkThread

从代码上看没有发现会造成检查停止的机制.

倒是由于Dialog内部有声明Handler的动作,可能会由于未进行looper.pepare而造成Handler绑定失败..
 楼主| 发表于 2015-5-5 22:33:25 | 显示全部楼层
dkmeteor 发表于 2015-5-5 17:11
不知道你的"其它UI操作"指什么.
----
以 TextView的setText为例,触发checkThread的调用栈是这样
  1. new Thread(new Runnable() {

  2.                         @Override
  3.                         public void run() {
  4.                                 Looper.prepare();
复制代码
其实我不明白的最主要的就是,在
Looper.prepare();
....//ui操作
Looper.loop();
这里的ui操作的动作是ui主线程执行的还是子线程执行的?
1.如果是ui主线程执行的,那它是怎么传递信息到ui主线程那里执行?
2.如果是非ui子线程执行的话,为啥它没有执行checkthread这个方法?
3.还是说Looper.prepare();和Looper.loop();已经把非ui子线程变成了ui线程了?
就是这几个不懂。。


您需要登录后才可以回帖 登录 | 加入eoe

本版积分规则

推荐阅读
赞助商们

QQ|新帖|小黑屋|eoe 移动开发者论坛 ( 京ICP备11018032 京公网安11010802020210  

GMT+8, 2018-9-20 15:32 , Processed in 0.166039 second(s), 38 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表
关闭

扫一扫 关注eoe官方微信