一文掌握 Android 视图绑定的原理,剖析 Window、Activity、View 三者关系与屏幕绘制流程

每一个 Android 开发相信都对 Activity 极为熟悉,当我们操作界面元素的时候,是否探究过其深层原理,之前的文章我们或多或少的了解了 Android 中三大核心服务中的两个 AMS 和 PMS,今天我们从 Activity 的 setContentView 方法触发,了解 Android 视图绑定的原理,判断 Window、Activity、View 三者关系,从而也了解一下最后一个核心服务 WindowManagerService(WMS)。

Activity 的 setContentView

Activity 是 Android 开发人员使用最频繁的 API 之一,在其 onCreate 方法中,一般我们都会使用 setContentView 方法来绑定视图关系,那么我们从 Activity 的 setContentView 看起:

less 复制代码
// AppCompatActivity
@Override
public void setContentView(@LayoutRes int layoutResID) {
    initViewTreeOwners();
    // 代理方法去执行
    getDelegate().setContentView(layoutResID);
}
// Activity
public void setContentView(@LayoutRes int layoutResID) {
    getWindow().setContentView(layoutResID);
    initWindowDecorActionBar();
}

可以看到不管是 AppCompatActivity 的 setContentView 还是 Activity 的 setContentView 方法,最后都是调用 mWindow 去处理的,它是 Window 窗口类型。那我们看看它是什么时候被创建出来的?

之前分析 startActivity 的过程,最终代码会调用到 ActivityThread 中的 performLaunchActivity 方法,通过反射创建 Activity 对象,并执行其 attach 方法。Window 就是在这个方法中被创建,是一个 PhoneWindow 类型

scss 复制代码
    @UnsupportedAppUsage
    final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken) {
        attachBaseContext(context);

        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        ...
        mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
    }

可以看到贯穿 Activity 始终的 mWindow 就是在这被创建出来的一个 PhoneWindow 对象。

之后 mWindow 调用 setWindowManager 方法,将系统 WindowManager 传给 PhoneWindow:

typescript 复制代码
    public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
            boolean hardwareAccelerated) {
        ...
        mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
    }

    public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
        return new WindowManagerImpl(mContext, parentWindow);
    }

最终,在 PhoneWindow 中持有了一个 WindowManagerImpl 的引用 mWindowManager ,他就是 WindowManager 的一个代理类,主要负责窗口的创建、显示、隐藏、删除等操作。

PhoneWindow 的 setContentView

根据上文发现 Activity 将 setContentView 的操作交给了 PhoneWindow,所以我们来看下 PhoneWindow 的 setContentView 方法:

scss 复制代码
    public void setContentView(int layoutResID) {
        if (mContentParent == null) {
            // 调用 installDecor 初始化 DecorView 和 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 {
            // 调用 setContentView 传入的布局添加到 mContentParent 中
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        ...
    }

    private void installDecor() {
        if (mDecor == null) {
            // 初始化 DecorView
            mDecor = generateDecor(-1);
        } else {
            mDecor.setWindow(this);
        }
        ...
        if (mContentParent == null) {
            // 初始化 mContentParent
            mContentParent = generateLayout(mDecor);
        }
    }

在 installDecor 方法内,PhoneWindow 初始化了两个重要的对象 DecorView 和 mContentParent,这两个分别是 Android 中两个重要的视图 FrameLayout 和 ViewGroup。

当我们将自己实现的布局添加到 Activity 中时,实际上是被添加到 mContentParent 中的,因此经过 setContentView 之后,PhoneWindow 内部的 View 关系如下所示:

markdown 复制代码
PhoneWindow
● DecorView(FrameLayout)
  ○ mContentParent (ViewGroup)
    ■ layoutId (View)

可以看到在 Activity 的 onCreate 中,PhoneWindow 已经持有了 DecorView 对象,但是它和 Activity 的关联关系还未建立,也没有被绘制到界面上显示。那 DecorView 是何时被绘制到屏幕上的呢?

我们知道在 Activity 执行完 onResume 之后其中的内容才是屏幕可见状态。造成这种现象的原因就是,onCreate 阶段只是初始化了 Activity 需要显示的内容,而在 onResume 阶段才会将 PhoneWindow 中的 DecorView 真正的绘制到屏幕上。

在 ActivityThread 的 handleResumeActivity 中,会调用 WindowManager 的 addView 方法将 DecorView 添加到 WindowManagerService(WMS) 上,如下所示:

ini 复制代码
    public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
            String reason) {
        ...
        if (r.window == null && !a.mFinished && willBeVisible) {
            ViewManager wm = a.getWindowManager();
            if (a.mVisibleFromClient) {
                if (!a.mWindowAdded) {
                    a.mWindowAdded = true;
                    wm.addView(decor, l);
                }
            }
        }
        ...
    }

