美文网首页
View的面试锦囊

View的面试锦囊

作者: dashingqi | 来源:发表于2020-10-12 17:18 被阅读0次
Android_Banner.jpg

View的draw流程

  • 绘制背景
  • 绘制自己
  • 绘制子View
  • 绘制装饰

View.getLocationInWindow()和View.getLocationOnScreen()区别

getLocationInWindow

  • 一个控件相对父控件上的坐标位置。

getLocationOnScreen

  • 一个控件在相对整个屏幕的坐标位置。

首次View的绘制是发生在什么时候

是在ActivityThread的handleResumeActivity()方法中

流程分析
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,                                   String reason) { 
if (r.window == null && !a.mFinished && willBeVisible) {
                    // 获取到Window对象
            r.window = r.activity.getWindow();
                    //获取到DecorView
            View decor = r.window.getDecorView();
            decor.setVisibility(View.INVISIBLE);
                    //获取到WindowMananger对象
            ViewManager wm = a.getWindowManager();
             ........ 
            if (a.mVisibleFromClient) {
                if (!a.mWindowAdded) {
                    a.mWindowAdded = true;
                    /**
                     * 重点中的重点
                     * 调用WindowManager的addView(还记得 WindowManager是一个接口类吧,唯一实现子类是WindowManagerImpl)
                     * 其实调用了WindowManger的addView()方法就是将DecoeView添加到了WindowManagerService中了
                     * (PhoneWindow只是处理一些应用窗口通用的逻辑(设置标题栏、导航栏))
                     * 去看下WindowManagerImpl的addView()
                     */
                    wm.addView(decor, l);
                } else {
                    a.onWindowAttributesChanged(l);
                }
            }
}
  
// WindowManagerImpl # addView()
  
  @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        /**
         * mGlobal ----> WindowManagerGlobal # addView
         */
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }
  // 在这里委托给WindowManagerGlobal 调用了它的addView()方法 
  
  public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        if (view == null) {
            throw new IllegalArgumentException("view must not be null");
        }
                    ....... 省略代码

            //创建了ViewRootImpl
            root = new ViewRootImpl(view.getContext(), display);

            // view ---> DecorView 设置布局参数
            view.setLayoutParams(wparams);

            //将添加的View保存到View列表中
            mViews.add(view);
            // 将root保存到ViewRootImpl的列表中
            mRoots.add(root);
            // 将wparams参数保存到mParams列表中
            mParams.add(wparams);

            // do this last because it fires off messages to start doing things
            try {
                /**
                重点
                 * root 是ViewRootImpl的对象
                 * 调用了ViewRootImpl的setView()
                 * 将DecorView添加到WMS
                 * 将窗口和窗口参数设置到ViewRootImpl中
                 */
                root.setView(view, wparams, panelParentView);
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
        }
    }
// 以上主要保存了一些信息。并且将View和布局参数传入到了ViewRootImpl的setView中

//ViewRootImpl # setView()
 public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {

                //这里传递进来的view其实是创建好的DecorView
                mView = view;
                                .......省略代码
                /**
                 * 重点呀
                 * 在将View添加到WindowManagerService中之前,确保View进行了一次 measure-->layout->draw
                 * 调用了此方法后,与ViewRootImpl关联的View会执行一次measure-->layout->draw
                 */
                requestLayout();
                if ((mWindowAttributes.inputFeatures
                        & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                    mInputChannel = new InputChannel();
                }
                mForceDecorViewVisibility = (mWindowAttributes.privateFlags
                        & PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY) != 0;
                try {
                    mOrigWindowType = mWindowAttributes.type;
                    mAttachInfo.mRecomputeGlobalAttributes = true;
                    collectViewAttributes();
                    /**
                     * 重点分析
                     */
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame,
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel,
                            mTempInsets);
                    setFrame(mTmpFrame);
                } 

                    ......省略代码
                                // 这里这个iew 是DecorView,把ViewRootImpl作为它的父亲设置给DecorView
                // 这也是DecorView与ViewRootImpl之间的关系
                view.assignParent(this);  
        }
    }
  • 可以说首次View的绘制 ActivityThread # performResumeActivity()方法是作为入口,真正调用了requestLayout()方法是在ViewRootImpl的setView()方法中。

