深入 Activity 三部曲(2)View 绘制流程之 DecorView 添加至窗口的过程

UI 优化系列专题,来聊一聊 Android 渲染相关知识,主要涉及 UI 渲染背景知识如何优化 UI 渲染两部内容。


UI 优化系列专题
  • UI 渲染背景知识

View 绘制流程之 setContentView() 到底做了什么?
View 绘制流程之 DecorView 添加至窗口的过程
深入 Activity 三部曲(3)View 绘制流程
Android 之 LayoutInflater 全面解析
关于渲染,你需要了解什么?
Android 之 Choreographer 详细分析

  • 如何优化 UI 渲染

Android 之如何优化 UI 渲染(上)
Android 之如何优化 UI 渲染(下)


在上一篇文章《View 加载过程之 setContentView() 到底做了什么 ?》,我们分析了 View 绘制流程的整个创建过程,再来简单回顾下该内容。

  • 每个 Activity 都有一个关联的 Window 对象,用来描述应用程序窗口,每个窗口内部又包含一个 DecorView 对象,DecorView 对象用来描述窗口的视图 — xml 布局。通过 setContentView() 设置的 View 布局最终添加到 DecorView 的 content 容器中。

DecorView 作为 Activity 的最顶层视图,它的整个创建过程我们已经知道了,那它又是如何添加至窗口中的呢?还是通过几个问题来了解下今天要分析的内容。

  1. DecorView 添加至窗口的过程
  • View 绘制流程的开始时机?
  • WindowManager 是什么?它的作用有哪些?
  1. View 绘制流程
  • ViewRootImpl 是什么?它的核心工作是什么?
  • 为什么 View 的 requestLayout() 最终会执行到 ViewRootImpl 的 requestLayout() ?
  • View 绘制流程为什么一定要在 Main 线程?

如果以上问题你都能够熟练并正确的回答出来,那么恭喜你可以直接跳过该篇文章。


View 的绘制时机

要分析 DecorView 如何添加到窗口上面的,我们要从 Activity 的创建过程开始说起,首先 Activity 的创建以及生命周期的调用,都是通过进程间通信完成的,在应用进程内完成调度任务的是 ActivityThread。ActivityThread 是我们应用进程的入口类(main 函数所在)。

在 ActivityThread 中,完成 Activity 创建任务的方法是 handleLaunchActivity 方法:

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
    //... 省略

    // 获取远程WindowManagerService代理对象
    WindowManagerGlobal.initialize();

    // 创建Activity,并调用其onCreate方法
    // 创建PhoneWindow、DecorView等
    Activity a = performLaunchActivity(r, customIntent);

    if (a != null) {
        r.createdConfig = new Configuration(mConfiguration);
        reportSizeConfigurations(r);
        Bundle oldState = r.state;
        //回调Activity的onResume
        //也会完成 Actiivty UI 绘制流程
        handleResumeActivity(r.token, false, r.isForward,
                !r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);
    }

    //... 省略

}

