Android 理解Window和WindowManager

8,359 阅读14分钟

概述

Window表示窗口的概念,他是一个抽象类,他的真正实现类是PhoneWindowWindowManager用来对Window进行管理,是外接访问Window的入口,Window操作的具体实现是在WindowManagerService中,WindowMagerWindowManagerService交互是IPC的过程

Android中所有的视图都是附加在Window上上呈现的,不管Activity,Dialog,Toast,他们的视图都是附加在Window上的,因此Window实际上是View的直接管理者

下面我们来详细的了解Window

Window和WindowMagaer

我们先来了解一下如何使用WindwoMagaer来添加一个Window

        Button button = new Button(this);
        button.setText("Window");
        WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
                WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, 0, 0, PixelFormat.TRANSPARENT);

        layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION;

        layoutParams.flags= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;

        layoutParams.gravity= Gravity.LEFT|Gravity.TOP;
        layoutParams.x=100;
        layoutParams.y=300;

        WindowManager windowManager = getWindowManager();

        windowManager.addView(button,layoutParams);

这段代码可以添加一个Window,位置在(100,300)处,这里面有俩个参数比较重要分别是,typeflag,下面分别介绍一下这俩个参数

TYPE 窗口的属性

type参数表示Window的类型,Window有三种类型,分别是Application Window(应用窗口),Sub Window(子窗口)和System Window(系统窗口),每个大类型又包含多个小类型,他们都定义在WindowMager的静态内部类LayoutParams中,下面对这三种类型进行讲解

Application Window(应用窗口)

Activity就是典型的应用窗口,应用窗口包含的类型如下:

        public static final int FIRST_APPLICATION_WINDOW = 1;
        //窗口的基础值,其他窗口要大于这个值
        public static final int TYPE_BASE_APPLICATION   = 1;
        // 普通应用程序的窗口
        public static final int TYPE_APPLICATION        = 2;

        public static final int TYPE_APPLICATION_STARTING = 3;

        public static final int TYPE_DRAWN_APPLICATION = 4;

        public static final int LAST_APPLICATION_WINDOW = 99;

应用窗口就包括了以上几中类型,其中最上方是起始值,最下方是结束值,也就是说应用窗口的Type值的范围是1-99,这个数值的大小涉及窗口的层级

Sub Window(子窗口)

子窗口不能够独立存在,要依附在其他窗口上才行,PopupWindow就属于子窗口,子窗口的定义类型如下:

        //子窗口的初始值
        public static final int FIRST_SUB_WINDOW = 1000;

        public static final int TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW;

        public static final int TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW + 1;

        public static final int TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW + 2;

        public static final int TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW + 3;

        public static final int TYPE_APPLICATION_MEDIA_OVERLAY  = FIRST_SUB_WINDOW + 4;

        public static final int TYPE_APPLICATION_ABOVE_SUB_PANEL = FIRST_SUB_WINDOW + 5;
        //子窗口的结束值
        public static final int LAST_SUB_WINDOW = 1999;

可以看出子窗口的type值范围是1000-1999

System Window (系统窗口)

Toast,输入法窗口,系统音量条窗口,系统错误窗口,都属于系统窗口,系统窗口的类型定义如下:

        public static final int FIRST_SYSTEM_WINDOW     = 2000;
        public static final int TYPE_STATUS_BAR         = FIRST_SYSTEM_WINDOW;
        public static final int TYPE_SEARCH_BAR         = FIRST_SYSTEM_WINDOW+1;
        @Deprecated
        public static final int TYPE_PHONE              = FIRST_SYSTEM_WINDOW+2;
        @Deprecated
        public static final int TYPE_SYSTEM_ALERT       = FIRST_SYSTEM_WINDOW+3;
        public static final int TYPE_KEYGUARD           = FIRST_SYSTEM_WINDOW+4;
        @Deprecated
        public static final int TYPE_TOAST              = FIRST_SYSTEM_WINDOW+5;
        @Deprecated
        public static final int TYPE_SYSTEM_OVERLAY     = FIRST_SYSTEM_WINDOW+6;
        @Deprecated
        public static final int TYPE_PRIORITY_PHONE     = FIRST_SYSTEM_WINDOW+7;
        ...
        public static final int LAST_SYSTEM_WINDOW      = 2999;