ViewRootImpl的创建时机在什么时候

正如“View首次绘制时机时的分析”,ViewRootImpl的创建时机是在View首次绘制时,是在调用WindowManagerGlobal的addView()的方法中进行创建的。

ViewRootImpl与DecorView的关系

ViewRootImpl 是作为DecorView的父亲

在ViewRootImpl的setView()方法中 -----> view.assignParent(this);

ViewRootImpl实现了ViewParent的接口

DecorView的布局是怎么样的

  • 对于布局层级,是这样的,Activity ---> PhoneWindow ---> DecorView

  • 对于DecorView 是包含 titel_bar和content两部分的,DecorView是继承FrameLayout

  • 上述所说的是title_bar 和 content 对应的是 R.layout.screen_simple布局,这也是默认的布局

  • 那么这个布局是在什么设置的呢?

    • 入口是setContentView,真正执行的地方是在PhoneWindow的installDecor()方法中

      // PhoneWindow # installDecor
      private void installDecor() {
              mForceDecorInstall = false;
              if (mDecor == null) {
                  //创建DecorView(其实是一个FrameLayout)
                  mDecor = generateDecor(-1);
                  mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
                  mDecor.setIsRootNamespace(true);
                  if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                      mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
                  }
              } else {
                // 绑定一个Window
                  mDecor.setWindow(this);
              }
              if (mContentParent == null) {
                  // 将创建的DecorView传递 调用generateLayout方法
                  mContentParent = generateLayout(mDecor);
               }
             }
      // generaLayout(DedcorView view)
      
      protected ViewGroup generateLayout(DecorView decor) {
             
              ...... 省略代码
                
              // Inflate the window decor. 
              int layoutResource;
                  // 默认加载的布局是R.layout.screen_simple; 实则是通过获取到的features来加载不同的
              int features = getLocalFeatures();
              if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
                  // 这是一种布局
                  layoutResource = R.layout.screen_swipe_dismiss;
                  setCloseOnSwipeEnabled(true);
              } else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
                  if (mIsFloating) {
                      TypedValue res = new TypedValue();
                      getContext().getTheme().resolveAttribute(
                              R.attr.dialogTitleIconsDecorLayout, res, true);
                      layoutResource = res.resourceId;
                  } else {
                      //这是一种
                      layoutResource = R.layout.screen_title_icons;
                  }
                  // XXX Remove this once action bar supports these features.
                  removeFeature(FEATURE_ACTION_BAR);
                  // System.out.println("Title Icons!");
              } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
                      && (features & (1 << FEATURE_ACTION_BAR)) == 0) {
                  // Special case for a window with only a progress bar (and title).
                  // XXX Need to have a no-title version of embedded windows.
                  // 这是一种
                  layoutResource = R.layout.screen_progress;
                  // System.out.println("Progress!");
              } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
                  // Special case for a window with a custom title.
                  // If the window is floating, we need a dialog layout
                  if (mIsFloating) {
                      TypedValue res = new TypedValue();
                      getContext().getTheme().resolveAttribute(
                              R.attr.dialogCustomTitleDecorLayout, res, true);
                      layoutResource = res.resourceId;
                  } else {
                      //这是一种
                      layoutResource = R.layout.screen_custom_title;
                  }
                  // XXX Remove this once action bar supports these features.
                  removeFeature(FEATURE_ACTION_BAR);
              } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
                  // If no other features and not embedded, only need a title.
                  // If the window is floating, we need a dialog layout
                  if (mIsFloating) {
                      TypedValue res = new TypedValue();
                      getContext().getTheme().resolveAttribute(
                              R.attr.dialogTitleDecorLayout, res, true);
                      layoutResource = res.resourceId;
                  } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
                    
                      layoutResource = a.getResourceId(
                              R.styleable.Window_windowActionBarFullscreenDecorLayout,
                              R.layout.screen_action_bar);
                  } else {
                    //这是一种
                      layoutResource = R.layout.screen_title;
                  }
                  // System.out.println("Title!");
              } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
                  layoutResource = R.layout.screen_simple_overlay_action_mode;
              } else {
                  // Embedded, so no decoration is needed.
                  // 默认的布局
                  layoutResource = R.layout.screen_simple;     
              }
              mDecor.startChanging();
                  //加载布局
              mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
          }
      
      // 加载布局
        void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
              if (mBackdropFrameRenderer != null) {
                  loadBackgroundDrawablesIfNeeded();
                  mBackdropFrameRenderer.onResourcesLoaded(
                          this, mResizingBackgroundDrawable, mCaptionBackgroundDrawable,
                          mUserCaptionBackgroundDrawable, getCurrentColor(mStatusColorViewState),
                          getCurrentColor(mNavigationColorViewState));
              }
      
              mDecorCaptionView = createDecorCaptionView(inflater);
                  //解析资源文件,获取到View的对象 也就是布局文件对应的View对象
              final View root = inflater.inflate(layoutResource, null);
              if (mDecorCaptionView != null) {
                  if (mDecorCaptionView.getParent() == null) {
                      addView(mDecorCaptionView,
                              new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
                  }
                  mDecorCaptionView.addView(root,
                          new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
              } else {
      
                  // 将获取到的View添加到DecorView中
                  addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
              }
              mContentRoot = (ViewGroup) root;
              initializeElevation();
          }
      

DecorView的创建时机

在上述分析过程中,在PhoneWindow的installDecor()方法中有调用到generateLayout(-1)创建了DecorView

这是DecorView创建的地方。

installDecor() 调用的时机是从setContentView()作为入口的,执行的流程就是 Activity(setContentView) ---> PhoneWindow(setContentView) ----> installDecor()

所以DecorView的创建时机是setContentView()方法的执行

setContentView()干了什么

  • 创建了DecorView
  • 调用了LayoutInflate.inflate()方法,将布局文件解析成View
  • 将View添加到DecorView当中。

layoutInflate的流程是什么

https://www.jianshu.com/p/cc5db309cdcf

PhoneWindow的创建时机

  • 创建的地方是Activity的attach方法中
  • Activity#attach方法的执行入口是在ActivityThread # performLacunchActivity()
  • perfromLaunchActivity()方法的执行设计到Activity的启动流程。

如何触发View的重新绘制呢?

  • requestLayout()
  • Invalidate()

requestLayout 和 invalidate的区别

区别和联系

  • requestLayout:当我们移动一个View位置或者改变View的大小时候调用
    • View调用这个requestLayout方法,会标记当前View以及父容器,同时逐层向上提交,直到ViewRootImpl中,会调用三大工作流程 measure、layout、draw 对每一个由标记的View进行这三步操作。
  • invalidate的方法调用会引起View树的重新绘制,比如说 setVisible
    • 当View调用了invalidate方法后,会为该View添加一个标记位,不断向父容器请求刷新,父容器计算出需要重绘的区域,传递到ViewRootImpl中,触发performTraversals方法,开始对View树重新绘制(绘制需要重新绘的视图)
    • postInvalidate功能如同 invalidate 只不过是在非UI线程中调用。
  • 总的来说就是:requestLayout() 会执行 onMeasure()和onLayout();invalidate()方法会执行onDraw()
    • requestLayout()过程会设置 FORCE_LAYOUT标志位;invalidate()过程会设置dirty标志
  • onLayout:如果该View是ViewGroup对象,需要实现该方法,对每个子View进行布局
  • onDraw:绘制视图本身(ViewGroup不需要实现,View都需要重载这个方法)
  • drawChild:去重新回调每个子View的draw()方法。

点击事件中开启一个子线程更新UI会怎么样?

 btn_toast.setOnClickListener { listener ->
            //Toast.makeText(this, "展示 吐司", Toast.LENGTH_SHORT).show()
            Thread(Runnable {
                var name = Thread.currentThread().name
                Log.d("tag", "$name")
                btn_toast.text = "子线程"
            }).start()
        }

这个问题分两种情况,控件宽高固定,控件高度固定宽度warp_content,

情况1
  • 这种情况是会更新成功,并且不会crash
  • 由于宽和高都是固定的,此时操作更新UI并不会走requestLayout,仅仅重新绘制一下内容,所以就不存在线程检查的操作,所以就更新成功了。
情况2
  • 这种情况会crash
    android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
  • 原因就在于,当前的宽度是wrap_content,而当我们去操作更新的UI操作时,此时会调用requestLayout(重新进行测量和布局),在我们的requestlayout中会进行线程的检查,发现没有在主线程就会报错。

从Activity创建到View的显示这个过程中主要都发生了什么?

这个问题很广泛,我个人认为可以拆分成以下几点回答

handleLaunchActivity

在handleLaunchActivity方法主要调用了performLaunchActivity() 和 handleResumeActivity()

  • 在performLaunchActivity中主要做了如下几个事情
    • 创建了Context
    • 通过反射创建了Activity
    • 通过反射创建了Application
    • 调用了attach
      • 为每个Activity创建一个PhoneWindow
      • 将WindowManger与PhoneWindow进行关联
    • 执行了Activity的onCreate()方法
setContentView()过程

调用流程是 Activity(setContentView) ---> PhoneWindow(setContentView) ---> installDecor()

  • 在installDecor中主要做了如下几个事情
    • 创建了DecorView ---> DecorView extends FrameLayout
    • 为DecorView中content加载布局文件
handleResumeActivity()方法的执行过程
  • 首先调用了performResumeActivity()
    • 执行了Activity的onResume()
    • 获取了Activity专属的Window
    • 获取到了DecorView
  • 接着调用了WindowManagerImpl的addView()
    • 内部调用了WindowManagerGlobal # addView()
  • WindowManagerGlobal # addView
    • 创建了ViewRootImpl
    • 调用了ViewRootImpl # setView()
  • ViewRootImpl # setView
    • requestLayout() ---> 保证在绘制之前 进行了 View的绘制流程
    • 将携带数据的Window发送到系统进程 WMS中进行数据的绘制。

相关文章

  • View的面试锦囊

    View的draw流程 绘制背景 绘制自己 绘制子View 绘制装饰 View.getLocationInWind...

  • 面试锦囊

    面试锦囊前端内参:https://coffe1891.gitbook.io/frontend-hard-mode-...

  • 面试小锦囊

    文/职场鱼美人 前世界500强高管,现英语校长,学霸妈妈,心理咨询师,马拉松跑者 每一次作为面试官,我都非常谨慎。...

  • Android 绘制原理浅析【干货】

    背景 对于Android开发,在面试的时候,经常会被问到,说一说View的绘制流程?我也经常问面试者,View的绘...

  • Android 绘制原理浅析

    背景 对于Android开发,在面试的时候,经常会被问到,说一说View的绘制流程?我也经常问面试者,View的绘...

  • Android 绘制原理浅析「干货」

    背景 对于Android开发,在面试的时候,经常会被问到,说一说View的绘制流程?我也经常问面试者,View的绘...

  • 《 善用时间》之锦囊

    锦囊1 锦囊2 锦囊3 锦囊4 锦囊5 锦囊6 锦囊7 锦囊8 锦囊9 锦囊10 锦囊11 锦囊12

  • 深入了解Android View 绘制流程

    View的绘制还有什么好聊的 前面我们已经减少了view的绘制流程了,很多同学都知道,面试的时候面试官问你:vie...

  • MS(5):android之进阶篇

    七、自定义View MS思考:Android面试一天一题(Day 30:老外的自定义View面试题)MS思考:老外...

  • 艺术开发探索第三章笔记

    《Android开发艺术探索》第三章笔记 最近面试整理 View的基础知识 什么是View View是Androi...

网友评论

      本文标题:View的面试锦囊

      本文链接:https://www.haomeiwen.com/subject/rakppktx.html