Android更新Ui进阶精解(一)

《代码里的世界》

用文字札记描绘自己 android学习之路

转载请保留出处 by Qiao http://blog.csdn.net/qiaoidea/article/details/45115047

[Android更新Ui进阶精解(一)][4] android ui线程检查机制
[Android更新Ui进阶精解(二)][5] android 线程更新UI机制


1.回顾

[前面一篇][1]简单讲了如何快速使用handler更新ui。稍微补充一些:

  1. 更新ui时可以直接使用这种方法,你不须非要再new一个子线程才使用,比如:
viewPostBtn.setOnClickListener(new OnClickListener() {
            
            @Override
            public void onClick(View v) {
                viewPostBtn.post(new Runnable() {
                    
                    @Override
                    public void run() {
                        titleView.setText("viewPost——Result");
                    }
                });
            }
        });
  1. 这几种方法可以同样延伸到很多类似的反不同意义的用法,比如
    sendMessage()可以延伸的方法
  2. sendMessageAtTime(Message msg, long uptimeMillis)在指定时间uptimeMillis时发送消息msg;
  3. sendMessageDelayed(Message msg, long delayMillis)延迟delayMillis时间后发送消息msg
  4. sendEmptyMessage(int what) 发送一个指定类型what的空消息;
  5. sendEmptyMessageAtTime(int what, long uptimeMillis)在指定时间uptimeMillis时发送一条指定类型what的空消息;
  6. sendEmptyMessageDelayed(int what, long delayMillis)延迟delayMillis时间后发送一条指定类型what的空消息;
  7. sendMessageAtFrontOfQueue(Message msg)在消息队列头(优先)发送这条消息msg;

同样,post()可以延伸的方法

  1. postAtTime(Runnable r, long uptimeMillis)
  2. postAtTime(Runnable r, Object token, long uptimeMillis)
  3. postDelayed(Runnable r, long delayMillis)
  4. postAtFrontOfQueue(Runnable r)
    请自行查阅相应方法,这里不予一一列出。

2.原理--源码分析

首先说[上篇][1]的第一个问题,android在生成页面的同时生成一个ViewRootImpl的对象,这个对象负责检查checkThread线程是否是在主ui线程,当我们尝试使用非ui线程更新视图时,checkThread则抛出异常。

1. 先看看负责检查线程的ViewRootImpl这段逻辑

@SuppressWarnings({"EmptyCatchBlock", "PointlessBooleanExpression"})  
public final class ViewRootImpl implements ViewParent,  
        View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {  
    // other code..
  
    void checkThread() {  
        if (mThread != Thread.currentThread()) {  
            throw new CalledFromWrongThreadException(  
                    "Only the original thread that created a view hierarchy can touch its views.");  
        }  
    }  
  
    // other code..
}  

好吧,为什么要这样?  引用一段比较合理解释:
 “那么为什么Android要求只能在UI主线程中更改View呢?这就要说到Android的单线程模型了,因为如果支持多线程修改View的话,由此产生的线程同步和线程安全问题将是非常繁琐的,所以Android直接就定死了,View的操作必须在UI线程,从而简化了系统设计。”

2. 再看看视图创建时候是何时添加了这个检查对象的

我们从activity创建说起,首先获取一个窗口管理器WindowManager,然后设置并初始化其container。接着通过activity得到根视图DecorView(FramLayout),最后将DecorView添加到 activity的ViewManager 中去,而这个ViewManager 在addView时候就会生成一个ViewRootImpl对象。说这么多感觉表述不清楚,更容易犯糊涂了。 看代码