handleLaunchActivity 方法主要完成以下两部分内容:

  • performLaunchActivity 方法,完成 Activity 的创建以及 onCreate 生命周期方法回调。

  • handleResumeActivity 方法,完成 onResume 生命周期方法回调后,发起 DecorView 向窗口添加过程,开始 View 绘制流程。

  1. performLaunchActivity 方法,Activity 的创建过程。
    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {

    //... 省略

    try {
        // 通过反射创建当前Activity实例
        java.lang.ClassLoader cl = appContext.getClassLoader();
        activity = mInstrumentation.newActivity(
                cl, component.getClassName(), r.intent);
        StrictMode.incrementExpectedActivityCount(activity.getClass());
        r.intent.setExtrasClassLoader(cl);
        r.intent.prepareToEnterProcess();
        if (r.state != null) {
            r.state.setClassLoader(cl);
        }
    } catch (Exception e) {
         //... 省略
    }

    try {
        Application app = r.packageInfo.makeApplication(false, mInstrumentation);


        if (activity != null) {  
            //调用Activity的attach方法,其中PhoneWindow就是该该方法中被创建
            activity.attach(appContext, this, getInstrumentation(), r.token,
                    r.ident, app, r.intent, r.activityInfo, title, r.parent,
                    r.embeddedID, r.lastNonConfigurationInstances, config,
                    r.referrer, r.voiceInteractor, window, r.configCallback);

            activity.mCalled = false;
            // 回调Activity的onCreate方法
            if (r.isPersistable()) {
                mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
            } else {
                mInstrumentation.callActivityOnCreate(activity, r.state);
            }
           
            // 当前Activity赋值给保存它记录的ActivityClientRecord
            r.activity = activity;
            r.stopped = true;
            if (!r.activity.mFinished) {
                // 回调Activity的onStart方法
                activity.performStart();
                r.stopped = false;
            }
            // Activity的状态恢复onRestoreInstanceState方法被回调
            // 如果Bundle为null,表示无数据,是不会调用onRestoreInstanceState
            if (!r.activity.mFinished) {
                if (r.isPersistable()) {
                    if (r.state != null || r.persistentState != null) {
                        mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state,
                                r.persistentState);
                    }
                } else if (r.state != null) {
                    mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state);
                }
            }
            // 回调Activity的onPostCreate方法
            if (!r.activity.mFinished) {
                activity.mCalled = false;
                if (r.isPersistable()) {
                    mInstrumentation.callActivityOnPostCreate(activity, r.state,
                            r.persistentState);
                } else {
                    mInstrumentation.callActivityOnPostCreate(activity, r.state);
                }
                if (!activity.mCalled) {
                    throw new SuperNotCalledException(
                        "Activity " + r.intent.getComponent().toShortString() +
                        " did not call through to super.onPostCreate()");
                }
            }
        }
        r.paused = true;
        // 保存当前ActivityClientRecord
        mActivities.put(r.token, r);

    } catch (SuperNotCalledException e) {
        throw e;

    } catch (Exception e) {
          // ... 省略           
    }

       return activity;
    }

系统通过 ClassLoader 反射创建当前 Activity 实例,然后调用 Activity 的 attach 方法,还记的上篇文章《View 绘制流程之 setContentView 到底做了什么?》,PhoneWindow 就是在 attach 方法中被创建的。

attach 方法执行结束后,依次回调 Activity 生命周期函数:onCreate() —> onStart() —> onRestoreInstanceState()(注意该方法不是必须,取决于是否真的保存了数据,即 Bundle != null)—> onPostCreate()

  1. handleResumeActivity 方法,View 在 Activity 的绘制起始点。

如果仅从名字来看,大家肯定会想到它最终会回调 Activity 的 onResume 生命周期方法。但实际它可能比我们想象的要复杂。