系统窗口接近40个,这里只列出一小部分,系统窗口的Type值在2000-2999之间

窗口的显示次序

上面介绍的Type值越大,就意味着靠用户越近,很显然系统的窗口是最大的,他在应用窗口和子窗口的上方

FLAG 窗口的标志

Flag就是窗口的标志,用于控制Window的显示,同样被定义在WindowManager的内部类LayoutParams中,一共有20多个,这里列出一些常用的

| type| 描述| |--|--|-- |FLAG_ALLOW_LOCK_WHILE_SCREEN_ON|只要窗口可见,就允许在开启状态的屏幕上锁屏| |FLAG_NOT_FOCUSABLE|窗口不能获取输入焦点,设置该标志的同时,FLAG_NOT_TOUCH_MODAL也会被设置| |FLAG_NOT_TOUCH_MODAL|将该窗口区域外的触摸事件,传递给其他Window,而自己只会处理窗口区域内的触摸事件| |FLAG_NOT_TOUCHABLE|窗口不接受任何触摸事件| |FLAG_KEEP_SCREEN_ON|只要窗口可见,就一直保持屏幕长亮| |FLAG_LAYOUT_NO_LIMITS|允许窗口超出屏幕外| |FLAG_FULLSCREEN|隐藏所有的屏幕装饰窗口,比如游戏视频等全屏显示| |FLAG_SHOW_WHEN_LOCKED|窗口可以在锁屏窗口之上显示| |FLAG_IGNORE_CHEEK_PRESSES|当用户脸贴近屏幕时(比如打电话时),不会响应此事件| |FLAG_TURN_SCREEN_ON|窗口显示时将屏幕点亮|

设置Window的Flag除了上方的方式外还可以采用下面的方式

        //第一种
        Window window = getWindow();
        window.addFlags();
        
        //第二种
        Window window = getWindow();
        window.setFlags();

软键盘模式

我们在写登陆界面的时候,默认弹出的软键盘窗口可能会覆盖输入框下面的按钮,为了让软键盘按照期望的方式显示,,WindowMagaer的静态内部类LayoutParams中定义了软键盘的相关模式,我们介绍一下常用的

SoftInputMode描述
SOFT_INPUT_STATE_UNSPECIFIED没有指定状态,系统会选择一个合适的状态或依赖于主题的设置
SOFT_INPUT_STATE_UNCHANGED不会改变软键盘的状态
SOFT_INPUT_STATE_HIDDEN当用户进入该窗口时,软键盘默认隐藏
SOFT_INPUT_STATE_ALWAYS_HIDDEN当窗口获取焦点时,软键盘总是隐藏
SOFT_INPUT_ADJUST_RESIZE当软键盘弹出时,窗口会调整大小
SOFT_INPUT_ADJUST_PAN当软键盘弹出时,窗口不需要调整大小,要确认输入焦点是否可见

软键盘模式可以在AndroidManifest中设置

  <activity android:name=".CameraActivity"
            android:launchMode="singleTask"
            android:windowSoftInputMode="adjustPan">

        </activity>

也可以代码设置

     getWindow().setSoftInputMode();

WindowManager

WindowMagaer所提供的功能很简单,只有常用的三个方法即,添加View,更新View,删除View,这个三个方法定义在ViewManager中,而WindowManager继承自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);
}

Window的内部机制

Window是一个抽象概念,每一个Window都对应一个View和一个ViewRootImplWindowView通过ViewRootImpl来建立联系,因此Window不是实际存在的,他是以View的形式存在的,在实际是一个中,不能直接访问Window,只有通过WindowManager才能访问

Window的添加过程

Window的添加是通过WindowManageraddView方法实现的,我们WindowManager##addView方法作为入口来分析,WindowMagager是一个接口,真正的实现在WindowManagerImpl

 @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }

