Android 11源码分析:从Activity的setContent方法看渲染流程(2)初识Window

1,404 阅读7分钟

这是我参与8月更文挑战的第8天,活动详情查看:8月更文挑战

从Activity的setContent方法看渲染流程(1)Activity,Window,View的关系

从Activity的setContent方法看渲染流程(2)初识Window

从Activity的setContent方法看渲染流程(3)再看Window

从Activity的setContent方法看渲染流程(番外篇)AppCompatActivity的setContent

在上一篇的分析中,我们已经知道DecorView以下的部分弄的很明白了,但是对于DecorView是如何显示在我们的屏幕上还是不太清楚。所以接着分析DecorView与PhoneWindow与Activity具体是如何建立联系的。 我们先弄清楚两个问题:

  1. DecorView何时绘制到屏幕中
  2. DecorView如何绘制到屏幕中(addView,removeView,upDateViewLayout)
  3. Activity是如何得到Touch事件的

DecorView何时绘制到屏幕中

Activity的启动流程中提到执行Activity的performLaunchActivity方法执行的时,先是通过反射创建了Activity,然后会调用Activity的attach方法,在上一篇我们知道attach方法做了PhoneWindow的初始化操作,再然后才是执行生命周期onCreate,而setContent方法又是在onCreate执行后,看起来一切都很合理。

先创建一个Activity,然后再为这个Activity创建一个PhoneWindow,执行onCreate,最后再把我们写的XML设置进去。 一切都准备就绪,但是我们到目前还是看不到界面,因为我们知道,Activity真正可见是在执行onResume的时候。所以对于DecorView何时绘制到屏幕中问题,其实答案已经出来了,接下来需要去代码里进行验证。

看过我之前文章的人知道找Activity不同生命周期具体执行代码要去TransactionExecutor里看performLifecycleSequence方法。

Activity的onRemune具体执行是在Activity的handleResumeActivity方法中,我们去看看这里是怎么处理PhoneWindow的。

android.app.ActivityThread

@Override
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
        String reason) {
            ......
            // TODO Push resumeArgs into the activity for consideration
            performResumeActivity内会触发Activity的onResume生命周期
            final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
            ......
            r.window = r.activity.getWindow();
            //获取到当前PhoneWindow的DecorView
            View decor = r.window.getDecorView();
            decor.setVisibility(View.INVISIBLE);
            ViewManager wm = a.getWindowManager();
            ......
            关键代码
            wm.addView(decor, l);
            ......
}

关键代码只有简单的一句wm.addView(decor, l);

找出WindowManagerImpl

这个wm是ViewManager类型,然后获取他的方法是Activity的WindowManager。先来搞清楚一下WindowManager和ViewManager是什么关系。

假装分析一波,能用ViewManager去接收WindowManager,说明WindowManager要么是ViewManager的子类,要么是实现了ViewManager接口。而且会使用wm.addView。就说明这个addView方法ViewManager里被定义了,在WindowManager中没有,否则不会这么去写。

其实也不用想这么多,各自点进去看看就知道了。

android.view.ViewManager

public interface ViewManager
{
    public void addView(View view, ViewGroup.LayoutParams params);
    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
    public void removeView(View view);
}

android.view.WindowManager
@SystemService(Context.WINDOW_SERVICE)
public interface WindowManager extends ViewManager {
   ......
}

ViewManager是个接口,里面虽然只有三个方法,但是我们却非常熟悉,这三个方法的重要性就不用多说了。

让我没想到的是WindowManager居然也是个接口......那addView具体的实现在哪呢。只能去getWindowManager具体找找了。

android.app.Activity
......
 private WindowManager mWindowManager;
 ......
public WindowManager getWindowManager() {
   return mWindowManager;
}