执行完 WindowManger 的 addView 方法后,DecorView 就被渲染绘制到屏幕上显示,可以接收屏幕触摸事件。

WindowManager 的 addView

PhoneWindow 只是负责处理一些应用窗口通用的逻辑(设置标题栏,导航栏等)。但是真正完成把一个 View 作为窗口添加到 WMS 的过程是由 WindowManager 来完成的。

WindowManager 是接口类型,上文中我们也了解到它真正的实现者是相应的代理类,看一下 WindowManagerImpl 的 addView 方法如下:

less 复制代码
    // WindowManagerImpl
    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
                mContext.getUserId());
    }

    // WindowManagerGlobal
    public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow, int userId) {
        ...
        ViewRootImpl root;
        View panelParentView = null;
        synchronized (mLock) {
            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 {
                root.setView(view, wparams, panelParentView, userId);
            } catch (RuntimeException e) {
                ...
            }
        }
    }

可以看出 WindowManagerImpl 的 addView 实际上是调用了 WindowManagerGlobal 的 addView 方法。WindowMangerGlobal 是一个单例。如源码中所示,在其 addView 方法中,创建了一个最关键的根视图的代理对象 ViewRootImpl,然后通过它的 setView 方法将 view 添加到 WMS 中。

ViewRootImpl 的 setView

scss 复制代码
    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
            int userId) {
        synchronized (this) {
            if (mView == null) {
                ...
                // view 刷新布局,视图的绘制流程方法
                requestLayout();
                InputChannel inputChannel = null;
                if ((mWindowAttributes.inputFeatures
                        & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                    // 创建了 InputChannel 处理输入事件的关键组件
                    inputChannel = new InputChannel();
                }
                mForceDecorViewVisibility = (mWindowAttributes.privateFlags
                        & PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY) != 0;
                try {
                    res = mWindowSession.addToDisplayAsUser(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(), userId, mTmpFrame,
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mDisplayCutout, inputChannel,
                            mTempInsets, mTempControls);
                    setFrame(mTmpFrame);
                } catch (RemoteException e) {
                    ...
                }
            }
        }
    }

看到 requestLayout 方法相信大家都不陌生,View 刷新布局的方法,调用此方法后 ViewRootImpl 所关联的 View 也执行 measure - layout - draw 操作,确保在 View 被添加到 Window 上显示到屏幕之前,已经完成测量和绘制操作。

之后 mWindowSession 对象调用 addToDisplay 方法将 View 添加到 WMS 中。可以看到 mWindowSession 对象是 IWindowSession 类型,在 ViewRootImpl 的构造方法里面,根据 WindowManagerGlobal.getWindowSession() 创建出来,是 WindowManagerGlobal 中的单例对象,初始化代码如下:

java 复制代码
    public static IWindowSession getWindowSession() {
        synchronized (WindowManagerGlobal.class) {
            if (sWindowSession == null) {
                try {
                    InputMethodManager.ensureDefaultInstanceForDefaultDisplayIfNecessary();
                    IWindowManager windowManager = getWindowManagerService();
                    sWindowSession = windowManager.openSession(
                            new IWindowSessionCallback.Stub() {
                                @Override
                                public void onAnimatorScaleChanged(float scale) {
                                    ValueAnimator.setDurationScale(scale);
                                }
                            });
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
            }
            return sWindowSession;
        }
    }

根据源码,可以发现 sWindowSession 真正的实现类是 System 进程中的 Session。就是用 AIDL 获取 System 进程中 Session 的对象。

其 addToDisplay 方法如下:

java 复制代码
    @Override
    public int addToDisplayAsUser(IWindow window, int seq, WindowManager.LayoutParams attrs,
            int viewVisibility, int displayId, int userId, Rect outFrame,
            Rect outContentInsets, Rect outStableInsets,
            DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel,
            InsetsState outInsetsState, InsetsSourceControl[] outActiveControls) {
        return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame,
                outContentInsets, outStableInsets, outDisplayCutout, outInputChannel,
                outInsetsState, outActiveControls, userId);
    }

里面的 mService 就是 WMS。至此,Window 已经成功的被传递给了 WMS。剩下的工作就全部转移到系统进程中的 WMS 来完成最终的添加操作。

ViewRootImpl 屏幕绘制过程

在上面的源码分析中,我们主要看了一下 ViewRootImpl 的 setView 方法中对 Window 的走向流程的源码,但是在 setView 方法中还有一个非常重要的方法就是

ViewRootImpl requestLayout 流程

scss 复制代码
    @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            // 检查是否为合法线程,一般情况下就是检查是否为主线程。
            checkThread();
            // 请求布局标识符设置为 true,这个参数决定了后续是否需要执行 measure 和 layout 操作。
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }

