Android—Window、WindowManage、屏幕绘制及刷新

Activity窗口层级: 

在这里插入图片描述

所以在onCreate方法体中setContentView方法都是设置DecorView的ContentView。 

Window、PhoneWindow、DecorView的关系:

public abstract class Window {
    ...
    @Nullable
    public View findViewById(@IdRes int id) {
        return getDecorView().findViewById(id);
    }

    public abstract void setContentView(@LayoutRes int layoutResID);
    ...
}

Window是一个抽象基类,它提供了一系列窗口的方法,比如设置背景,标题等等,而它的唯一实现类则是PhoneWindow

public class PhoneWindow extends Window implements MenuBuilder.Callback {

    private final static String TAG = "PhoneWindow";

    ...

    // This is the top-level view of the window, containing the window decor.
    private DecorView mDecor;

    // This is the view in which the window contents are placed. It is either
    // mDecor itself, or a child of mDecor where the contents go.
    private ViewGroup mContentParent;

    private ViewGroup mContentRoot;
    ...
}

在PhoneWindow里面,出现了成员变量DecorView的而这里,DecorView则是PhoneWindow里面的一个内部类,它是继承FrameLayout,所以DecorView是一个FrameLayout窗口。

 private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {

        /* package */int mDefaultOpacity = PixelFormat.OPAQUE;

        /** The feature ID of the panel, or -1 if this is the application's DecorView */
        private final int mFeatureId;

        private final Rect mDrawingBounds = new Rect();

        private final Rect mBackgroundPadding = new Rect();

        private final Rect mFramePadding = new Rect();

        private final Rect mFrameOffsets = new Rect();
        ....
 }

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);
}

ViewManager接口定义了add、update、remove方法操作View。

实现类:

ViewGroup

public abstract class ViewGroup extends View implements ViewParent, ViewManager {
    private static final String TAG = "ViewGroup";
    ...
    public void addView(View child, LayoutParams params) {
        addView(child, -1, params);
    }
    
     /*
     * @param child the child view to add
     * @param index the position at which to add the child or -1 to add last
     * @param params the layout parameters to set on the child
     */
    public void addView(View child, int index, LayoutParams params) {
        // addViewInner() will call child.requestLayout() when setting the new LayoutParams
        // therefore, we call requestLayout() on ourselves before, so that the child's request
        // will be blocked at our level
        requestLayout();
        invalidate(true);
        addViewInner(child, index, params, false);
    }
    ...

ViewGroup里面实现了ViewManager接口,View通过ViewGroup的addView方法添加到ViewGroup中,而ViewGroup层层嵌套到最顶级都会显在一个窗口Window中。

WindowManager

/*   The interface that apps use to talk to the window manager.
Use Context.getSystemService(Context.WINDOW_SERVICE) to get one of these.
*/
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

可以看到WindowManager是一个接口,而且它继承与ViewManager。WindowManager字面理解就是窗口管理器,每一个窗口管理器都与一个的窗口显示绑定。获取实例可以通过Context.getSystemService(Context.WINDOW_SERVICE)获取。既然继承了ViewManager,那么它也就可以进行添加删除View的操作了,不过它的操作放在它的实现类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的方法。所以WindowManager最后方法实现是在WindowManagerGlobal里面。

一个WindowManager管理多个Window,那么DecorView与WM的绑定是在什么时候?

activity的启动主要是ActivityThread.attach()方法,attach方法最后调用handleBindApplication(),通过mInstrumentation运行onCreate方法,之后调用onStart,onRestoreInstanceState。