而的赋值操作在attach里

   final void attach(......){
        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        mWindow.setWindowControllerCallback(mWindowControllerCallback);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
        ......
       给PhoneWindow设置一个WindowManager 
       mWindow.setWindowManager((WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
        if (mParent != null) {
            mWindow.setContainer(mParent.getWindow());
        }
        取出WindowManager来赋值给mWindowManager
        mWindowManager = mWindow.getWindowManager();
   }

所以想知道mWindowManager到底是啥还得看mWindow.getWindowManager。而mWindow.getWindowManager返回的又是上面的一行mWindow.setWindowManager,所以去setWindowManager看看吧

android.view.Window

    ......
    private WindowManager mWindowManager;
    ......
    public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
    boolean hardwareAccelerated) {
        mAppToken = appToken;
        mAppName = appName;
        mHardwareAccelerated = hardwareAccelerated;
        if (wm == null) {
            wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
        }
        关键代码
        mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
    }
    ......
    public WindowManager getWindowManager() {
        return mWindowManager;
    }

真相浮出水面

mWindowManager的真实对象是WindowManagerImpl

addView

这一小节我们来看看Window的添加过程

找了这么久我们就是为了弄明白wm.addView(decor, l);的wm到底是个啥。下面去看看addView到底把我们的DecorView怎么了。

android.view.WindowManagerImpl

public final class WindowManagerImpl implements WindowManager {
    @UnsupportedAppUsage
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
    ......
    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
                mContext.getUserId());
    }
    ......
}

WindowManagerImpl居然又是个空壳,大部分操作都给了单例类WindowManagerGlobal去处理,

android.view.WindowManagerGlobal
    
    private final ArrayList<View> mViews = new ArrayList<View>();
   
    private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
    
    private final ArrayList<WindowManager.LayoutParams> mParams =
            new ArrayList<WindowManager.LayoutParams>();
    private final ArraySet<View> mDyingViews = new ArraySet<View>();
    
    public void addView(View view, ViewGroup.LayoutParams params,Display display,
    Window parentWindow, int userId) {
    ......
     ViewRootImpl root;
     View panelParentView = null;
     synchronized (mLock) {
            进行参数检查 
            if (view == null) {
            throw new IllegalArgumentException("view must not be null");
            }
            if (display == null) {
                throw new IllegalArgumentException("display must not be null");
            }
            if (!(params instanceof WindowManager.LayoutParams)) {
                throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
            }

            final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
            如果是子Windowh还需要进行布局参数的调整
            if (parentWindow != null) {
                parentWindow.adjustLayoutParamsForSubWindow(wparams);
            }       
            ......
            创建ViewRootImpl
            root = new ViewRootImpl(view.getContext(), display);

            view.setLayoutParams(wparams);

            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);

            // do this last because it fires off messages to start doing things
            try {
                //把DecorView交给ViewRootImpl
                root.setView(view, wparams, panelParentView, userId);
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
      }
   }

首先我们看到WindowManagerGlobal维护了4个集合

  • mViews :存储了所有Window所对应的View

  • mRoots :存储的是所以Window所对应的ViewRootImpl

  • mParams:存储的是所有Window所对应的布局参数

  • mDyingViews:存储的是那些正在被删除的View

在addView中后面的关键代码创建了ViewRootImpl,并将我们的DecorView交给了它。

android.view.ViewRootImpl

   public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
   int userId) {
        synchronized (this) {
            if (mView == null) {
                mView = view;
                }
            ......
            触发一次屏幕刷新
            requestLayout();
        try {
            将最后的操作给mWindowSession
            res = mWindowSession.addToDisplayAsUser(mWindow, mSeq, mWindowAttributes,
                    getHostVisibility(), mDisplay.getDisplayId(), userId, mTmpFrame,
                    mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                    mAttachInfo.mDisplayCutout, inputChannel,
                    mTempInsets, mTempControls);
            setFrame(mTmpFrame);
        } ......
       // Set up the input pipeline.
       输入事件处理
       CharSequence counterSuffix = attrs.getTitle();
       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);        
        ......
   }
    @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }

setView方法先是做了个刷新布局的操作,内部执行的scheduleTraversals() 就是View绘制的入口,调用此方法后 ViewRootImpl 所关联的 View 也执行 measure - layout - draw 操作,确保在 View 被添加到 Window 上显示到屏幕之前,已经完成测量和绘制操作。至此由ViewRootImpl完成了添加View到Window的操作。 然后将调用mWindowSession的addToDisplayAsUser方法来完成Window的添加过程,内部真实执行的地方在WMS(WindowManagerService)