final void handleResumeActivity(IBinder token,
        boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
    // 获取当前Activity的ActivityClientRecord
    ActivityClientRecord r = mActivities.get(token);
    if (!checkAndUpdateLifecycleSeq(seq, r, "resumeActivity")) {
        return;
    }

    mSomeActivitiesChanged = true;

    // 执行Activity的onResume生命周期方法
    r = performResumeActivity(token, clearHide, reason);

    if (r != null) {
        final Activity a = r.activity;

        final int forwardBit = isForward ?
                WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION : 0;

        boolean willBeVisible = !a.mStartedActivity;
        if (!willBeVisible) {
            try {
                // 活动是否可见状态
                willBeVisible = ActivityManager.getService().willActivityBeVisible(
                        a.getActivityToken());
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }
        // 重点在这里
        if (r.window == null && !a.mFinished && willBeVisible) {
            //获取当前PhoneWindow,就是在setContentView中创建的
            r.window = r.activity.getWindow();
            //获取当前Activity的根视图DecorView
            View decor = r.window.getDecorView();
            //设置DecorView不可见
            decor.setVisibility(View.INVISIBLE);
            //WindowManager是一个接口,实现类是WindowManagerImpl
            ViewManager wm = a.getWindowManager();
            WindowManager.LayoutParams l = r.window.getAttributes();
            //当前DecorView赋值给Activity
            a.mDecor = decor;
            l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
            l.softInputMode |= forwardBit;
            if (r.mPreserveWindow) {
                a.mWindowAdded = true;
                r.mPreserveWindow = false;
                // 首次创建此时,该DecorView还没有关联ViewRootImpl
                ViewRootImpl impl = decor.getViewRootImpl();
                if (impl != null) {
                    impl.notifyChildRebuilt();
                }
            }
            if (a.mVisibleFromClient) {
                if (!a.mWindowAdded) {
                    a.mWindowAdded = true;
                    // 这里将DecorView关联到WindowManager
                    // 最后也会与ViewRootImpl进行关联
                    wm.addView(decor, l);
                } else {
                    //如果Window已经被添加,则只需要更新属性
                    a.onWindowAttributesChanged(l);
                }
            }
        } else if (!willBeVisible) {}} else {}
}

先来看下 onResume 生命周期方法的回调过程,performResumeActivity 方法如下:

public final ActivityClientRecord performResumeActivity(IBinder token,
        boolean clearHide, String reason) {
    //获取当前Activity的ActivityClientRecord
    ActivityClientRecord r = mActivities.get(token);

    //判断Activity没有被Finish
    if (r != null && !r.activity.mFinished) {
        if (clearHide) {
            r.hideForNow = false;
            r.activity.mStartedActivity = false;
        }
        try {
            //onStateNotSave方法在onResume之前
            //表示当前Activity的状态不在被保存
            //一般在onNewIntent方法或者onActivityResult时可能会使用
            r.activity.onStateNotSaved();
            //同上,这里是通知该Activity的Fragment
            r.activity.mFragments.noteStateNotSaved();
            if (r.pendingIntents != null) {
                deliverNewIntents(r, r.pendingIntents);
                r.pendingIntents = null;
            }
            if (r.pendingResults != null) {
                deliverResults(r, r.pendingResults);
                r.pendingResults = null;
            }
            //回调Activity的onResume方法
            r.activity.performResume();

            //... 省略

            r.paused = false;
            r.stopped = false;
            r.state = null;
            r.persistentState = null;
        } catch (Exception e) {
             //... 省略
        }
    }
    return r;
}
  • 注意 onStateNotSaved 方法,如果不是看源码可能还真不一定知道它的存在,Activity 提供了onSaveInstanceState(Bundle outState) 帮助我们保存 Activity 相关状态信息,onStateNotSaved 方法就是告知我们当前 Activity 相关状态信息不再被保存。

  • performResume 方法最终回调 Activity 的 onResume 方法。

不过,我们今天要分析的重点是 DecorView 如何添加到窗口中的?回到
handleResumeActivity 方法,重点部分来了(注意查看上面贴出源码):

//获取当前Activity中PhoneWindow,就是在setContentView时关联的
r.window = r.activity.getWindow();
//获取当前Activity的根视图DecorView
View decor = r.window.getDecorView();
//设置DecorView不可见
decor.setVisibility(View.INVISIBLE);
//WindowManager是一个接口,实现类是WindowManagerImpl
//WindowManager是在PhoneWindow创建后,调用其setWindowManager
//被添加,这里直接通过getWindowManager获取
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();

getWindow() 就是在 Activity 的 attach 方法关联的 PhoneWindow。

  • getDecorView(),获取当前 PhoneWindow 中 DecorView,将其置为不可见状态。

  • getWindowManager(),获取当前 PhoneWindow 的
    WindowManager。调用 WindowManager 的 addView 方法,将 DecorView 添加到 WindowManager 中。

WindowManager 是何妨神圣?翻看它的源码发现是一个接口,继承自 ViewManager:

public interface WindowManager extends ViewManager {  
         //... 省略
}

ViewManager 中主要包含三个操作 View 的关键方法:

public interface ViewManager {

  //添加View到Window
  public void addView(View view, ViewGroup.LayoutParams params);
  //更新View的LayoutParams
  public void updateViewLayout(View view, ViewGroup.LayoutParams params);
  //在Window中移除该View
  public void removeView(View view);
}

那它的实现类是谁呢,其实大家根据 Google 工程师的命名规范也大概能猜到,没错 WindowManagerImpl,它是在哪里被创建的呢? 前面我们也有分析过 PhoneWindow 是在 Activity 的 attach 方法中被创建,而 WindowManager 就是在 PhoneWindow 被创建后,调用它的 setWindowManager 方法完成:

// PhoneWindow 的 setWindowManager 方法
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
        boolean hardwareAccelerated) {
    if (wm == null) {
        //这个是SystemServerRegistry中注册的WindowManagerImpl,即应用进程窗口管理者WindowManager。
        wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
    }
    //为当前PhoneWindow创建本地WindowManagerImpl
    mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}