可以看到在执行 scheduleTraversals 方法前,ViewRootImpl 主要进行了主线程的检测和标志位的设置,方便后续操作

ini 复制代码
    @UnsupportedAppUsage
    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            ...
        }
    }

在这主要是调用 Choreographer 的 postCallback 方法,实际上是发送一个 Message 到主线程消息队列。

ini 复制代码
    private void postCallbackDelayedInternal(int callbackType,
            Object action, Object token, long delayMillis) {
        ...
        synchronized (mLock) {
            ...
            if (dueTime <= now) {
                scheduleFrameLocked(now);
            } else {
                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
                msg.arg1 = callbackType;
                msg.setAsynchronous(true);
                mHandler.sendMessageAtTime(msg, dueTime);
            }
        }
    }

看到 msg.setAsynchronous(true) 就说明刚刚通过 Handler 发送到 MessageQueue 中的 Message 被设置为异步类型的消息,目的是通过调用此方法,保证 UI 绘制操作优先执行。传递过来的 Runnable 是一个实现 Runnable 接口的 TraversalRunnable 类型对象,看下他的 run 方法:

typescript 复制代码
final class TraversalRunnable implements Runnable {
    @Override
    public void run() {
        doTraversal();
    }
}
void doTraversal() {
    if (mTraversalScheduled) {
        ...
        performTraversals();
        ...
    }
}

发现其实执行的是

ViewRootImpl 的 performTraversals 方法

这个方法点击去,发现非常长,核心处理的是:

scss 复制代码
private void performTraversals() {
    // Ask host how big it wants to be
    windowSizeMayChange |= measureHierarchy(host, lp, res,
        desiredWindowWidth, desiredWindowHeight);
    ...
    performLayout(lp, mWidth, mHeight);
    ...
    performDraw();    
}

可以看到其中实现的就是这三个核心方法 measure --> layout --> draw

先来看下实现 measure 的 measureHierarchy 方法

ViewRootImpl 的 measureHierarchy

arduino 复制代码
    private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,
            final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {
        int childWidthMeasureSpec;
        int childHeightMeasureSpec;
        boolean windowSizeMayChange = false;
        if (!goodMeasure) {
            childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
            childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
            
            performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
            if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) {
                windowSizeMayChange = true;
            }
        }
        return windowSizeMayChange;
    }

在 measureHierarchy 方法中通过 getRootMeasureSpec 方法获取了根 View的MeasureSpec,实际上 MeasureSpec 中的宽高此处获取的值是 Window 的宽高。

之后是调用 performMeasure 方法来进行测量工作的,所以我们直接看 performMeasure 方法的实现

ViewRootImpl 的 performMeasure

csharp 复制代码
    private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
        if (mView == null) {
            return;
        }
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
        try {
            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }

这个方法很简单,只是执行了 mView 的 measure 方法,这个 mView 就是 DecorVIew。其 DecorView 的 measure 方法中,会调用 onMeasure 方法,而 DecorView 是继承自 FrameLayout 的,因此最终会执行 FrameLayout 中的 onMeasure 方法,并递归调用子 View 的 onMeasure 方法。所以说 View 的测量是一层递归的调用实现的。

点击 performLayout 方法,可以看到最终也是执行了 view 的 layout 方法。