我们发现他其实把事情交给了WindowManagerGlobal

    public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        //注释1
        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");
        }
            ····
            //注释2
            root = new ViewRootImpl(view.getContext(), display);

            view.setLayoutParams(wparams);
            //注释3
            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);

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

我们首先了解几个重要的变量

    //储存所有Window对应的View
    private final ArrayList<View> mViews = new ArrayList<View>();
    //储存所有Window对应的ViewRoot
    private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
    //布局参数列表
    private final ArrayList<WindowManager.LayoutParams> mParams =
            new ArrayList<WindowManager.LayoutParams>();
  • 注释1处,它主要是检查参数是否合法
  • 注释2处,在此处创建了ViewRootImpl并赋值给root变量
  • 注释3处,将View,root和params添加到列表中
  • 注释4处,调用ViewRootImpl来更新界面并完成Window的添加过程

ViewRootImpl有很多的职责

  • View树的根,并管理View树
  • 触发View的测量,布局和绘制
  • 输入时间的中转站
  • 管理Surface
  • 负责与WMS通信

我们继续看一下ViewRootImplsetView方法

    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
        ···
             // 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();
        ···
           try {
                    mOrigWindowType = mWindowAttributes.type;
                    mAttachInfo.mRecomputeGlobalAttributes = true;
                    collectViewAttributes();
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);
                } catch (RemoteException e) {
    
        }
        }

这个方法首先会调用requestLayout方法来完成一部刷新请求

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

scheduleTraversals实际是View绘制的入口

然后调用mWindowSession.addToDisplay方法,mWindowSession是一个IWindowSession类型的,是一个Binder对象,用于进程间通信,也就是说addToDisplay方法其实是运行在WMS所在的进程system_server进程

   @Override
    public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
            int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets,
            Rect outOutsets, InputChannel outInputChannel) {

        return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
                outContentInsets, outStableInsets, outOutsets, outInputChannel);
    }

addToDisplay方法内部调用了WMSaddWindow方法并将自身也就是Session传入了进去,每个应用程序都会有一个SessionWMS会用ArrayList来保存起来,这样所有的工作都交给了WMS来做

WMS会为这个添加的窗口分配Surface,并确定窗口的显示次序,负责显示界面的是画布Surface,而不是窗口本身,WMS会把Surface交给SurfaceFlinger处理,SurfaceFlinger会把这些Surface混合并绘制到屏幕上

Window的更新过程

Window的更新过程和添加过程是类似的,需要调用WindowManagerupdateViewLayout方法,然后会继续进入WindowManagerGlobalupdateViewLayout方法,我们直接从这个方法进行分析

   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;
        //注释1
        view.setLayoutParams(wparams);

        synchronized (mLock) {
            //注释2
            int index = findViewLocked(view, true);
            //注释3
            ViewRootImpl root = mRoots.get(index);
            //注释4
            mParams.remove(index);
            //注释5
            mParams.add(index, wparams);
            //注释6
            root.setLayoutParams(wparams, false);
        }
    }
  • 注释1,将更新的参数设置到View中
  • 注释2,得到要更新的窗口在View列表中的索引
  • 注释3,根据索引获取窗口的ViewRoot
  • 注释4 5,用于更新布局参数列表
  • 注释6,调用ViewRootsetLayoutParams方法,将更新的参数设置到ViewRootImpl中,setLayoutParams方法最终会调用ViewRootImplscheduleTraversals方法,我们看下这个方法

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

我们看下注释1,mChoreographer翻译为编舞者,用于接受系统的VSync信号,在下一个帧渲染时控制一些操作,mChoreographerpostCallback方法用于添加回调,这个添加的回调,将在下一帧渲染时执行,这个添加的回调指的是TraversalRunnable类型的mTraversalRunnable,如下:

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

这个方法内部调用了doTraversal

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

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

            performTraversals();

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

这个方法又调用了performTraversals,这个方法中更新了Window的视图,并且完成了View的绘制流程,measure,layout,draw,这样就完成了View的更新

Activity的Window的创建过程