 private void handleBindApplication(AppBindData data) {
       //创建appContext 
      final ContextImpl appContext = ContextImpl.createAppContext(this, data.info);
      try {
            // If the app is being launched for full backup or restore, bring it up in
            // a restricted environment with the base application class.
            //通过反射创建Application
            app = data.info.makeApplication(data.restrictedBackupMode, null);
            mInitialApplication = app;
 
           try {
                //调用Application的onCreate方法
                mInstrumentation.callApplicationOnCreate(app);
            } catch (Exception e) {
            }
        }
}

跳过Activity的onCreate,onStart方法以及onRestoreInstanceState恢复数据方法,直接到handleResumeActivity方法就是onResume。

final void handleResumeActivity(IBinder token,
            boolean clearHide, boolean isForward, boolean reallyResume) {

            //调用activity.onResume,把activity数据记录更新到ActivityClientRecord
            ActivityClientRecord r = performResumeActivity(token, clearHide);
 
            .............

            if (r.window == null && !a.mFinished && willBeVisible) {
                r.window = r.activity.getWindow(); //赋值
                View decor = r.window.getDecorView();
                decor.setVisibility(View.INVISIBLE);
                ViewManager wm = a.getWindowManager();
                WindowManager.LayoutParams l = r.window.getAttributes();
                a.mDecor = decor;
                l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
                l.softInputMode |= forwardBit;
                if (a.mVisibleFromClient) {
                    a.mWindowAdded = true;
                    //decorView添加进windowmanager,把当前的DecorView与WindowManager绑定一起
                    wm.addView(decor, l);
                }
            .....
    }

所以DecorView与WindowManager在onResume之后绑定的,这也解释了在onCreate、onStart、onResume中不能获取View宽高的原因。ViewRootImpl 还未创建,就不存在渲染操作,也就不存在View的测量步骤了。

addView其实是上面WindowManagerGlobal的方法。

    public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        ...
        ViewRootImpl root;
        View panelParentView = null;
        
        ...
            
        root = new ViewRootImpl(view.getContext(), display);
        view.setLayoutParams(wparams);
        mViews.add(view);
        mRoots.add(root);
        mParams.add(wparams);
        //ViewRootImpl开始绘制view
        root.setView(view, wparams, panelParentView);
        ...
    }

ViewRootImpl,它代表了View树的根,每个View 的刷新,绘制,点击事件的分发其实都是由 ViewRootImpl 作为发起者的,由 ViewRootImpl 控制这些操作从 DecorView 开始遍历 View 树去分发处理。

我们看下前面调用到了viewRootImpl的setView方法

  public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
                ...
                // 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.

                //绘制view
                requestLayout();    
                ...
                //将DecorView与ViewRootImpl绑定。
                view.assignParent(this);
             }

    @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            //重点
            scheduleTraversals();
        }
    }

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

view.assignParent(this)将ViewRootImpl设为decorView的parent。

requestLayout里调用了scheduleTraversals()方法。这个方法是渲染屏幕的关键方法,由它来发起一次绘制View树的任务请求,invalidate()和postInvalidate()里也调用了这个方法。 

ViewRootImpl 是实现了 ViewParent 接口的,所以assignParent方法就将 DecorView 和 ViewRootImpl 绑定起来了。每个Activity 的根布局都是 DecorView,而 DecorView 的 parent 又是 ViewRootImpl。

上面是ViewRootImpl的requestLayout方法,下面看下View的requestLayout方法。

public void requestLayout() {
    if (mMeasureCache != null) mMeasureCache.clear();

    if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
        // Only trigger request-during-layout logic if this is the view requesting it,
        // not the views in its parent hierarchy
        ViewRootImpl viewRoot = getViewRootImpl();
        if (viewRoot != null && viewRoot.isInLayout()) {
            if (!viewRoot.requestLayoutDuringLayout(this)) {
                return;
            }
        }
        mAttachInfo.mViewRequestingLayout = this;
    }

    //为当前view设置标记位 PFLAG_FORCE_LAYOUT
    mPrivateFlags |= PFLAG_FORCE_LAYOUT;
    mPrivateFlags |= PFLAG_INVALIDATED;

    if (mParent != null && !mParent.isLayoutRequested()) {
        //向父容器请求布局
        mParent.requestLayout();
    }
    if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
        mAttachInfo.mViewRequestingLayout = null;
    }
}

 标记位的作用就是标记了当前的View是需要进行重新布局的,接着调用mParent.requestLayout方法,即调用父容器的requestLayout方法,而父容器又会调用它的父容器的requestLayout方法,即requestLayout事件层层向上传递,因为刚刚把decorView的父亲设为ViewRootImpl,所以事件直到ViewRootImpl,也即是说子View的requestLayout事件,最终会被ViewRootImpl接收并得到处理。

流程图:

屏幕刷新:16.6ms

Android系统中每隔16.6ms会发送一次VSYNC信号有可能会触发UI的渲染。