ViewRootImpl 的 performDraw

typescript 复制代码
private void performDraw() {
    ...
    try {
        boolean canUseAsync = draw(fullRedrawNeeded);
        ...
    }
}

可以看出,在 performDraw 方法中,调用的 ViewRootImpl 的 draw 方法。在 draw 方法中进行 UI 绘制操作。

java 复制代码
    private boolean draw(boolean fullRedrawNeeded) {
        Surface surface = mSurface;
        ...
        if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
            if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {
                ...
                // 硬件加速绘制
                mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
            } else {
                // 软件绘制
                if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
                        scalingRequired, dirty, surfaceInsets)) {
                    return false;
                }
            }
        }rn useAsyncReport;
    }

在这可以看出 Android 系统提供了 2 种绘制方式,硬件加速绘制和软件绘制。

Android 的硬件加速绘制主要依赖 GPU 来处理图形的渲染,而软件绘制则主要依赖 CPU 进行图形的渲染。

在这我们主要分析软件绘制流程。

ViewRootImpl 的 drawSoftware

java 复制代码
    private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
            boolean scalingRequired, Rect dirty, Rect surfaceInsets) {
        ...
        try {
            ...
            mView.draw(canvas);
        } finally {
            try {
                surface.unlockCanvasAndPost(canvas);
            } catch (IllegalArgumentException e) {
                return false;
            }
        }
        return true;
    }

里面核心就是调用 DecorView 的 draw 方法将 UI 元素绘制到画布 Canvas 对象中。然后使用 Surface 请求将 Canvas 中的内容显示到屏幕上,实际上就是将 Canvas 中的内容提交给 SurfaceFlinger 进行合成处理。

进入 DecorView 的 draw 方法,可以发现它并没有复写 draw 方法,而是调用的顶层 View 的 draw 方法:

scss 复制代码
    public void draw(Canvas canvas) {

        // Step 1, draw the background, if needed
        int saveCount;
        drawBackground(canvas);

        // skip step 2 & 5 if possible (common case)
        final int viewFlags = mViewFlags;
        boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
        boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
        if (!verticalEdges && !horizontalEdges) {
            // Step 3, draw the content
            onDraw(canvas);

            // Step 4, draw the children
            dispatchDraw(canvas);

            drawAutofilledHighlight(canvas);

            // Overlay is part of the content and draws beneath Foreground
            if (mOverlay != null && !mOverlay.isEmpty()) {
                mOverlay.getOverlayView().dispatchDraw(canvas);
            }

            // Step 6, draw decorations (foreground, scrollbars)
            onDrawForeground(canvas);

            // Step 7, draw the default focus highlight
            drawDefaultFocusHighlight(canvas);

            if (isShowingLayoutBounds()) {
                debugDrawFocus(canvas);
            }

            // we're done...
            return;
        }

        // Step 2, save the canvas' layers

        // Step 3, draw the content
        onDraw(canvas);

        // Step 4, draw the children
        dispatchDraw(canvas);

        // Step 5, draw the fade effect and restore layers

        // Step 6, draw decorations (foreground, scrollbars)
        onDrawForeground(canvas);

        // Step 7, draw the default focus highlight
        drawDefaultFocusHighlight(canvas);
    }

draw 方法是 Android 源码中为数不多的写的很仔细注释的方法,可以看到他已经把里面的步骤解释的清清楚楚。

  1. Draw the background
    1. 绘制 View 的背景
  1. If necessary, save the canvas' layers to prepare for fading
    1. 保存画布的图层信息
  1. Draw view's content
    1. 绘制 View 自身内容
  1. Draw children
    1. 对 draw 事件进行分发,在 View 中是空实现,实际调用的是 ViewGroup 中的实现,并递归调用子 View 的 draw 事件
  1. If necessary, draw the fading edges and restore layers
  2. Draw decorations (scrollbars for instance)
  3. If necessary, draw the default focus highlight

Activity 设置输入事件

上面我们说到当执行完 WindowManger 的 addView 方法后 DecorView 除了被渲染绘制到屏幕上显示,还有一个就是能够接受输入事件了。我们知道当触屏事件发生之后,Touch 事件首先是被传入到 Activity,然后才被下发到布局中的 ViewGroup 或者 View。那么 Touch 事件是如何传递到 Activity 上的呢?

