Android framework : 应用的UI线程如何启动

慕课网 剖析framework 笔记

 

4-3 应用的UI线程是怎么启动的

考察

1,什么是UI线程

2,UI线程的启动流程,消息循环是怎么创建的

3,了解Android的UI显示原理,UI线程和UI之间的关联

 

1,什么是UI线程

1)UI线程是刷新UI所在的线程。非UI线程不能刷新UI

2)UI是单线程刷新的。如果多个线程都能刷新UI就无所谓是不是UI线程了,

为什么是单线程?如果是多线程,这个UI框架要到处上锁,很容易出问题

 

那UI线程到底 是哪个线程?UI线程是主线程吗?

回想下平时写代码有什么耗时操作,都是丢给子线程处理,再回到UI线程刷新UI,这是怎么做的?:

1,Activity.runOnUiThread(Runnable)

2,View.post(Runnable)

都带个Runnable,Runnable就会执行到UI线程

 

1,Activity.runOnUiThread(Runnable)

final void runOnUiThread(Runnable action){
    //判断当前的thread是不是UIthread
    if(Thread.currentThread() != mUiThread){
        //不是就post到消息队列里面
        mHandler.post(action);
    }else{
        //是的话就直接run
        action.run();
    }
}

//看看mUiThread和mHandle在哪里初始化的,
//mHandler是Activity里面的全局变量,在Acitivity创建时就一起创建了
//它创建的时候没有指定looper,所以创建Activity对象时在哪个线程,那handler就用的是哪个线程的looper
final Handler mHandler = new Handler();

//mUiThread
//它在调用Activity的attach函数的时候赋值的
//attach是在activity启动的时候调用的
final void attach(Context context,...){
    ...
    mUiThread = Thread.currentThread();
    ...
}

 

回顾下Activity的启动流程:

1,创建Activity对象

2,创建Context

3,调用attach 给Activity赋context,

4,onCreate

这4不都是在AP进程的主线程做的,

所以对于Activity来说,UI线程就是主线程。

 

2,View.post(Runnable)

public boolean post(Runnable action){
    final AttachInfo attachInfo = mAttachInfo;
    //1,attachInfo不为null,把action post到mHandler里面。
    if(attachInfo != null){
        return attachInfo.mHandler.post(action);
    }
    //2,attachInfo是null,把action post 到 ViewRootImpl的runQueue
    ViewRootImpl.getRunQueue().post(action);
    return true;
}

 

情况1,

何时设置AttachInfo?

它是ViewTree第一次绘制的时候,会递归的给所有的子View都附上一个attachInfo,

attachInfo从哪里来?它是在ViewRootImpl构造函数里面创建的

里面的mHandler对应的就是ViewRootImpl对象创建时所在的线程,所以关键是ViewRootImpl,

 

情况2,

AttachInfo为什么会是NUll

因为ViewRootImpl还没来得及创建,它是在Activity的onResume之后创建的

所以如果你在Activity的onCreate就用View post了一个Runnable,就会放到RunQueue里面,等ViewRootImpl创建好后,

再丢到VIewRootImpl所在线程处理,

 

所以不管哪种情况,最后都要丢到ViewRootImpl的线程处理。

 

在Andoird6.0里面runQUeue是在ViewRootImpl的thread local里面,每个线程一个runQUeue,

之后ViewRootImpl处理的时候只处理了ViewRootImpl所在的线程的runQUeue,这是一个bug,其他线程的runQueue不处理是不行的,

 

后来Android版本修复了这个问题,

每个View都有自己的runQueue,在View执行AttachToWindow回调的时候就会处理runQUeue里面的runnable,

但也是在ViewRootImpl所在线程处理的,

 

所以对View来说,它的UI线程,就是ViewRootImpl创建时所在的线程,

 

再看一个问题,一个异常,是子线程刷新UI时抛出的,

只有create view hierarchy的那个riginal thread才可以touch这个view,

也是在暗示并不是只有主线程才可以touch view

它是怎么判断我们的线程是不是最初创建view hierarchy的线程?

看看信息,View.requstLayout会调到ViewRootImpl的checkThread,所以每次requestLayout都会检查线程,不对就抛出异常

 

 

看看checkThread实现

void checkThread(){
    //mThread就是创建View hierarchy的thread,什么时候init的?在ViewRootImpl的构造函数init的,
    //所以可以确定UI线程就是ViewRootImpl所在的线程。
    //注意不是View布局加载所在线程,而是ViewRootImpl创建所在线程
    if(mThread != Thread.currentThread()){
        throw new CalledFromWrongThreadException(
            "Only the orignal thrad that catred a view can touch its views"
        )
    }
}