View的事件如何反馈到Activity

对于后面的输入事件处理的代码我也不是很明白,但是之前好像在哪里看过,所有对于利用管道和系统底层通信的机制有点印象。 这一块的简单的理解就是:这里是设置了一系列的输入通道。因为一个触屏事件的发生是肯定是由屏幕发起,再经过驱动层一系列的计算处理最后通过 Socket 跨进程通知 Android Framework 层。 其实看到这一块我一开始是不打算继续深入了,因为的也知道我的能力差不多就这了,但是抱着好奇的心点了进去想看看ViewPostImeInputStage是个啥。结果却有了意想不到的收获。

android.view.ViewRootImpl
   final class ViewPostImeInputStage extends InputStage {
        public ViewPostImeInputStage(InputStage next) {
            super(next);
        }

        @Override
        protected int onProcess(QueuedInputEvent q) {
            if (q.mEvent instanceof KeyEvent) {
                return processKeyEvent(q);
            } else {
                final int source = q.mEvent.getSource();
                if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
                    只看这里,其实下面的两个都是类似
                    return processPointerEvent(q);
                } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
                    return processTrackballEvent(q);
                } else {
                    return processGenericMotionEvent(q);
                }
            }
        }
       }
private int processPointerEvent(QueuedInputEvent q) {
    final MotionEvent event = (MotionEvent)q.mEvent;

    mAttachInfo.mUnbufferedDispatchRequested = false;
    mAttachInfo.mHandlingPointerEvent = true;
    boolean handled = mView.dispatchPointerEvent(event);
    ......
    return handled ? FINISH_HANDLED : FORWARD;
}

注意一下参数的传递就知道这个mView其实就是一开始我们DecorView。但是dispatchPointerEvent是在View定义的。

android.view.View
    public final boolean dispatchPointerEvent(MotionEvent event) {
        if (event.isTouchEvent()) {
            return dispatchTouchEvent(event);
        } else {
            return dispatchGenericMotionEvent(event);
        }
    }

返回了dispatchTouchEvent和dispatchGenericMotionEvent的执行。所以去DecorView看看

com.android.internal.policy.DecorView

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        final Window.Callback cb = mWindow.getCallback();
        return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
                ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean dispatchTrackballEvent(MotionEvent ev) {
        final Window.Callback cb = mWindow.getCallback();
        return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
                ? cb.dispatchTrackballEvent(ev) : super.dispatchTrackballEvent(ev);
    }

    @Override
    public boolean dispatchGenericMotionEvent(MotionEvent ev) {
        final Window.Callback cb = mWindow.getCallback();
        return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
                ? cb.dispatchGenericMotionEvent(ev) : super.dispatchGenericMotionEvent(ev);
    }

我们发现不管是哪个方法,最后都是从mWindow获取一个回调cb,然后把事回调出去。不知道读者对之前在Activity的attach里的时候的代码 mWindow.setCallback(this);还有没有印象,方法接收的参数类型是Window内部的一个接口Callback而我们的Activity实现了这个接口。所以mWindow.setCallback就是把Activity设置了进去,所以这个cb就是我们的Activity,所以DecorView的事件,就这样传递给了Activity

removeView

android.view.WindowManagerGlobal

   @UnsupportedAppUsage
    public void removeView(View view, boolean immediate) {
        if (view == null) {
            throw new IllegalArgumentException("view must not be null");
        }

        synchronized (mLock) {
            找到需要删除View的索引
            int index = findViewLocked(view, true);
            View curView = mRoots.get(index).getView();
            真的执行删除操作
            removeViewLocked(index, immediate);
            if (curView == view) {
                return;
            }

            throw new IllegalStateException("Calling with view " + view
                    + " but the ViewAncestor is attached to " + curView);
        }
    }

Window的删除过程页是在WindowManagerGlobal中。先是找到需要删除View的索引,然后传递到removeViewLocked方法里。

   private void removeViewLocked(int index, boolean immediate) {
        ViewRootImpl root = mRoots.get(index);
        View view = root.getView();

        if (root != null) {
            root.getImeFocusController().onWindowDismissed();
        }
        boolean deferred = root.die(immediate);
        if (view != null) {
            view.assignParent(null);
            if (deferred) {
                mDyingViews.add(view);
            }
        }
    }