看下代表 Activity 级别的窗口管理者 WindowManager 创建过程:

public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
    //为当前 PhoneWindow 关联一个 WindowManagerImpl
    return new WindowManagerImpl(mContext, parentWindow);
}

验证下我们的猜测,WindowManagerImpl 是否实现了 WindowManager ?它的声明如下:

public final class WindowManagerImpl implements WindowManager {
    //实际WindowManagerImpl中的操作代理给了WindowManagerGlobal
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();

    //... 省略
}

系统为每个 PhoneWindow 都关联一个 WindowManagerImpl。这里要说明的是,在 Android 中,Window(PhoneWindow) 是 View 的容器,每个窗口都会关联一个 Surface。而 WindowManager 则负责管理这些窗口,并把它们的数据传递给 SurfaceFlinger

即 addView 方法实际调用到 WindowManagerImpl 中:

public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
    applyDefaultToken(params);
    //丢给了WindowManagerGlobal的addView
    mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}

mGlobal 是 WindowManagerGlobal。它又是何妨神圣?

  • 实际上 WindowManager 管理窗口的操作又委托给了 WindowManagerGlobal,WindowMnagerGlobal 是一个单例,即应用进程内仅有一个,这好像也是命名 Global 的原因吧。

WindowManagerGlobal 中主要包含几个比较重要的 Window 管理容器:

//保存DecorView
private final ArrayList<View> mViews = new ArrayList<View>();
//保存DecorView对应的ViewRootImpl
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
//保存DecorView的LayoutParams
private final ArrayList<WindowManager.LayoutParams> mParams =
        new ArrayList<WindowManager.LayoutParams>();
//保存已经被removeView的DecorView
private final ArraySet<View> mDyingViews = new ArraySet<View>();

WindowManagerImpl 将添加操作委托给 WindowManagerGlobal, addView 方法如下:

