Android自定义View开篇:View绘制时机

前言

Android 中 Activity 是作为应用程序的载体存在,代表着一个完整的用户界面,提供了一个窗口来绘制各种视图,当 Activity 启动时,我们会通过 setContentView 方法来设置一个内容视图,这个内容视图就是用户看到的界面。那么 View 和 activity 是如何关联在一起的呢 ?

上图是 View 和 Activity 之间的关系。先解释图中一些类的作用以及相关关系:

  • Window:每个Activity都会创建一个Window用于承载View视图的显示,Window是一个抽象类,存在一个唯一实现类PhoneWindow。
  • PhoneWindow:该类继承于Window类,是Window类的具体实现,我们可以通过该类去绘制窗口。并且,该类内部包含了一个 DecorView 对象,该 DectorView 对象是所有应用窗口的根 View。
  • DecorView:最顶层的View,是一个FrameLayout子类,最终会被加载到Window当中,它内部只有一个垂直方向的LinearLayout分为两部分:一个是TitleBar(ActionBar 的容器),另一个是ContentView(Activity对应的XML布局,通过setContentView设置到DecorView中)。
  • WindowManager : 是一个接口,里面常用的方法有:添加View,更新View和删除View。主要是用来管理 Window 的。WindowManager 具体的实现类是WindowManagerImpl。最终,WindowManagerImpl 会将业务交给 WindowManagerGlobal 来处理。
  • WindowManagerService (WMS) : 负责管理各 app 窗口的创建,更新,删除, 显示顺序。运行在 system_server 进程。
  • ViewRootImpl :拥有 DecorView 的实例,通过该实例来控制 DecorView ,最终通过执行ViewRootImpl的performTraversals()开启整个View树的绘制。ViewRootImpl 的一个内部类 W,实现了 IWindow 接口,IWindow 接口是供 WMS 使用的,WSM 通过调用 IWindow 一些方法,通过 Binder 通信的方式,最后执行到了 W 中对应的方法中。同样的,ViewRootImpl 通过 IWindowSession 来调用 WMS 的 Session 一些方法。Session 类继承自 IWindowSession.Stub,每一个应用进程都有一个唯一的 Session 对象与 WMS 通信。

DecorView 的创建

首先来看一下Activity中setContentView源码:

    public void setContentView(@LayoutRes int layoutResID) {
        //将xml布局传递到Window当中
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

从代码可以看出,Activity的setContentView实质是将View传递到Window的setContentView()方法中,Window的setContenView会在内部调用installDecor()方法创建DecorView,看一下它的部分源码:

 public void setContentView(int layoutResID) { 
        if (mContentParent == null) {
            //初始化DecorView以及其内部的content
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        ...............
        } else {
            //将contentView加载到DecorVoew当中
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        ...............
    }

  private void installDecor() {
        ...............
        if (mDecor == null) {
            //实例化DecorView
            mDecor = generateDecor(-1);
            ...............
            }
        } else {
            mDecor.setWindow(this);
        }
       if (mContentParent == null) {
            //获取Content
            mContentParent = generateLayout(mDecor);
       }  
        ...............
 }

 protected DecorView generateDecor(int featureId) {
        ...............
        return new DecorView(context, featureId, this, getAttributes());
 }

通过generateDecor()new一个DecorView,然后调用generateLayout()获取DecorView中content,最终通过inflate将Activity视图添加到DecorView中的content中,到此,DecorView 的创建就讲完了。可是我们似乎并没有看到 DecorView 是被添加的,什么时候对用户可见的。

WindowManager

View 创建完以后,那 Decorview 是怎么添加到屏幕中去的呢?当然是 WindowManager 呢,那么是如何将 View 传到 WindowManager 中呢。

public interface WindowManager extends ViewManager {
    public static class BadTokenException extends RuntimeException{...}
    public static class InvalidDisplayException extends RuntimeException{...}
    public Display getDefaultDisplay();
    public void removeViewImmediate(View view);
    public static class LayoutParams extends ViewGroup.LayoutParams
        implements Parcelable

ViewManager接口定义了一组规则,也就是add、update、remove的操作View接口。也就是说ViewManager是用来添加和移除activity中View的接口,可以通过Context.getSystemService()获取实例。

WindowManager是一个接口,而且它继承与ViewManager。WindowManager字面理解就是窗口管理器,每一个窗口管理器都与一个的窗口显示绑定。获取实例可以通过
Context.getSystemService(Context.WINDOW_SERVICE)获取。既然继承了ViewManager,那么它也就可以进行添加删除View的操作了,不过它的操作放在它的实现类WindowManagerImpl里面。

  • BadTokenException:则是addView时它的LayoutParams无效则会被抛出,或是添加第二个View的时候没有移除第一个View则会被抛出
  • InvalidDisplayException:如果一个窗口是在一个二级的显示上而指定的显示找不到则会被抛出
  • getDefaultDisplay:返回当前WindowManager管理的显示Display
  • removeViewImmediate:表示从窗口上移除View,一般是当View调用了onDetachedFromWindow也就是从Window上分开后,把它移除。
  • LayoutParams:静态内部类。显然是Window的布局参数,里面定义了一系列的窗口属性。
WindowManagerImpl
public final class WindowManagerImpl implements WindowManager {
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
    private final Display mDisplay;
    private final Window mParentWindow;
     @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mDisplay, mParentWindow);
    }
    ...
     @Override
    public void removeView(View view) {
        mGlobal.removeView(view, false);
    }
}

可以看到WindowManagerImpl里面有一个成员变量WindowManagerGlobal,而真正的实现则是在WindowManagerGlobal了,类似代理,只不过WindowManagerGlobal是个没有实现WindowManager的类的,自己定义了套实现。

public final class WindowManagerGlobal {
    private static final String TAG = "WindowManager";
     public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
            ...
     }
}

ViewRootImpl

实际上,View 的绘制是由 ViewRootImpl 来负责的。每个应用程序窗口的 DecorView 都有一个与之关联的 ViewRootImpl 对象,这种关联关系是由 WindowManager 来维护的。

先看 ViewRootImpl 的 setView 方法,该方法很长,我们将一些不重要的点注释掉:

/**
     * We have one child
     */
    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
                mView = view;
                ......
               
                mAdded = true;
                int res; /* = WindowManagerImpl.ADD_OKAY; */

                // Schedule the first layout -before- adding to the window
                // manager, to make sure we do the relayout before receiving
                // any other events from the system.

                requestLayout();
                ......
            }
        }
    }

这里先将 mView 保存了 DecorView 的实例,然后调用 requestLayout() 方法,以完成应用程序用户界面的初次布局。

public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }

因为是 UI 绘制,所以一定要确保是在主线程进行的,checkThread 主要是做一个校验。接着调用 scheduleTraversals 开始计划绘制了。

void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

最终调用其 run 方法:

final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }

该方法内部最终会调用 performTraversals 进行绘制。

void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }

            performTraversals();

            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }

到此,DecorView 与 activity 之间的绑定关系就讲完了。后面代码比较多,总结一下就是:
ViewRootImpl的作用是用来衔接WindowManager和DecorView,在Activity被创建后会通过WindowManager将DecorView添加到PhoneWindow中并且创建ViewRootImpl实例,随后将DecorView与ViewRootImpl进行关联,最终通过执行ViewRootImpl的performTraversals()开启整个View树的绘制。

下一章,将会介绍 performTraversals 所做的事情,也就是 View 绘制流程。
Android自定义View中篇:View绘制流程

你可能感兴趣的:(Android自定义View开篇:View绘制时机)