这个需要了解App的启动过程,这个我就不再重复说了,不了解的可以看我之前的文章Android App启动过程,他最后会调用performLaunchActivity方法来完成整个启动过程,这个方法内部会通过类加载器创建Activity的实例对象,并调用了attach方法,为其关联运行中所依赖的一系列变量

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    ...
    Activity activity = null;
    try {
        java.lang.ClassLoader cl = appContext.getClassLoader();
        activity = mInstrumentation.newActivity(
                cl, component.getClassName(), r.intent);
        ...
    } catch (Exception e) {
        ...
    }

    try {
        // 返回之前创建过的 application 对象
        Application app = r.packageInfo.makeApplication(false, mInstrumentation);
        ...
        if (activity != null) {
            ...
            // attach 到 window 上
            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);
            ...
            if (r.isPersistable()) {
                mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
            } else {
                mInstrumentation.callActivityOnCreate(activity, r.state);
            }
            ...
        }
    } catch (Exception e) {
        ...
    }
    return activity;
}
  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) {
        attachBaseContext(context);

        mFragments.attachHost(null /*parent*/);

        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        mWindow.setWindowControllerCallback(this);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
        if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
            mWindow.setSoftInputMode(info.softInputMode);
        }
        if (info.uiOptions != 0) {
            mWindow.setUiOptions(info.uiOptions);
        }
        mUiThread = Thread.currentThread();

       ...

        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());
        }
        mWindowManager = mWindow.getWindowManager();
        mCurrentConfig = config;

        mWindow.setColorMode(info.colorMode);

        setAutofillCompatibilityEnabled(application.isAutofillCompatibilityEnabled());
        enableAutofillCompatibilityIfNeeded();
    }

这个方法,会创建Activity所属的Window对象并为其设置回调接口,到这里Window已经创建完成了,下面我们分析一下Activity的视图是怎么依附到Window上的,由于Activity的视图是从setContentView方法提供,我们从setContentView方法开始分析

getWindow().setContentView(layoutResID);
initWindowDecorActionBar();

