自定义View——背景知识

  1. Activity
    • 作用:负责生命周期管理与事件处理,每个Activity组合了一个Window,实际视图控制是交由Winodw管理ui排版。是为了满足多窗口管理和傻瓜式视图管理的需要而诞生的。
    • 起源:在Activity中的onCreate()方法中会调用setContentView()加载自定义视图,实际调用的是WindowsetContentView方法
    • 相关源码:
      android/app/Activity.java
      public void setContentView(@LayoutRes int layoutResID) {
          //调用mWindow.setContentView()
          getWindow().setContentView(layoutResID);
          initWindowDecorActionBar();
      }
      
      android/app/Activity.java
      final void attach(... ...){
         ... ...
          //实例化mWindow
         mWindow = new PhoneWindow(this, window, activityConfigCallback);
          //实现window的接口,键盘触摸 ui策略等
         mWindow.setWindowControllerCallback(this);
         mWindow.setCallback(this);
         mWindow.setOnWindowDismissedCallback(this);
         ... ...
      }
      
  2. Window
    • 作用:唯一实现类PhoneWindow,创建了顶层视图DecorViewDecorView作为父布局用来加载ActivitysetContentView()方法传入的layoutRes。是为了管理ui排版,视图控制而诞生的。
    • 相关源码:
      com/android/internal/policy/PhoneWindow.java
      @Override
      public void setContentView(int layoutResID) {
          if (mContentParent == null) {
              //创建DecorView,并实例化DecorView布局内的ViewGroup控件mContentParent
              installDecor();
          } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
              mContentParent.removeAllViews();
          }
      
          if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
              final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                      getContext());
              transitionTo(newScene);
          } else {
              //将mContentParent作为父控件,加载layoutResID
              mLayoutInflater.inflate(layoutResID, mContentParent);
          }
         ... ...
      }
      
      com/android/internal/policy/PhoneWindow.java
      private void installDecor() {
          ... ...
          if (mDecor == null) {
              //创建DecorView
              mDecor = generateDecor(-1);
             ... ...
          }
          ... ...
          if (mContentParent == null) {
              //实例化mContentParent
             mContentParent = generateLayout(mDecor);
              ... ...
          }
      }
      
      com/android/internal/policy/PhoneWindow.java
      protected DecorView generateDecor(int featureId) {
          ... ...
          //创建DecorView
          return new DecorView(context, featureId, this, getAttributes());
      }
      
      com/android/internal/policy/PhoneWindow.java
      public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
      protected ViewGroup generateLayout(DecorView decor) {
          ... ...
          int layoutResource;
          if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
              layoutResource = R.layout.screen_swipe_dismiss;
              ... ...
          } 
          ... ...
          else {
             layoutResource = R.layout.screen_simple;
          }
          ... ...
          //加载DecorView布局
          mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
          //从DecorView中获取id为content的控件
         ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
          ... ...
          //将contentParent返回
          return contentParent;
      }
      
      android/view/Window.java
      public <T extends View> T findViewById(@IdRes int id) {
          //返回DecorView中给定id的视图控件
          return getDecorView().findViewById(id);
      }
      
      frameworks/base/core/res/res/layout/screen_simple.xml
      <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:fitsSystemWindows="true"
         android:orientation="vertical">
         <ViewStub android:id="@+id/action_mode_bar_stub"
             android:inflatedId="@+id/action_mode_bar"
             android:layout="@layout/action_mode_bar"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:theme="?attr/actionBarTheme" />
      
         <FrameLayout
             android:id="@android:id/content"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
             android:foregroundInsidePadding="false"
             android:foregroundGravity="fill_horizontal|top"
             android:foreground="?android:attr/windowContentOverlay" />
      </LinearLayout>
      
  3. DecorView
    • 作用:继承自FrameLayout,是Android视图树的根视图,View层的事件都会先经过DecorView,再分发到下面的View
    • 相关源码:
      com/android/internal/policy/DecorView.java
      void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
          mDecorCaptionView = createDecorCaptionView(inflater);
          //加载PhoneWindow生成的layoutResource
          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 {
             //添加到DecorView中
              addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
          }
          mContentRoot = (ViewGroup) root;
      }
      
  4. WindowManager
    • 作用:在Activity生命周期onResume之后,绑定DecorViewViewRootImpl
    • 相关源码:
      android/app/ActivityThread.java
      public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
              String reason) {
             ... ...
          //执行activity的onResume方法
          final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
          ... ...
             final Activity a = r.activity;
             if (r.window == null && !a.mFinished && willBeVisible) {
              //获取activity的window实例
             r.window = r.activity.getWindow();
              //获取window中实例化的DecorView
             View decor = r.window.getDecorView();
             decor.setVisibility(View.INVISIBLE);
              //获取windowManager,获取的是WindowManager的子类,WindowManagerImpl
             ViewManager wm = a.getWindowManager();
             WindowManager.LayoutParams l = r.window.getAttributes();
             a.mDecor = decor;
             if (a.mVisibleFromClient) {
                 if (!a.mWindowAdded) {
                     a.mWindowAdded = true;
                      //windowManager调用addView添加DecorView
                     wm.addView(decor, l);
                 }
                  ... ...
             }
         }
          ... ...
      }
      
      android/view/WindowManagerImpl.java
      public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
          mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
      }
      
      android/view/WindowManagerGlobal.java
      public void addView(View view, ViewGroup.LayoutParams params,
              Display display, Window parentWindow) {
          ViewRootImpl root;
          //实例化ViewRootImpl
          root = new ViewRootImpl(view.getContext(), display);
          //保存decorView对象
          mViews.add(view);
          //保存viewRootImpl对象
          mRoots.add(root);
          //保存decorView的layout params
          mParams.add(wparams);
          try {
              //绑定ViewRootImpl与DecorView
              root.setView(view, wparams, panelParentView);
          }... ...
      }
      
  5. ViewRoot(实际实现类是android.view.ViewRootImpl)
    • 作用
      1. 连接DecorViewWindowManager,也可以说是WindowDecorView的纽带
      2. 通过DecorView完成View的三大流程绘制
      3. 用责任链模式,向DecorView分发用户的InputEvent事件
    • 注意:在Activity.onResume()之后,ViewRootImpl.setView中将DecorView传入,并在之后执行requestLayout()通过DecorView递归执行View的三大流程,所以View的三大流程都是在Activity.onResume之后。
    • 相关源码:
      android/view/ViewRootImpl.java
      final IWindowSession mWindowSession;
      
      public ViewRootImpl(Context context, Display display) {
          //WindowManagerGlobal获取IWindowSession
          mWindowSession = WindowManagerGlobal.getWindowSession();
      }
      
      public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
          synchronized (this) {
              if (mView == null) {
                  //这个mView就是DecorView,view的三大绘制流程均是通过mView来递归的
                  mView = view;
                  ... ...
                  //view的三大绘制流程
                  requestLayout();
                  //创建InputChannel
                  mInputChannel = new InputChannel();
                  //wms根据当前的window创建了SocketPair用于跨进程通信,并对传入的mInputChannel进行了注册
                  //此后ViewRootImpl中的mInputChannel就指向了正确的InputChannel
                  //client端与server端就能进行双向通信了
                  res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                                 getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame,
                             mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                             mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel,
                             mTempInsets);
                  //创建WindowInputEventReceiver,处理事件分发
                  mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
                                Looper.myLooper());
                  //组装InputStage责任链
                  mSyntheticInputStage = new SyntheticInputStage();
                  InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage);
                  InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage,
                              "aq:native-post-ime:" + counterSuffix);
                  InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage);
                  InputStage imeStage = new ImeInputStage(earlyPostImeStage,
                              "aq:ime:" + counterSuffix);
                  InputStage viewPreImeStage = new ViewPreImeInputStage(imeStage);
                  InputStage nativePreImeStage = new NativePreImeInputStage(viewPreImeStage,
                              "aq:native-pre-ime:" + counterSuffix);
                  mFirstInputStage = nativePreImeStage;
                  mFirstPostImeInputStage = earlyPostImeStage;
              }
          }
      }
      
      • View的三大绘制流程入口
      android/view/ViewRootImpl.java
      public void requestLayout() {
          if (!mHandlingLayoutInLayoutRequest) {
              checkThread();
              mLayoutRequested = true;
              //这里执行view的绘制流程
              scheduleTraversals();
          }
      }
      
      final class TraversalRunnable implements Runnable {
         @Override
         public void run() {
             doTraversal();
         }
      }
      
      final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
      
      void scheduleTraversals() {
         if (!mTraversalScheduled) {
              ... ...
             mChoreographer.postCallback(
                 Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
              ... ...
         }
      }
      
      void doTraversal() {
         if (mTraversalScheduled) {
             ... ...
             performTraversals();
              ... ...
         }
      }
      
      private void performTraversals() {
          ... ...
          //measure流程
          performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
          //layout流程
          performLayout(lp, mWidth, mHeight);
          //draw流程
          performDraw();
      }
      
      private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
         try {
              //执行DecorView的mesaure流程
             mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_VIEW);
         }
      }
      
      private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
                  int desiredWindowHeight) {
          final View host = mView;
          try {
              //执行DecorView的layout流程
             host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
          }
      }
      
      private void performDraw() {
          ... ...
          try {
             boolean canUseAsync = draw(fullRedrawNeeded);
          }
          ... ...
      }
      
      private boolean draw(boolean fullRedrawNeeded) {
          ... ...
          if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
              scalingRequired, dirty, surfaceInsets)) {
             return false;
         }
          ... ...
      }
      
      private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
              boolean scalingRequired, Rect dirty, Rect surfaceInsets) {
          ... ...
          //执行DecorView的draw流程
          mView.draw(canvas);
          ... ...
      }
      
  6. 综上
    1. 创建DecorView的流程:
      • Activityattach时,会创建PhoneWindow实例mWindow;
      • ActivityonCreate中调用setContentView方法时,会间接调用mWindow.setContentView;
      • PhoneWindowsetContentView中会创建DecorView的实例,并将Activity传入的布局加载到DecorViewidcontentmContentParent中;
      • DecorView是android视图树的根节点,最底层的View,在PhoneWindow创建布局时generateLayout会根据不同类型的主题创建布局,其中会有一个idcontentViewGroupActivity中传入的布局文件就是以content为父布局;
    2. 建立DecorViewWindowManager的联系并最终绘制显示的流程:
      • ActivityThread在调用handleResumeActivity,会执行ActivityonResume方法,随后,会创建ViewRootImpl的实例,获取ActivityWindow实例,并获取Window中的DecorView实例,使用WindowManager,将ViewRootImplDecorView绑定;
      • ViewRootImpl获取到DecorView的实例后,会持有该引用,并分步调用mViewmeasure,layout,draw方法。
    3. InputManager监控到硬件层面的输入事件时,会通知ViewRootImpl对输入事件进行底层分发(具体细节查看View的事件分发机制)
      • 创建InputChannel,并通过BinderSystemServer进程中完成InputChannel的注册。
      • 创建WindowInputEventReceiver来处理事件分发。
      • 组装InputStage责任链,负责不同InputEvent事件的处理。
  7. 其他要点
    • View ---getLeft() getTop() getRight() getBottom()返回的是View 相对与ViewGroup的左上右下距离
    • MotionEvent---getRawX() getRawY()返回的是触摸点相对于屏幕的x/y距离 getX() getY()返回的是触摸点相对于自身控件的x/y距离
  8. 系列文章
    1. View的背景知识
    2. View的测量流程
    3. View的布局流程
    4. View的绘制背景知识
    5. View的绘制流程
    6. View的三大绘制流程总结
    7. View的事件分发机制
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,529评论 5 475
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,015评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,409评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,385评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,387评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,466评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,880评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,528评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,727评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,528评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,602评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,302评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,873评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,890评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,132评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,777评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,310评论 2 342