一个典型的显示系统中,一般包括CPU、GPU、display三个部分。

  • CPU一般负责计算数据,把计算好数据交给GPU。
  • GPU会对图形数据进行渲染,渲染好后放到buffer里存起来。
  • Display(屏幕或者显示器)负责把buffer里的数据呈现到屏幕上。

在屏幕刷新中,Android系统引入了双缓冲机制。GPU只向Back Buffer中写入绘制数据,Dispaly只读取Frame Buffer的数据。GPU会定期交换Back Buffer和Frame Buffer。交换的频率也是16.6ms,这就与屏幕的刷新频率保持了同步。

双缓冲机制作用:避免GPU写数据时,Display在读取数据从而造成的屏幕闪烁。

上面我们已经看过了requestLayout方法,下面通过invalidate方法,分析刷新View的过程。

//View.class
@UiThread
public class View implements Drawable.Callback, KeyEvent.Callback,AccessibilityEventSource {
    ...    
    public void invalidate() {
        invalidate(true);
    }
    
    //invalidateCache为true表示全部重绘
    void invalidate(boolean invalidateCache) {
        invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
    }

    void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
            boolean fullInvalidate) {
            ...
            final AttachInfo ai = mAttachInfo;
            final ViewParent p = mParent;
            if (p != null && ai != null && l < r && t < b) {
                final Rect damage = ai.mTmpInvalRect;
                damage.set(l, t, r, b);
            //重点!
                p.invalidateChild(this, damage);
            }
            ...
        }
    }
}

invalidateInternal方法中通过调用View的父布局invalidateChild方法来请求重绘。View的父布局可能是ViewGroup或者是ViewRootImpl类。

public abstract class ViewGroup extends View implements ViewParent, ViewManager {
    @Override
    public final void invalidateChild(View child, final Rect dirty) {
        ViewParent parent = this;
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            ...
            //这是一个从当前的布局View向上不断遍历当前布局View的父布局,最后遍历到ViewRootImpl的循环
            do {
                View view = null;
                if (parent instanceof View) {
                    view = (View) parent;
                }
                if (drawAnimation) {
                    if (view != null) {
                        view.mPrivateFlags |= PFLAG_DRAW_ANIMATION;
                    } else if (parent instanceof ViewRootImpl) {
                        ((ViewRootImpl) parent).mIsAnimating = true;
                    }
                }
                ...             
                //这里调用的是父布局的invalidateChildInParent方法
                parent = parent.invalidateChildInParent(location, dirty);
            } while (parent != null);
        }
    }
}

public final class ViewRootImpl implements ViewParent,View.AttachInfo.Callbacks, ThreadedRenderer.HardwareDrawCallbacks {
    @Override
    public void invalidateChild(View child, Rect dirty) {
        invalidateChildInParent(null, dirty);
    }
}

所以如果View的父布局是ViewGroup,会进入一个do while循环,最终两个都还是会到ViewRootImpl的invalidateChildInParent方法中。

public final class ViewRootImpl implements ViewParent,View.AttachInfo.Callbacks, ThreadedRenderer.HardwareDrawCallbacks {

    @Override
    public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
        checkThread();
      ....

        invalidateRectOnScreen(dirty);

        return null;
    }   
    
    private void invalidateRectOnScreen(Rect dirty) {
        final Rect localDirty = mDirty;
        ...
        if (!mWillDrawSoon && (intersected || mIsAnimating)) {
            //调用scheduleTraversals方法进行绘制
            scheduleTraversals();
        }
    } 
}

可以看到同requestLayout方法一样,都是调用到了ViewRootImpl中的scheduleTraversals方法。

//ViewRootImpl.class
void scheduleTraversals() {
    if (!mTraversalScheduled) {
        ....
        //关键在这里!!!
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        if (!mUnbufferedInputDispatch) {
            scheduleConsumeBatchedInput();
        }
        ....
    }
}

Choreoprapher类的作用是编排输入事件、动画事件和绘制事件的执行,它的postCallback方法的作用就是将要执行的事件放入内部的一个队列中,最后会执行传入的Runnable,这里也就是mTraversalRunnable。所以我们来看看mTraversalRunnable

//ViewRootImpl.class
final class TraversalRunnable implements Runnable {
    @Override
    public void run() {
        doTraversal();
    }
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();

void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
            ....
            //找到我们的performTraversals方法来,这里就是开始绘制View的地方啦!
            performTraversals();
            ....
        }
    }