//view是DecorView
public void addView(View view, ViewGroup.LayoutParams params,
                    Display display, Window parentWindow) {
    if (view == null) {
        //DecorView不能为null
        throw new IllegalArgumentException("view must not be null");
    }
    if (display == null) {
        //每个Activity都会关联一个Display
        //通过ContextImpl创建
        throw new IllegalArgumentException("display must not be null");
    }
    if (!(params instanceof WindowManager.LayoutParams)) {
        //必须是WindowManager.LayoutParams
        throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
    }

    final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
    if (parentWindow != null) {
        parentWindow.adjustLayoutParamsForSubWindow(wparams);
    } else {
        // If there's no parent, then hardware acceleration for this view is
        // set from the application's hardware acceleration setting.
        final Context context = view.getContext();
        //应用是否配置了硬件加速
        if (context != null
                && (context.getApplicationInfo().flags
                & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
            //硬件加速
            wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
        }
    }

    //重要的ViewRootImpl
    //绘制流程的真正发起点
    ViewRootImpl root;
    View panelParentView = null;

    synchronized (mLock) {
        // ... 省略

        //判断DecorView是否已经添加到Window
        int index = findViewLocked(view, false);
        //如果已经存在该DecorView
        if (index >= 0) {
            //此时它必须是已经被移除Window,在mDyingViews容器
            if (mDyingViews.contains(view)) {
                //如果存在,就将这个已经存在的view对应的window移除
                mRoots.get(index).doDie();
            } else {
                //否则说明已经被添加,不需要再添加
                throw new IllegalStateException("View " + view
                        + " has already been added to the window manager.");
            }
        }

        // ... 省略

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

        view.setLayoutParams(wparams);
        //将DecorView保存在Window中
        mViews.add(view);
        //保存当前ViewRootImpl
        mRoots.add(root);
        mParams.add(wparams);

        try {
            //调用ViewRootImpl的setView
            //参数DecorView
            root.setView(view, wparams, panelParentView);
        } catch (RuntimeException e) {
            // ... 省略
        }
    }
}
  1. 注意 findViewLocked 方法,如果 DecorView 被重复添加将会抛异常。
throw new IllegalStateException("View " + view
                    + " has already been added to the window manager.");
  1. 创建 ViewRootImpl 对象,它最后会与当前 DecorView 进行关联,作为 DecorView 的 parent。ViewRootImpl 是 View 绘制流程的真正发起点。后面会分析到。
  • 在 Android 中,所有的元素都在 Surface 这张画纸上进行绘制和渲染,普通 View(例如非 SurfaceView 或 TextureView) 是没有 Surface 的,一般 Activity 包含多个 View 形成 View Hierachy 的树形结构,只有最顶层的 DecorView 才是对 WindowManagerService “可见的”。而为普通 View 提供 Surface 的正是 ViewRootImpl

保存 DecorView 和对应的 ViewRootImpl 在 WindowManager 中,继续跟踪最后 root.setView(view, wparams, panelParentView) 方法

 public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    synchronized (this) {
        // 判断是否已经关联过View
        if (mView == null) {
            // DecorView赋值到ViewRootImpl中
            mView = view;

            // 获取当前屏幕最新的状态,并且给当前DecorView注册屏幕监听,用来检测灭屏和亮屏
            mAttachInfo.mDisplayState = mDisplay.getState();
            mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);

            // ... 省略

            // 获取屏幕兼容模式,一般情况下不考虑 mTranslator是空的
            CompatibilityInfo compatibilityInfo =
                    mDisplay.getDisplayAdjustments().getCompatibilityInfo();
            mTranslator = compatibilityInfo.getTranslator();

            // If the application owns the surface, don't enable hardware acceleration
            if (mSurfaceHolder == null) {
                enableHardwareAcceleration(attrs);
            }

             // ... 省略

            mAdded = true;
            int res; /* = WindowManagerImpl.ADD_OKAY; */

            // 绘制流程真正开始的地方
            requestLayout();
           
            try {
                mOrigWindowType = mWindowAttributes.type;
                mAttachInfo.mRecomputeGlobalAttributes = true;
                collectViewAttributes();
                // mWindowSession本质是一个Binder,它的实际类型是Session,这里获取到的是远程代理对象
                // 发送跨进程消息,将DecorView显示出来
                // mWindow是ViewRootImpl中的一个静态类,这个类可以用来和WindowSession交互,就等于间接在和WindowManagerService交互
                res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                        getHostVisibility(), mDisplay.getDisplayId(),
                        mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                        mAttachInfo.mOutsets, mInputChannel);
            } catch (RemoteException e) {
                // ... 省略
            } finally {
                if (restore) {
                    attrs.restore();
                }
            }

            // 处理WindowManagerService的返回结果,这里你会看到一些非常熟悉的异常
            // 一大堆儿关于Window操作异常
            if (res < WindowManagerGlobal.ADD_OKAY) {
                mAttachInfo.mRootView = null;
                mAdded = false;
                mFallbackEventHandler.setView(null);
                unscheduleTraversals();
                setAccessibilityFocus(null, null);
                switch (res) {
                    case WindowManagerGlobal.ADD_BAD_APP_TOKEN:
                    case WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN:
                        throw new WindowManager.BadTokenException(
                                "Unable to add window -- token " + attrs.token
                                + " is not valid; is your activity running?");
                    case WindowManagerGlobal.ADD_NOT_APP_TOKEN:
                        throw new WindowManager.BadTokenException(
                                "Unable to add window -- token " + attrs.token
                                + " is not for an application");
                    case WindowManagerGlobal.ADD_APP_EXITING:
                        throw new WindowManager.BadTokenException(
                                "Unable to add window -- app for token " + attrs.token
                                + " is exiting");
                    case WindowManagerGlobal.ADD_DUPLICATE_ADD:
                        throw new WindowManager.BadTokenException(
                                "Unable to add window -- window " + mWindow
                                + " has already been added");
                    case WindowManagerGlobal.ADD_STARTING_NOT_NEEDED:
                        // Silently ignore -- we would have just removed it
                        // right away, anyway.
                        return;
                    case WindowManagerGlobal.ADD_MULTIPLE_SINGLETON:
                        throw new WindowManager.BadTokenException("Unable to add window "
                                + mWindow + " -- another window of type "
                                + mWindowAttributes.type + " already exists");
                    case WindowManagerGlobal.ADD_PERMISSION_DENIED:
                        throw new WindowManager.BadTokenException("Unable to add window "
                                + mWindow + " -- permission denied for window type "
                                + mWindowAttributes.type);
                    case WindowManagerGlobal.ADD_INVALID_DISPLAY:
                        throw new WindowManager.InvalidDisplayException("Unable to add window "
                                + mWindow + " -- the specified display can not be found");
                    case WindowManagerGlobal.ADD_INVALID_TYPE:
                        throw new WindowManager.InvalidDisplayException("Unable to add window "
                                + mWindow + " -- the specified window type "
                                + mWindowAttributes.type + " is not valid");
                }
                throw new RuntimeException(
                        "Unable to add window -- unknown error code " + res);
            }

            // ... 省略

            // 将ViewRootImpl添加到DecorView
            // 这也是后续调用View.requestLayout,最终会调用到ViewRootImpl的requestLayout
            // 调用View的requestLayout会不断的调用parent.requestLayout,实际最终走到ViewRootImpl的requestLayout
            view.assignParent(this);

           // ... 省略
        }
    }
}
  1. requestLayout 绘制流程的真正起始方法:
  // 这个是不是很熟悉
  @Override
  public void requestLayout() {
      if (!mHandlingLayoutInLayoutRequest) {
          //检查操作线程
          //Android中检查UI绘制流程是否在主线程是在ViewRootImpl中
          checkThread();
          mLayoutRequested = true;
          scheduleTraversals();
      }
  }