我们点进去发现他其实调用了PhoneWindow的setContentView方法,我们看下这个方法

  public void setContentView(int layoutResID) {
        // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
        // decor, when theme attributes and the like are crystalized. Do not check the feature
        // before this happens.
        if (mContentParent == null) {
            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 {
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

这个方法主要做了以下几件事

  • 如果DecorView不存在则创建DecorView,DecorViewActivity中的顶级View,一般来说他包括标题栏内容栏,这个会随着主题的改变而改变,反正内容栏一定存在,并且他有固定的idandroid.R.id.content, 创建DecorViewinstallDecor方法完成,内部会通过generateDecor方法创建,这个时候DecorView还是一个空白的Framlayout
   private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {
            //创建DecroView
            mDecor = generateDecor(-1);
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        } else {
            mDecor.setWindow(this);
        }
        if (mContentParent == null) {
            //向DecorView添加内容
            mContentParent = generateLayout(mDecor);
        }
        ...
    }
   protected DecorView generateDecor(int featureId) {
        ...
        return new DecorView(context, featureId, this, getAttributes());
    }
  • 初始化DecorView的结构,通过generateLayout方法加载具体的布局文件到DecorView中,并为内容栏变量赋值
 ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
  • 然后将setContentViewView添加到内容栏中, mLayoutInflater.inflate(layoutResID, mContentParent);这时Activity的布局文件就已经添加到了DecorView的内容栏中
  • 最后回调onContentChanged方法,通知Activity视图已经改变

通过上方的步骤,现在DecorView已经创建并初始化完成,Activity的布局也添加到DecorView的内容栏中,但是这个时候DecorView还没有被WindowManager添加到Window

ActivityThreadhandleResumeActivity会调用ActivityonResume方法,并且会调用ViewManageraddView方法把DecorView添加到Window

    @Override
    public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
            String reason) {

    
        //调用Activity的onResume方法
        final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
        ...
        if (r.window == null && !a.mFinished && willBeVisible) {
            r.window = r.activity.getWindow();
            View decor = r.window.getDecorView();
            decor.setVisibility(View.INVISIBLE);
            //获取WindowMagaer
            ViewManager wm = a.getWindowManager();
            WindowManager.LayoutParams l = r.window.getAttributes();
            a.mDecor = decor;
            l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
         
            if (a.mVisibleFromClient) {
                if (!a.mWindowAdded) {
                    a.mWindowAdded = true;
                    //把DecorView添加到Window
                    wm.addView(decor, l);
                } 
                ...
            
    }

到这里Activity的Window创建过程分析完毕

Choreographer

先上代码

## ViewRootImpl.java

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

mChoreographer翻译为编舞者,用于接受系统的VSync信号,在下一个帧渲染时控制一些操作,mChoreographerpostCallback方法用于添加回调,这个添加的回调,将在下一帧渲染时执行,这个添加的回调指的是TraversalRunnable类型的mTraversalRunnable,如下:

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

这个方法内部调用了doTraversal

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

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

            performTraversals();

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

这个方法又调用了performTraversals,这个方法中更新了Window的视图,并且完成了View的绘制流程,measure,layout,draw,这样就完成了View的更新

继续追一下mChoreographer.postCallback

## Choreographer.java
public void postCallback(int callbackType, Runnable action, Object token) {
        postCallbackDelayed(callbackType, action, token, 0);
    }


 public void postCallbackDelayed(int callbackType,
            Runnable action, Object token, long delayMillis) {
        if (action == null) {
            throw new IllegalArgumentException("action must not be null");
        }
        if (callbackType < 0 || callbackType > CALLBACK_LAST) {
            throw new IllegalArgumentException("callbackType is invalid");
        }

        postCallbackDelayedInternal(callbackType, action, token, delayMillis);
    }


private void postCallbackDelayedInternal(int callbackType,
            Object action, Object token, long delayMillis) {
        if (DEBUG_FRAMES) {
            Log.d(TAG, "PostCallback: type=" + callbackType
                    + ", action=" + action + ", token=" + token
                    + ", delayMillis=" + delayMillis);
        }

        synchronized (mLock) {
            final long now = SystemClock.uptimeMillis();
            final long dueTime = now + delayMillis;
            //把Runnable加入数组中
            mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);

            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);
            }
        }
    }
 private void scheduleFrameLocked(long now) {
        if (!mFrameScheduled) {
            mFrameScheduled = true;
            if (USE_VSYNC) {//// Android 4.1 之后 USE_VSYNCUSE_VSYNC 默认为 true
                if (DEBUG_FRAMES) {
                    Log.d(TAG, "Scheduling next frame on vsync.");
                }

                // 如果再主线程,则立即执行,否则就发送异步消息
                if (isRunningOnLooperThreadLocked()) {
                    scheduleVsyncLocked();
                } else {
                    Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
                    msg.setAsynchronous(true);
                    mHandler.sendMessageAtFrontOfQueue(msg);
                }
            } else {// 未开启 vsync,4.1 之后默认开启
                final long nextFrameTime = Math.max(
                        mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS + sFrameDelay, now);
                if (DEBUG_FRAMES) {
                    Log.d(TAG, "Scheduling next frame in " + (nextFrameTime - now) + " ms.");
                }
                Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
                msg.setAsynchronous(true);
                mHandler.sendMessageAtTime(msg, nextFrameTime);
            }
        }
    }

这里就是判断是否开启USE_VSYNC,如果是主线程则立即执行

 private void scheduleVsyncLocked() {
        mDisplayEventReceiver.scheduleVsync();
    }

VSYNC其实是为了解决,屏幕刷新率和GPU帧率不一致导致的屏幕撕裂问题,VSYNC机制再Android4.1引入,简单理解为,VSYNC是硬件发送的定时信号,通过Choreographer来监听这个信号,每当信号来临,开始绘制工作

mDisplayEventReceiver 类型是FrameDisplayEventReceiver其父类为DisplayEventReceiver,scheduleVsync方法为父类的方法

## DisplayEventReceiver.java

 public void scheduleVsync() {
        if (mReceiverPtr == 0) {
            Log.w(TAG, "Attempted to schedule a vertical sync pulse but the display event "
                    + "receiver has already been disposed.");
        } else {
            //注册Vsync信号
            nativeScheduleVsync(mReceiverPtr);
        }
    }