//所以可以确定UI线程就是ViewRootImpl所在的线程。

//注意不是View布局加载所在线程,而是ViewRootImpl创建所在线程

 

所以ViewRootImpl创建是在哪个线程?

调用流程:

ActivityThread.handleResumeActivity => WindowManagerImpl.addView => WindowManagerGlobal.addView => ViewRootImpl的创建

 

handleResumeActivity是在应用进程的主线程调的,

所以

Activity的DecorView对应的ViewRootImpl是在主线程创建的

 

关于UI线程的三个结论:

1,对Activity来说,UI线程就是主线程

通过分析activity.runOnUiThead()得知

2,对VIew来说,UI线程就是ViewRootImpl创建时所在的线程

通过分析View.post(Runnable r)

3,Activity的DecorView对应的ViewRootImpl是在主线程创建的

通过checkThread

 

结论:

一个Activity,里面有一个PhoneWIndow,PhoneWindow厘米有一个DecorView,

DecorView是整个界面最顶层的View,

DecorView有一个ViewRootImpl,负责DecorView的绘制流程,事件分发,以及与WMS通信,

VIewRootImpl是在主线程创建的,所以对于Activity的DecorView来说,UI线程就是主线程

 

 

UI线程就是主线程,这个结论之所以成立,

是因为DecorView的ViewRootImpl恰好在主线程创建而已,如果不再主线程创建会怎么样?那就不在主线程刷新UI了吗?

 

看看Activity是怎么触发ViewRootImpl创建的,以handleResumeActivity为入口分析一下:

final void handleResumeActivity(IBinder token,...){
    ...
    final Activity a = r.activity;
    //得到Activity的WindowManager
    ViewManager wm = a.getWindowManager();
    //把Actvitiy的decorView add到WindowManager
    wm.addView(decor, l);
    ....
}

//addView里做了什么:
//new一个ViewRootImpl对象
root = new ViewRootImpl(view.getContext(), display);
//通过serView把View交给ViewRootImpl管理,这步非常重要。
root.setView(view, wparams, panelParentView);

 

模仿一下,做一个实验

new Thread(){
    @Override
    public void run(){
        Looper.prepare();
        ...
        //view是通过LayoutInflater加载出来的,通过addView把View加入WindowManager,
        //设置下View的textView,发现成功了,点击View的一个按钮,onCreate函数是跑 在子线程的,
        //如果在主线程去刷新UI,反而会抛出那个异常:WrongThread
        getWindowManager().addView(view, params);
        
        Looper.loop();
    }
}.start();

 

前面说的都是UI线程, 现在说说启动

2,UI线程的启动流程,

对应用来说,UI线程默认就是主线程,

那么UI线程的启动,就是主线程的启动,这个大家都熟悉,AP启动时主线程就有了,

 

AP启动流程:

zygote fork进程 =》 启动Binder线程 =》 执行入口函数

入口函数就是ActivityThread.main()

public static void main(String[] args){
    //创建Looper,looper我们都熟悉,平时创建子线程的looper都要调用Looper.prepare()
    //这个prepareMainLooper我们没用过,因为我们不能调。
    Looper.prepareMainLooper();
    ...
    Looper.loop();
}

 

为什么不能调用prepareMainLooper?

public static void prepareMainLooper(){
    //这里这是false,表示looper是否可以退出,
    //主线程的looper是不能退出的,
    //而我们平时的普通线程的不带参数,就是可以退出。
    //所以调用主线程的quit是会抛异常的
    prepare(false);
    synchronized(Looper.class){
        if(sMainLooper != null){
            throw new IllegalStateException("main Looper has already been prepared");
        }
        //设置sMainLooper,是静态变量,只能设置一次, 
        //AP进程启动时已经设置好了,所以我们不能再去重复设置了,
        sMainLooper = myLooper();
    }
}

//其中的prepare
private static void prepare(boolean quitAllowed){
    if(sThreadLocal.get() != null){
        throw new RuntimeException("only one looper may be created per thread");
    }
    //创建线程的looper,存在ThreadLocal,它是线程私有的map,不和别人共享。
    sThreadLocal.set(new Looper(quitAllowed));
}

 

 

总结,应用的UI线程如何启动

1,UI线程是什么,解释为什么UI线程是主线程

2,既然UI线程是主线程,它是和AP进程一起启动的,可以顺便说AP进程的启动流程,

还要说UI线程的消息循环是怎么创建的

3,UI线程和UI体系之间的关系,如ViewRootImpl的原理

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值