注意 checkThread(),相信每个 Android 开发人员都曾遇到过在子线程更新 View 操作会抛异常。

void checkThread() {
    // 这就是为什么在子线程对View绘制过程操作会抛异常
    if (mThread != Thread.currentThread()) {
        throw new CalledFromWrongThreadException(
                "Only the original thread that created a view hierarchy can touch its views.");
    }
}

然后 scheduleTraversals 方法发送 Runnable 消息到主线程,完成 View 绘制的三大流程:measure、layout、draw。这部分内容在下节详细介绍。

  1. mWindowSession 本质是一个 Binder 对象,它的实际类型是 Session。每个应用进程都会对应一个 Session。WindowManagerService(WMS) 保存这些 Session 用来记录所有向 WMS 提出窗口管理服务的客户端。
//通过WindowManagerService远程调用为应用进程创建一个Session对象
public IWindowSession openSession(IWindowSessionCallback callback, IInputMethodClient client,
        IInputContext inputContext) {
    if (client == null) throw new IllegalArgumentException("null client");
    if (inputContext == null) throw new IllegalArgumentException("null inputContext");

    //每个应用进程都关联一个Session
    Session session = new Session(this, callback, client, inputContext);
    return session;
}
  1. mWindow 是 ViewRootImpl 静态内部类 W,内部通过弱引用持有当前 ViewRootImpl,它将作为客户端保存在 WMS 中,用来和 WindowSession 交互,实际间接在与 WMS 交互。