removeViewLocked内部也是通过ViewRootImpl來完成刪除操作的。 在removeView方法里我们就看到了mRoots.get(index).getView(),里面又有View view = root.getView();ViewRootImpl的getView返回的View其实就是上面setView方法里,传进来的我们的顶层视图DecorView。

看看ViewRootImpl的die方法

Params:immediate – True, do now if not in traversal. False, put on queue and do later.
Returns:True, request has been queued. False, request has been completed.
boolean die(boolean immediate) {
        // Make sure we do execute immediately if we are in the middle of a traversal or the damage
        // done by dispatchDetachedFromWindow will cause havoc on return.
        if (immediate && !mIsInTraversal) {
            doDie();
            return false;
        }

        if (!mIsDrawing) {
            destroyHardwareRenderer();
        } else {
            Log.e(mTag, "Attempting to destroy the window while drawing!\n" +
                    "  window=" + this + ", title=" + mWindowAttributes.getTitle());
        }
        mHandler.sendEmptyMessage(MSG_DIE);
        return true;
    }

参数immediates表示是否立即删除,返回false表示删除已完成,返回true表示加入待删除的队列里。看removeViewLocked的最后,将返回true加入了待删除的列表mDyingViews就明白了。 如果是需要立即删除则执行doDie,如果是是异步删除,则发送个消息,ViewRootImpl内部Handle接收消息的处理还是执行doDie。

现在看来doDie才是真正执行删除操作的地方。

void doDie() {
        checkThread();
        if (LOCAL_LOGV) Log.v(mTag, "DIE in " + this + " of " + mSurface);
        synchronized (this) {
            if (mRemoved) {
                return;
            }
            mRemoved = true;
            if (mAdded) {
                关键代码
                dispatchDetachedFromWindow();
            }
        ......
        全局单例WindowManagerGlobal也执行对应方法
        WindowManagerGlobal.getInstance().doRemoveView(this);
    }
void dispatchDetachedFromWindow() {
    内部完成视图的移除
    mView.dispatchDetachedFromWindow();
     try {
 内部通过WMA完成Window的删除
       mWindowSession.remove(mWindow);
      } catch (RemoteException e) {}
}

在dispatchDetachedFromWindow方法内部通过View的dispatchDetachedFromWindow方法完成View的删除,同时在通过mWindowSession来完成Window的删除

updateViewLayout

android.view.WindowManagerGlobal
    public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
        if (view == null) {
            throw new IllegalArgumentException("view must not be null");
        }
        if (!(params instanceof WindowManager.LayoutParams)) {
            throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
        }

        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
        更新View参数
        view.setLayoutParams(wparams);

        synchronized (mLock) {
            int index = findViewLocked(view, true);
            ViewRootImpl root = mRoots.get(index);
            mParams.remove(index);
            mParams.add(index, wparams);
            更新ViewRootImpl参数
            root.setLayoutParams(wparams, false);
        }
    }

updateViewLayout内部做的事情比较简单,先是更新View的参数,然后更新ViewRootImpl的参数。

  • setLayoutParams内部会触发scheduleTraversals来对View重新布局,scheduleTraversals一旦触发,就会执行relayoutWindow方法,触发WMS来更新Window视图。我就不贴代码了,太多了,附上调用路径吧。
  • scheduleTraversals->mTraversalRunnable(TraversalRunnable)->doTraversal->performTraversals->relayoutWindow->内部执行mWindowSession.relayout通知WMS更新Window

总结

  1. DecorView在Activity的handleResumeActivity方法执行通过wm.addView完成操作。执行时机是在onResume后面一点点
  2. DecorView存在于PhoneWindow中,DecorView的添加删除更新操作是由ViewRootImpl负责,而PhoneWindow的添加删除更新操作由WMS负责
  3. PhoneWindow的事件通过Callback接口回调给Activity

准确的说DecorView的添加删除更新操作是由ViewRootImpl负责是不够严谨的,因为DecorView也是View,ViewRootImpl负责View添加更新移除操作,然后内部会通过mWindowSession与WMS通信,完成Window的添加更新移除操作