ViewRootImpl 中的 setView 方法中,除了调用 IWindowSession 执行跨进程添加 View 之外,还有一项重要的操作就是设置输入事件的处理:

ini 复制代码
    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
            int userId) {
        synchronized (this) {
            if (mView == null) {
                ...
                requestLayout();
                ...
                try {
                    res = mWindowSession.addToDisplayAsUser(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(), userId, mTmpFrame,
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mDisplayCutout, inputChannel,
                            mTempInsets, mTempControls);
                } catch (RemoteException e) {
                    ...
                }
                // 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);
            }
        }
    }

可以看到在 ViewRootImpl 的 setView 方法中,执行完刷新布局的方法后,还设置了一系列的输入通道,都是继承 InputStage 的实现子类。

当一个触屏事件的发生是由屏幕发起,然后经过驱动层一系列的优化计算通过 Socket 跨进程通知 Android Framework 层(实际上就是 WMS),最终屏幕的触摸事件会被发送到上图中的输入管道中。

这些输入管道实际上是一个链表结构,当某一个屏幕触摸事件到达其中的 ViewPostImeInputState 时,会经过 onProcess 来处理,如下所示:

scala 复制代码
    final class ViewPostImeInputStage extends InputStage {
        @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) {
        ...
        boolean handled = mView.dispatchPointerEvent(event);
        ...
    }

可以看到在 onProcess 中最终调用了一个 mView 的 dispatchPointerEvent 方法,mView 实际上就是 PhoneWindow 中的 DecorView,而在 dispatchPointerEvent 方法内跟踪,发现最终调用了 PhoneWindow 中 Callback 的 dispatchTouchEvent 方法

java 复制代码
    @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);
    }

可以看出,最后的执行是通过 Window.Callback 来执行 dispatchTouchEvent 方法的,那这个 Callback 跟 Activity 有什么关系呢?之前在启动 Activity 阶段,创建 Activity 对象并调用 attach 方法时,我们发现 mWindow 创建出来,设置了一个 mWindow.setCallback(this); 这个方法也就说明 Activity 将自身传递给了 PhoneWindow,再接着看 Activity 的 dispatchTouchEvent 方法:

typescript 复制代码
// Activity
public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction();
    }
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    return onTouchEvent(ev);
}
// PhoneWindow
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
    return mDecor.superDispatchTouchEvent(event);
}

可以看出 Touch 事件最后还是回到了 PhoneWindow 中的 DecorView 来处理。剩下的就是从 DecorView 开始将事件层层传递给内部的子 View 中了。

总结

本章主要通过 Android 视图绑定的流程,分析了 Activity、Window、View 之间的关系和 View 进行渲染的简单流程。整个过程 Activity 表面上参与度比较低,大部分 View 的添加操作都被封装到 Window 中实现。而 Activity 就相当于 Android 提供给开发人员的一个管理类,通过它能够更简单的实现 Window 和 View 的操作逻辑。

相关推荐
秋月霜风12 分钟前
mariadb主从配置步骤
android·adb·mariadb
Python私教1 小时前
Python ORM 框架 SQLModel 快速入门教程
android·java·python
编程乐学2 小时前
基于Android Studio 蜜雪冰城(奶茶饮品点餐)—原创
android·gitee·android studio·大作业·安卓课设·奶茶点餐
problc3 小时前
Android中的引用类型:Weak Reference, Soft Reference, Phantom Reference 和 WeakHashMap
android
IH_LZH3 小时前
Broadcast:Android中实现组件及进程间通信
android·java·android studio·broadcast
去看全世界的云3 小时前
【Android】Handler用法及原理解析
android·java
机器之心4 小时前
o1 带火的 CoT 到底行不行?新论文引发了论战
android·人工智能
机器之心4 小时前
从架构、工艺到能效表现,全面了解 LLM 硬件加速,这篇综述就够了
android·人工智能
AntDreamer4 小时前
在实际开发中,如何根据项目需求调整 RecyclerView 的缓存策略?
android·java·缓存·面试·性能优化·kotlin
运维Z叔6 小时前
云安全 | AWS S3存储桶安全设计缺陷分析
android·网络·网络协议·tcp/ip·安全·云计算·aws