//有 vsync 信号时,由 native 调用此方法
private void dispatchVsync(long timestampNanos, long physicalDisplayId, int frame) {
        onVsync(timestampNanos, physicalDisplayId, frame);
    }

这里是调用了native方法nativeScheduleVsync注册信号,当有Vsync信号时回调dispatchVsync方法,然后调用onVsync方法,这个方法是由子类FrameDisplayEventReceiver实现


    private final class FrameDisplayEventReceiver extends DisplayEventReceiver
            implements Runnable {
        private boolean mHavePendingVsync;
        private long mTimestampNanos;
        private int mFrame;

        public FrameDisplayEventReceiver(Looper looper, int vsyncSource) {
            super(looper, vsyncSource);
        }

       
        @Override
        public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {
           
            long now = System.nanoTime();
            if (timestampNanos > now) {
                Log.w(TAG, "Frame time is " + ((timestampNanos - now) * 0.000001f)
                        + " ms in the future!  Check that graphics HAL is generating vsync "
                        + "timestamps using the correct timebase.");
                timestampNanos = now;
            }

            if (mHavePendingVsync) {
                Log.w(TAG, "Already have a pending vsync event.  There should only be "
                        + "one at a time.");
            } else {
                mHavePendingVsync = true;
            }

            mTimestampNanos = timestampNanos;
            mFrame = frame;
            //这里传入this,回掉自身的run方法
            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);
        }
    }

这里sendMessageAtTime方法的参数timestampNanos / TimeUtils.NANOS_PER_MS,timestampNanos表示vysnc信号时间戳,单位纳秒,这里转换成毫秒,此时Vysnc已经发生,timestampNanos是比当前时间小的,这样消息就可以塞到MessageQueue的前面,此处的this,表示回调自身的run方法,最后调用doFrame

    void doFrame(long frameTimeNanos, int frame) {
        final long startNanos;
        synchronized (mLock) {
            if (!mFrameScheduled) {
                return; // no work to do
            }
            //
            long intendedFrameTimeNanos = frameTimeNanos;
            startNanos = System.nanoTime();
            final long jitterNanos = startNanos - frameTimeNanos;
            //startNanos当前时间,frameTimeNanos表示Vysnc信号时间,相减得到主线程耗时时间
            if (jitterNanos >= mFrameIntervalNanos) {
                // mFrameIntervalNanos表示一帧时间
                final long skippedFrames = jitterNanos / mFrameIntervalNanos;
                if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
                    //掉帧抄超过30帧打印log
                    Log.i(TAG, "Skipped " + skippedFrames + " frames!  "
                            + "The application may be doing too much work on its main thread.");
                }
                final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;
                if (DEBUG_JANK) {
                    Log.d(TAG, "Missed vsync by " + (jitterNanos * 0.000001f) + " ms "
                            + "which is more than the frame interval of "
                            + (mFrameIntervalNanos * 0.000001f) + " ms!  "
                            + "Skipping " + skippedFrames + " frames and setting frame "
                            + "time to " + (lastFrameOffset * 0.000001f) + " ms in the past.");
                }
                frameTimeNanos = startNanos - lastFrameOffset;
            }

            if (frameTimeNanos < mLastFrameTimeNanos) {
                if (DEBUG_JANK) {
                    Log.d(TAG, "Frame time appears to be going backwards.  May be due to a "
                            + "previously skipped frame.  Waiting for next vsync.");
                }
                scheduleVsyncLocked();
                return;
            }

            if (mFPSDivisor > 1) {
                long timeSinceVsync = frameTimeNanos - mLastFrameTimeNanos;
                if (timeSinceVsync < (mFrameIntervalNanos * mFPSDivisor) && timeSinceVsync > 0) {
                    scheduleVsyncLocked();
                    return;
                }
            }

            mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);
            mFrameScheduled = false;
            mLastFrameTimeNanos = frameTimeNanos;
        }

        try {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame");
            AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);

            //开始回调callback
            mFrameInfo.markInputHandlingStart();
            doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);

            mFrameInfo.markAnimationsStart();
            doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
            doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos);

            mFrameInfo.markPerformTraversalsStart();
            doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);

            doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
        } finally {
            AnimationUtils.unlockAnimationClock();
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }


    }

