Android 界面显示原理

313 阅读4分钟

本文只关注界面是如何显示出来的 , 对于activity启动流程就不多讨论了 ,

涉及到的关键类 :

  • ActivityThread
  • Activity / PhoneWindow / DecorView
  • WindowManager / WindowManagerImpl / WindowManagerGlobal
  • ViewRootImpl
  • WindowManagerService
  • Choreographer

先从 ActivityThread #handleResumeActivity 说起 , 此时activity 已经创建出来并调用了onCreate onStart ... 等一系列生命周期方法了

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


        //这里会调用onResume
        final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
   
        final Activity a = r.activity;
 
        //willBeVisible =true 表示activity 将要变为Visible
        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) {
            //r.activity.getWindow(); = PhoneWindow
            r.window = r.activity.getWindow();
            View decor = r.window.getDecorView();
            //将decor 设置为不可见
            decor.setVisibility(View.INVISIBLE);
            //wm = WindowManagerImpl  
            ViewManager wm = a.getWindowManager();
            WindowManager.LayoutParams l = r.window.getAttributes();
            a.mDecor = decor;
            //设置窗口类型为TYPE_BASE_APPLICATION
            l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
  
            if (a.mVisibleFromClient) {
                if (!a.mWindowAdded) {
                    // 关键代码
                    a.mWindowAdded = true;
                    //调用WindowManagerImpl.addView(decor, l);
                    wm.addView(decor, l);
                } else {
                    a.onWindowAttributesChanged(l);
                }
            }

        } 

    }

a.getWindowManager(); 返回的是WindowManagerImpl , parentWindow =phoneWindow

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

关键代码 wm.addView(decor, l) , 将decor添加到WindowManager中

WindowManagerImpl#addView , 又会交给WindowManagerGlobal 调用

private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance(); 

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

mViews 用来保存调用addView添加的view

mRoots 用来保存ViewRootImpl

mParams 用来保存view 的布局参数

接着看 addview方法

WindowManagerGlobal #addview
    public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        //view = decorView  
        //parentWindow 为PhoneWindow
  
        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
        if (parentWindow != null) {
            parentWindow.adjustLayoutParamsForSubWindow(wparams);
        } 

        ViewRootImpl root;

        synchronized (mLock) {
        
            root = new ViewRootImpl(view.getContext(), display);

            view.setLayoutParams(wparams);
            
            //将view  ViewRootImpl params 保存起来
            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);

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

看官方代码注释最后做这个是因为它发送消息开始做事情

setview里面到底发送了什么消息

继续跟进root.setView

ViewRootImpl#setView
    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
                mView = view;

 
                if (view instanceof RootViewSurfaceTaker) {
                    //decorView 实现了RootViewSurfaceTaker , 这里会进来
                    mSurfaceHolderCallback =
                            ((RootViewSurfaceTaker)view).willYouTakeTheSurface();
                    if (mSurfaceHolderCallback != null) {
                        mSurfaceHolder = new TakenSurfaceHolder();
                        mSurfaceHolder.setFormat(PixelFormat.UNKNOWN);
                        mSurfaceHolder.addCallback(mSurfaceHolderCallback);
                    }
                }


                CompatibilityInfo compatibilityInfo =
                        mDisplay.getDisplayAdjustments().getCompatibilityInfo();
                mTranslator = compatibilityInfo.getTranslator();

                // If the application owns the surface, don't enable hardware acceleration
                if (mSurfaceHolder == null) {
                    // While this is supposed to enable only, it can effectively disable
                    // the acceleration too.
                    enableHardwareAcceleration(attrs);
                    final boolean useMTRenderer = MT_RENDERER_AVAILABLE
                            && mAttachInfo.mThreadedRenderer != null;
                    if (mUseMTRenderer != useMTRenderer) {
                        // Shouldn't be resizing, as it's done only in window setup,
                        // but end just in case.
                        endDragResizing();
                        mUseMTRenderer = useMTRenderer;
                    }
                }

                int res; /* = WindowManagerImpl.ADD_OKAY; */
                
                // 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 {
                
                    //wms 添加 mWindow , mWindow和activity的PhoneWindow 不是一个概念
                    //mWindow 中有两个属性 1. 弱引用持有ViewRootImpl 和 2. WindowSession
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);
                } 

                
                //wms添加window失败 抛异常
                if (res < WindowManagerGlobal.ADD_OKAY) { 
                    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);
                }

            }
        }
    }

WMS 添加mWindow之前会调用 requestLayout

官方注释: 在添加到窗口之前调用 requestLayout , 确保我们在从系统接收任何其他事件之前进行重新布局。

按理说wms 添加了window 界面才会显示出来 , 所以应该是wms添加窗口成功之后再调用requestLayout , 但是为什么这里先调用了requestLayout

进入requestLayout 看看

   @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            //不在主线程调用会报错
            checkThread();
            scheduleTraversals();
        }
    }
    
    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        }
    }

上面代码给Choreographer post 一个 callback , 对于屏幕刷新为60hz(即一秒60帧)来说 , Choreographer 会每隔16.6ms 回调一次给 TraversalRunnable ,

TraversalRunnable

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

doTraversal

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

        }
    }

performTraversals 方法会调用 measure layout draw , 完成view的元素显示

performTraversals 代码太多 , 这里贴点关键的代码

 private void performTraversals() {
        // cache mView since it is used so much below...
        final View host = mView;


        if (host == null || !mAdded)
        // 当wms添加mWindow失败直接return 
            return;
     
     
        if (!mStopped || mReportNextDraw) {
                boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
                        (relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
                if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
                        || mHeight != host.getMeasuredHeight() || contentInsetsChanged ||
                        updatedConfiguration) {
     
                     // Ask host how big it wants to be
                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                }
        }
        
        
       if (didLayout) {
           
            performLayout(lp, mWidth, mHeight);
 
        }
       
       
        if (!cancelDraw && !newSurface) {
            
            performDraw();
        }

 }

在WMS添加mWindow 成功情况下 , 当 performDraw 调用完成 , 界面就显示出来了

总结 :

activity 执行完 onResume 后 , ViewRootImpl 会向Choreographer 发送一个 CallBack ,

Choreographer 在下一帧时会回调给ViewRootImpl , ViewRootImpl 调用

performTraversals方法完成 measure layout draw , 等到 WMS 将 window 添加成功 , 界面就显示出来了