/**
* 以下方法是在调用activity Resume时候执行
* @ActivityClientRecord r 记录activity相关状态及参数
*/
 if (r.window == null && !a.mFinished && willBeVisible) {  
                 //获取根视图DecorView
                r.window = r.activity.getWindow();  
                View decor = r.window.getDecorView();  
                decor.setVisibility(View.INVISIBLE);
                //获取并添加至ViewManager   
                ViewManager wm = a.getWindowManager();  
                WindowManager.LayoutParams l = r.window.getAttributes();  
                a.mDecor = decor;  
                l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;  
                l.softInputMode |= forwardBit;  
                if (a.mVisibleFromClient) {  
                    a.mWindowAdded = true;  
                    wm.addView(decor, l);  
                }  
  
            }

然后更新和显示activity ,调用了r.activity.makeVisible()方法

 if (!r.activity.mFinished && willBeVisible  
                    && r.activity.mDecor != null && !r.hideForNow) {  
                //判断配置等是否需要更新view最后调用wm.updateViewLayout(decor, l); 方法,略过。。。 
                //最后显示activity
                r.activity.mVisibleFromServer = true;  
                mNumVisibleActivities++;  
                if (r.activity.mVisibleFromClient) {  
                    r.activity.makeVisible();  //显示
                }  
            }  

再看activity的makeVisible()方法

    void makeVisible() {  
        if (!mWindowAdded) {  
            ViewManager wm = getWindowManager();  
            wm.addView(mDecor, getWindow().getAttributes());  //注意这里。。
            mWindowAdded = true;  
        }  
        mDecor.setVisibility(View.VISIBLE);  
    } 

这个 ViewManager 的addview方法正是关键,它将添加我们提到的ViewRootImpl,其具体实现可以看WindowManagerGlobal:

//主要展示添加ViewRootImpl的过程,其他代码略
public final class WindowManagerGlobal {  
    public void addView(View view, ViewGroup.LayoutParams params,  
            Display display, Window parentWindow) { 
        ViewRootImpl root;  
  
        synchronized (mLock) { 
  
            root = new ViewRootImpl(view.getContext(), display);  
  
            mRoots.add(root);  
        }  
    }  
}  

至此,我们大略知道了ui线程是在何时对更新过程和加以控制检查,并了解了检查的内部原理。
  看到这里很多小伙伴们肯定会不满了,你他瞄不是说讲解handler更新Ui的原理吗,净扯这些有屁用呢!额,由于篇幅限制,这段放在下一篇[Android更新Ui进阶精解(二)][5] 讲解。咱线继续上一话题:
  那么,我们真的就不能在子线程里更新Ui了吗?显然不是,基于前面讲解的部分,既然是在onResume时候生成检查ViewRootImpl对象,所以我们其实可以在oncreate里更新Ui,比如

         //onCreate调用这段
         new Thread(new Runnable() {  
            @Override  
            public void run() {  
                titleView.setText("OtherThread");  
            }  
        }).start();  

当然,你是不能这样用的

   new Thread(new Runnable() {  
            @Override  
            public void run() {  
                try {  
                    Thread.sleep(200);  //睡了就再起不来了
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                }  
                titleView.setText("OtherThread");  
            }  
        }).start();  

但是为什么我我们又能在OnResume里这么用?

@Override  
    protected void onResume() {  
        super.onResume();  
        new Thread(new Runnable() {  
            @Override  
            public void run() {  
                titleView.setText("OtherThread");  
            }  
        }).start();  
    }  

爱哥说是因为消息队列Message Queue在接收和处理过程并非立即的,需要一个过程。(这一部分我大爱哥 [爱哥 --非UI线程更新UI][3] 其实有精讲,大家不妨看一下。)其实我觉得不妨可以大胆猜想,只要view是在渲染到视图之前,我们都是可以通过其他线程来更改的。大家有空可以研究下。

--
[1]: http://www.jianshu.com/p/4c60506c3ae1
[2]: http://www.cnblogs.com/xirihanlin/archive/2011/04/11/2012746.html
[3]: http://blog.csdn.net/aigestudio/article/details/43449123
[4]: http://www.jianshu.com/p/6de0a42a44d6
[5]: http://##

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容