//mWindow 是 ViewRootImpl 的静态内部类
//内部持有当前ViewRootImpl
static class W extends IWindow.Stub {
    //弱引用持有当前ViewRootImpl
    //因为它将被保存在WindowManagerService
    private final WeakReference<ViewRootImpl> mViewAncestor;
    private final IWindowSession mWindowSession;

    W(ViewRootImpl viewAncestor) {
        mViewAncestor = new WeakReference<ViewRootImpl>(viewAncestor);
        mWindowSession = viewAncestor.mWindowSession;
    }

     // ... 省略

    /**
     * View 显示状态发生变化
     * 重新执行scheduleTraversals遍历过程
     */
    public void dispatchAppVisibility(boolean visible) {
        final ViewRootImpl viewAncestor = mViewAncestor.get();
        if (viewAncestor != null) {
            viewAncestor.dispatchAppVisibility(visible);
        }
    }


   /**
    * 熟悉的Window焦点发生变化 
    */
    public void windowFocusChanged(boolean hasFocus, boolean inTouchMode) {
        final ViewRootImpl viewAncestor = mViewAncestor.get();
        if (viewAncestor != null) {
            //最终回到View的windowFocusChanged方法
            //和Activity的windowFocusChanged方法
            viewAncestor.windowFocusChanged(hasFocus, inTouchMode);
        }
    }

    // ... 省略
}

简单看下它在 WindowManagerService 中的保存:

//mWindowMap是HashMap
//client是应用进程ViewRootImpl中W(mWindow)静态内部类,这里持有需要窗口服务的代理类
//win是WindowState,保存窗口信息,在WMS用于描述一个窗口
mWindowMap.put(client.asBinder(), win);
  1. 注意看 if ( res < WindowManagerGlobal.ADD_OKAY ),这里是不是有很多眼熟的异常信息,一大堆儿异常扑面而来。相信在你的 Bug 记录中它们肯定出现过。

  2. 方法最后 view.assignParent(this),为什么 view 的 requestLayout 方法,最终会调用到 ViewRootImpl 的 requestLayout 方法。答案就在这里。

void assignParent(ViewParent parent) {
    if (mParent == null) {
        //将ViewRootImpl作为DecorView的parent
        mParent = parent;
    } else if (parent == null) {
        mParent = null;
    } else {
        throw new RuntimeException("view " + this + " being added, but"
                + " it already has a parent");
    }
}

将 ViewRootImpl 作为 DecorView 的 parent。这就是为什么调用 View 的 requestLayout 方法后最终会走到 ViewRootImpl 的 requestLayout 方法

View 的 requestLayout() 会不断调用其 Parent 的 requestLayout(),最后调用到 DecorView 的 Parent,此时 Parent 就是 ViewRootImpl。到了 ViewRootImpl 的 requestLayout() 就会重新发起绘制流程。

至此,我们可以回答文章开头提出的相关问题了。通过一张图来了解下 DecorView 添加至窗口的过程。

image.png
  • View 在 Activity 的绘制时机是在生命周期函数 onResume 之后,真正开始的方法是在 ViewRootImpl 的 requestLayout()。

  • WindowManager 只是一个接口,定义了 Window 操作的基础 API,实现类是 WindowManagerImpl;每个 PhoneWindow 都会关联一个 WindowManagerImpl,实际管理工作又委托给了 WindowManagerGlobal,负责管理应用进程所有 Window(实际是 DecorView)。

  • ViewRootImpl 会作为 DecorView 的 parent,View 的 requestLayout 方法会一直调用 parent 的 requestLayout()。即最终会调用到 ViewRootImpl 的 requestLayout 方法。

  • 在 requestLayout 方法会去检查当前执行 View 绘制的线程,如果不是 Main 线程就会抛出异常。


以上便是个人在学习 DecorView 添加至 Window 过程的学习和体会,文中如有不妥或有更好的分析结果,欢迎大家指出。

文章如果对你有帮助,请留个赞吧!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容