小结:当我们调用View的invalidate或requestLayout方法后,View会去不断向上调用父布局的绘制方法并在这个过程中计算需要重绘的区域,最终调用过程会走到ViewRootImpl中,调用的是ViewRootImpl的performTraversals执行重绘操作。

performTraversals方法里面就是执行onMeasure,onLayout,onDraw三部曲了。不过有时可能只需要执行 performDraw() 绘制流程,有时可能只执行performMeasure() 测量和 performLayout()布局流程。

performTraversals() 是在 doTraversal() 中被调用的,而 doTraversal() 又被封装到一个 Runnable 里,那么关键就是这个 Runnable 什么时候被执行了? 

postCallback()最后会调用到一个native方法nativeScheduleVsync(mReceiverPtr),这个方法其实相当于向系统订阅一个接受一个Vsync信号。android系统每过16.6ms会发送一个Vsync信号,由下面这个类来接收Vsync信号。

private final class FrameDisplayEventReceiver extends DisplayEventReceiver
            implements Runnable {
        ...
        @Override
        public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
        ...
            mTimestampNanos = timestampNanos;
            mFrame = frame;
            Message msg = Message.obtain(mHandler, this);
            msg.setAsynchronous(true);
            mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
        }

        @Override
        public void run() {
            mHavePendingVsync = false;
            doFrame(mTimestampNanos, mFrame);
        }

    }

上面这个类中的onVsync()函数就是来回调Vsync信号,这个回调方法里面将这个类自己的run()方法传给mHandler来进行处理,同时将这个消息设为异步消息。

    void doFrame(long frameTimeNanos, int frame) {
        final long startNanos;
        synchronized (mLock) {
        ...
        try {
        ...
        // Choreographer.CALLBACK_TRAVERSAL这个参数和 mChoreographer.postCallback()里面传入的一致
            doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
        ...
        }
        ...
    }

    void doCallbacks(int callbackType, long frameTimeNanos) {
        CallbackRecord callbacks;
        synchronized (mLock) {
            ...
            // 取出之前放入mCallbackQueues的mTraversalRunnable
            callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
                    now / TimeUtils.NANOS_PER_MS);
            ...
            // 回调mTraversalRunnable的run函数
            for (CallbackRecord c = callbacks; c != null; c = c.next) {
                c.run(frameTimeNanos);
            }
            ...
        }
    }

所以当Vsync信号来时,调用doFrame方法运行performTraversals方法。

总结:

通过WindowManager将DecorView传给ViewRootImpl的setView方法,在setView方法中的performTraversals()方法中分别对View进行measure, layout, draw,通过ViewGroup的类分别对子View依次进行测量,摆放和绘制。

requestlayout和invalidate的区别?

  • 调用View的requestLayout会不断回调直到ViewRootImpl调用它的requestLayout的会触发onMeasure重新测量,并调用布局onLayout重新布局,不一定会调用onDraw,除非显示不一样了,调用顺序是requestLayout ,然后invalidate。

  • view的invalidate会递归调用父view的invalidateChildInParent,直到ViewRootImpl的invalidateChildInParent,然后触发peformTraversals,会导致当前view被重绘,不会导致onMeasure和onLayout被调用,只有OnDraw会被调用

  • postInvalidate是在非UI线程中调用,invalidate则是在UI线程中调用。

画面造成丢帧大体上有两类原因:

  1. 遍历绘制 View 树计算屏幕数据的时间超过了 16.6ms;
  2. 主线程一直在处理其他耗时的消息,导致遍历绘制 View 树的工作迟迟不能开始,从而超过了 16.6 ms 底层切换下一帧画面的时机。

View刷新时机:

代码里调用了某个 View 发起的刷新请求,这个重绘工作并不会马上就开始,而是需要等到下一个屏幕刷新信号来的时候才开始。

而只有当界面有刷新的需要时,我们 app 才会在下一个屏幕刷新信号来时,遍历绘制 View 树来重新计算屏幕数据。如果界面没有刷新的需要,一直保持不变时,我们 app 就不会去接收每隔 16.6ms 的屏幕刷新信号事件了,但底层仍然会以这个固定频率来切换每一帧的画面,只是后面这些帧的画面都是相同的而已。


 

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值