下面调用doCallbacks方法

  void doCallbacks(int callbackType, long frameTimeNanos) {
        CallbackRecord callbacks;
        synchronized (mLock) {
         
            final long now = System.nanoTime();
            //根据callbackType找到callbacks对象
            callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
                    now / TimeUtils.NANOS_PER_MS);
            if (callbacks == null) {
                return;
            }
            mCallbacksRunning = true;

        ...
          
        try {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, CALLBACK_TRACE_TITLES[callbackType]);
            for (CallbackRecord c = callbacks; c != null; c = c.next) {
                if (DEBUG_FRAMES) {
                    Log.d(TAG, "RunCallback: type=" + callbackType
                            + ", action=" + c.action + ", token=" + c.token
                            + ", latencyMillis=" + (SystemClock.uptimeMillis() - c.dueTime));
                }
                //执行callback的run方法
                c.run(frameTimeNanos);
            }
        } 
        ...
    }

根据callbackType找到callbacks对象,然后执行run方法,callbackType有四种 CALLBACK_INPUTCALLBACK_ANIMATIONCALLBACK_TRAVERSALCALLBACK_COMMIT

private static final class CallbackRecord {
   
        public void run(long frameTimeNanos) {
            if (token == FRAME_CALLBACK_TOKEN) {
                //注意这里我们自定义的就是FrameCallback,回调这里
                ((FrameCallback)action).doFrame(frameTimeNanos);
            } else {
                //到这里就执行了Runnable的run方法
                ((Runnable)action).run();
            }
        }
    }

到这里就执行了Runnablerun方法

如何监测应用的 FPS?

其实这里就应用到了一个

> Choreographer.java

public void postFrameCallback(FrameCallback callback) {
    postFrameCallbackDelayed(callback, 0);
}

public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis) {
    ......
    // 这里的类型是 CALLBACK_ANIMATION
    postCallbackDelayedInternal(CALLBACK_ANIMATION,
            callback, FRAME_CALLBACK_TOKEN, delayMillis);
}

public void postFrameCallback(FrameCallback callback) { public void postFrameCallback(FrameCallback callback) { 这里可以看到这个方法和上面的postCallback一样,差别就是callbackType,这个的callTypeCALLBACK_ANIMATION,也就是说每次Vysnc回调都会调用我们自定义的FrameCallbackdoFrame方法,我们可以再这个方法中计算fps

实战

public class Fps1 {

    private boolean isFpsOpen;
    private ArrayList<Integer> listeners;
    private int count;
    private Handler handler;
    private long FPS_INTERVAL_TIME = 1000L;
    private FpsCallback mfpsCallback;
    private MyCallback fpsRunnable;


    public Fps1() {
        handler = new Handler(Looper.getMainLooper());
    }


    public void startMonitor(FpsCallback fpsCallback) {
        // 防止重复开启
        if (!isFpsOpen) {
            isFpsOpen = true;
            this.mfpsCallback = fpsCallback;
            fpsRunnable = new MyCallback();
            handler.postDelayed(fpsRunnable, FPS_INTERVAL_TIME);
            Choreographer.getInstance().postFrameCallback(fpsRunnable);
        }
    }

    public void stopMonitor() {
        count = 0;
        handler.removeCallbacks(fpsRunnable);
        Choreographer.getInstance().removeFrameCallback(fpsRunnable);
        isFpsOpen = false;
    }


    class MyCallback implements Choreographer.FrameCallback, Runnable {

        @Override
        public void doFrame(long frameTimeNanos) {
            count++;
            Choreographer.getInstance().postFrameCallback(this);
        }

        @Override
        public void run() {
            mfpsCallback.fps(count);
            count = 0;
            handler.postDelayed(this, FPS_INTERVAL_TIME);
        }
    }

    interface FpsCallback {
        void fps(int fps);
    }
}

参考:《Android开发艺术探索》《Android进阶解密》