发布时间:2020/12/17 阅读量:997

setContentView流程简述

  • setContentView调用了window.setContentView方法,window是一个抽象类,在Android中,window只有一个实现类,phoneWindow

  • ActivityThread通过performLaunchActivity创建了Activity,Activity中的生命周期函数如onResume等实际上就是ActivityThread调用了Activity的各个方法而已

    • 如onCreate,是ActivityThread调用了performCreate方法

    • attach方法是在onCreate方法之前调用的,在这个方法中,window完成了赋值

    • mWindow = new PhoneWindow(...)

  • DecorView是window中的顶层容器,用户的xml视图通过DecorView的addView方法被添加到了Decordview中,不同的设置对应不同的父布局xml,在用户不进行任何requestWindowFeature(如隐藏标题等)设置的情况下,最终用户的xml中会添加到screen_simple.xml中,具体是通过inflater.inflate的方式添加到了id为content的FrameLayout中去

            复制代码

    <img alt=

    • 其他的比如requestWindowFeature(FEATURE_NO_TITLE)其实就是替换了顶层xml布局,甚至其中也包含常见的VISIBLE、GONE操作

    • screen_simple.xml

完整流程如下

对于上图的解释如下:

  1. 每一个Activiy都有一个Window对象,它的具体实例是一个PhoneWindow,phoneWindow的最顶层容器是一个DecorView,DecorView是布局顶层容器

  2. 每一个Activity都有一个windowManger对象,它的具体实例是WindowMangerImpl,WindowMangerImpl的addView调用了WindowMangerGlobal对象的addView方法

  3. WindowMangerGlobal是一个单例对象,是一个全局掌控者,addView的方法将用户的xml文件填充到了DecorView的布局中

  4. 所以实际上我们在setContentView(xml layout)上实际上还包含了多层父布局层级,自顶向下包含了

    • 最顶层Window,包含DecorView对象

    • DecorView包含了statusBar、actionBar,以及一个id为content的FrameLayout(根据布局属性不同内部有差别)

    • xml layout最终被添加填充到了FrameLayout中

  5. 简单来讲,就是mWindow负责创建了窗体和布局容器DecorView,而mWindowManager负责将布局依附到布局容器DecorView中

  6. 除去中间的处理流程,其余的测量布局绘制流程基本上就是View的绘制流程measure、layout、draw

关键函数调用

  • performLaunchActivity创建了Activity,执行Activity初始化,这个过程创建了一些对象,包括PhoneWindow,PhoneWindowManager等,然后会创建出DecorView,执行onCreate函数等

  • handleResumeActivity调用了onResume,由于这个时候还没有走到绘制路程,所以这时候不能获取view的宽高,之后会调用windowManager的addView方法,它调用了WindowManagerGloabal的addView方法,new创建了viewImpl对象,调用了setView方法,调用之后,会执行scheduleTraversals,会向主线程post一个callback

  • 下一次进行轮询的时候,会执行performTraversals,这次的执行会调用decorView的measure和layout方法,第一次调用并不会马上绘制

  • 然后在下次轮询的时候,再次执行performTraversals的时候,进行真正的绘制draw

子线程到底能不能更新UI

CSDN _ Android中子线程真的不能更新UI吗?

onCreate{
    //可以更新
    thread{
      tv_setview.text = "${Thread.currentThread().name}:${SystemClock.uptimeMillis()}"
    }
    //不可以更新
    thread{
      sleep(2000)
      tv_setview.text = "${Thread.currentThread().name}:${SystemClock.uptimeMillis()}"
    }
}    
复制代码
  • 可以更新的原因是ViewRootImpl没有创建出来,不会触发checkThread方法

  • 不可以更新的原因是睡了两秒之后ViewRootImpl被创建出来,在访问UI的时候,ViewRootImpl会去检查当前是哪个线程访问的UI,ViewRootImpl的checkThread方法

    void checkThread() {
        if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
    }复制代码
    • 如果不是主线程,那就会抛出如下异常:Only the original thread that created a view hierarchy can touch its views

  • 如果想要在子线程中弹吐司,需要添加Looper代码,否则会报异常Can't toast on a thread that has not called Looper.prepare()

    //子线程吐司不报错写法
    thread{
      Looper.prepare()
      Toast.makeText(this,"sss",0).show();
      Looper.loop()
    }复制代码

子线程更新UI的方式

  1. 主线程申请成功后子线程申请

    tv_setview.setOnClickListener(View.OnClickListener {
          tv_setview.text = "Main"
          thread{
            tv_setview.text = "${Thread.currentThread().name}:${SystemClock.uptimeMillis()}"
          }
        })复制代码

    (补充解释: textview.text = "Main" 这一行代码会在主线程运行,这会在主线程正常的执行 requsetLayout 的整个流程,这样就完成了「申请」修改布局。

此时,在子线程立即调用 textview.text = "xx.." 这个代码就会因为它已经「申请」过 requestLayout 了,就不会层层往上调用 parent 的 requsetLayout() 方法,也就不会在子线程 触发 checkThread() 方法了!)

  1. 在子线程中创建ViewImpl

    thread {
            Looper.prepare()
            val button = Button(this)
            windowManager.addView(button, WindowManager.LayoutParams())
            button.setOnClickListener(OnClickListener {
              button.text = "${Thread.currentThread().name}:${SystemClock.uptimeMillis()}"
            })
            Looper.loop()
          }复制代码
  2. 利用硬件加速机制绕开 requestLayout()
    在硬件加速的支持下,如果控件只是进行了 invalidate() ,而没有触发 requestLayout() 是 不会触发 ViewRootImpl#checkThread() 的。

  3. SurfaceView

Android 中有一个控件 SurfaceView ,它可以通过 holder 获得 Canvas 对象,可以直接在子线 程中更新 UI。

view.post() 传入的 Runnable 一定会被执行吗?为什么?

  • View.post()只有在View attachedToWindow的时候才会立即执行。

  • 在attach之前调用的话,低于7.0时可以自己new一个Handler或者自定义View重写dispatchAttachedToWindow自己储存pendingTask并在attached之后执行。

Dialog的布局流程

与Activity类似,最终顶层布局也是DecorView,默认布局是alert_dialog.xml
其他情况如下,核心类是AlertController

AlertController(...){
    ...
    
    //获取不同布局在安卓系统中对应的id
    mAlertDialogLayout = a.getResourceId(com.android.internal.R.styleable.AlertDialog_layout,
            com.android.internal.R.layout.alert_dialog);
    mButtonPanelSideLayout = a.getResourceId(
            com.android.internal.R.styleable.AlertDialog_buttonPanelSideLayout, 0);

    mListLayout = a.getResourceId(
            com.android.internal.R.styleable.AlertDialog_listLayout,
            com.android.internal.R.layout.select_dialog);
    mMultiChoiceItemLayout = a.getResourceId(
            com.android.internal.R.styleable.AlertDialog_multiChoiceItemLayout,
            com.android.internal.R.layout.select_dialog_multichoice);
    mSingleChoiceItemLayout = a.getResourceId(
            com.android.internal.R.styleable.AlertDialog_singleChoiceItemLayout,
            com.android.internal.R.layout.select_dialog_singlechoice);
    mListItemLayout = a.getResourceId(
            com.android.internal.R.styleable.AlertDialog_listItemLayout,
            com.android.internal.R.layout.select_dialog_item);
    
    ...
}

十年

建站经验

多一份参考,总有益处

联系享趣,免费获得专属《策划方案》及报价

咨询相关问题或预约面谈,可以通过以下方式与我们联系

业务热线:156-1688-1988

在线咨询提交需求
以工匠精神·铸就行业典范