从Activity建立到View呈现中间发生了什么?

前言

前段时间公司招人,做为面试官,我常常让面试者简述View的绘制流程。他们基本都能讲明白View的测量(measure)、布局(layout)、绘制(draw)等过程。还有少数人会提到DecorView和ViewRootImp的做用。可是,当我继续追问关于Window的内容时,几乎没有人回答上来。而本章将会带你深刻理解Window、DecorView、ViewRootImp。除此以外,你还能在本章找到如下问题的答案:java

  1. 为何要有设计Window?
  2. 子线程真的不能更新UI吗?
  3. 为何在Activity的onCreate方法中没法获取View的宽和高?

图解Activity启动到View显现过程

上图出现了五个对象:Activity、Window、WindowManager、DecorView、ViewRootImpl,我分别解释一下它们的做用。面试

  • Activity:Activity像是一个指挥官,它不处理具体的事务,只在适当的时候指挥Window/WindowManager工做。例如:在attach时建立Window对象、onResume后通知WindowManager添加view。app

  • Window:Window是一个窗口,它是View的容器。Android中的视图以View树的形式组织在一块儿,而View树必须依附在Window上才能工做。一个Window对应着一个View树。启动Activity时会建立一个Window,显示Dialog时也会建立一Window。所以Activity内部能够有多个Window。因为View的测量、布局、绘制只是在View树内进行的,所以一个Window内View的改动不会影响到另外一个Window。Window是一个抽象类,它只有一个实现类PhoneWindow。ide

  • WindowManager:WindowManager是Window的管理类。它不直接操做Window,而是操做Window内的DecorView。WindowManager是一个接口。它的具体实现类是WindowManagerImpl。oop

    public interface WindowManager{
        public void addView(View view, ViewGroup.LayoutParams params);
        public void updateViewLayout(View view, ViewGroup.LayoutParams params);
        public void removeView(View view);
    }
    复制代码
  • DecorView是View树的顶级View,它是FrameLayout的子类。根据Activity设置的Theme,DecorView会有不一样布局。但不管布局怎么变,DecorView都有一个Id为R.id.content的FrameLayout。Activity.setContentView()方法就是在这个FrameLayout中添加子View。布局

  • ViewRootImpl是链接WindowManager和DecorView的纽带,View的三大流程均是经过ViewRootImpl来完成的。post

源码解密

Android插件化之启动Activity中,我介绍过Activity的启动流程。其中handleLaunchActivity()方法是启动Activity的核心方法。本节就以它为切入点开始分析。ui

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        //省略代码...
      
      	//performLaunchActivity
        Activity a = performLaunchActivity(r, customIntent);
        if (a != null) {
            r.createdConfig = new Configuration(mConfiguration);
            Bundle oldState = r.state;
            
            //handleResumeActivity
            handleResumeActivity(r.token, false, r.isForward,!r.activity.mFinished && !r.startsNotResumed);
            
            //省略代码...
        }
    }
复制代码

handleLaunchActivity()主要调用了两个方法:performLaunchActivity()和handleResumeActivity()this

  • performLaunchActivity:完成Activity的建立,以及调用Activity的 onCreate()和onStart()方法。spa

  • handleResumeActivity:调用Activity的onResume()方法,处理View的呈现。

performLaunchActivity

咱们进入performLaunchActivity()方法,核心代码以下:

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        ActivityInfo aInfo = r.activityInfo;
        ComponentName component = r.intent.getComponent();
        java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
      	//建立Activity
        Activity activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
        Application app = r.packageInfo.makeApplication(false, mInstrumentation);

        if (activity != null) {
            //建立Context
            Context appContext = createBaseContextForActivity(r, activity);
            CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
            Configuration config = new Configuration(mCompatConfiguration);
            
            //调用Activity.attach。
            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);
          
            //省略代码...
          
            //调用Activity.onCreate()方法。
            if (r.isPersistable()) {
                mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
            } else {
                mInstrumentation.callActivityOnCreate(activity, r.state);
            }
            r.activity = activity;
            
            if (!r.activity.mFinished) {
              	//调用Activity.onStart()方法。
                activity.performStart();
            }
        }
        r.paused = true;
        mActivities.put(r.token, r);
        return activity;
    }
复制代码

performLaunchActivity()主要作了如下几件事:

  1. 建立Activity。
  2. 建立Context。
  3. 调用Activity.attach(),建立Window,关联WindowManager。
  4. 调用Activity.onCreate()。
  5. 调用Activity.onStart()。

attach

上面说了,在Activity.attach()方法执行时会建立Window并为Window关联WindowManager。咱们看一下伪代码。

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) {
        attachBaseContext(context);
        //建立Window
        mWindow = new PhoneWindow(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);
        }
      	//设置UI线程
        mUiThread = Thread.currentThread();
        mMainThread = aThread;
        //关联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());
        }
        mWindowManager = mWindow.getWindowManager();
    }

复制代码

setContentView

顺着流程图,Activity.setContentView()方法会被调用。Activity.setContentView()内又直接调用了PhoneWindow.setContentView()。咱们直接看PhoneWindow.setContentView()的源码。

private DecorView mDecor;
    
    //setContentView传过来的View会被add到mContentParent中。mContentParent的Id是R.id.content。
    private ViewGroup mContentParent;

    @Override
    public void setContentView(View view, ViewGroup.LayoutParams params) {
        if (mContentParent == null) {
            installDecor();
        }else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }
      	//省略代码。。。
    }

    private void installDecor() {
        if (mDecor == null) {
            //初始化DecorView
            mDecor = generateDecor();
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        }
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);
            //省略代码。。。
        }
    }
复制代码

若是mContentParent为null,会执行installDecor()方法。generateDecor()源代码很长,可是它的逻辑很简单主要是根据Activity Theme的不一样,初始化不一样的布局。DecorView的布局虽然不一样,但它们都一个Id为R.id.content的FrameLayout。Activity.setContentView()就是在这个FrameLayout中添加子View。

handleResumeActivity

performLaunchActivity()执行完后,紧接着执行handleResumeActivity()。

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

        //处理Activity的onRestart onResume生命周期。
        ActivityClientRecord r = performResumeActivity(token, clearHide);

        if (r != null) {
            if (r.window == null && !a.mFinished) {
                r.window = r.activity.getWindow();
                View decor = r.window.getDecorView();
                //设置DecorView不可见
              	decor.setVisibility(View.INVISIBLE);
               
                ViewManager wm = a.getWindowManager();
                WindowManager.LayoutParams l = r.window.getAttributes();
              
                if (a.mVisibleFromClient) {
                    a.mWindowAdded = true;
                    //利用WindowManager添加DecorView。
                    wm.addView(decor, l);
                }
            }
            if (!r.activity.mFinished && r.activity.mDecor != null) {
                if (r.activity.mVisibleFromClient) {
                    //设置DecorView可见。
                    r.activity.makeVisible();
                }
            }

            //IPC调用,通知AMS Activity启动完成。
            ActivityManagerNative.getDefault().activityResumed(token);
            
        } else {
            try {
                ActivityManagerNative.getDefault()
                    .finishActivity(token, Activity.RESULT_CANCELED, null, false);
            } catch (RemoteException ex) {
            }
        }
    }
复制代码

handleResumeActivity()处理的事情比较多。我总结为如下几个过程:

  1. 经过performResumeActivity()处理Activity的onRestart onResume的生命周期。
  2. 将DecorView设置为InVisible。
  3. 经过WindowManager.addView()将DecorView绘制完成。
  4. 将DecorView设置为Visiable。
  5. 通知AMS Activity启动完成。

WindowManger.addView()

前面说过,WindowManger是一个抽象类,它的实现类是WindowManagerImpl。WindowManager.addView()封装了View绘制的细节。咱们着重看一下。

public final class WindowManagerImpl implements WindowManager {
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
    private final Display mDisplay;
    private final Window mParentWindow;

    private IBinder mDefaultToken;

    public WindowManagerImpl(Display display) {
        this(display, null);
    }

    private WindowManagerImpl(Display display, Window parentWindow) {
        mDisplay = display;
        mParentWindow = parentWindow;
    }

    public void setDefaultToken(IBinder token) {
        mDefaultToken = token;
    }

    @Override
    //这里的View是PhoneWindow内建立的DecorView。
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mDisplay, mParentWindow);
    }

    private void applyDefaultToken(@NonNull ViewGroup.LayoutParams params) {
        if (mDefaultToken != null && mParentWindow == null) {
            final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
            if (wparams.token == null) {
                wparams.token = mDefaultToken;
            }
        }
    }
}
复制代码

WindowManagerImpl.addView会调用WindowManagerGlobal.addView()。在WindowManagerGlobal.addView()方法执行以前,会先执行applyDefaultToken()方法。这个方法实际上是给传进来的DecorView加一个身份标识,表示这个DecorView属于哪一个Activity。这样系统(WindowManagerService)才会知道要把DecorView绘制到哪一个Activity。

咱们继续追踪WindowManagerGlobal.addView(),伪代码以下:

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>();
    
    //这里的View是PhoneWindow内建立的DecorView。
    public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {
      
        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
      	//省略代码....
   
        root = new ViewRootImpl(view.getContext(), display);

       	//省略代码...

        mViews.add(view);
        mRoots.add(root);
        mParams.add(wparams);
        
        root.setView(view, wparams, panelParentView);
    }
复制代码

首先会建立ViewRootImpl,随后把View、ViewRootImpl、LayoutParams都保存在List中,以供未来更新UI使用。它们的index值相同,这样就三者就对应起来了。最后,调用ViewRootImpl.setView()方法。

ViewRootImpl.setView()

public class ViewRootImpl{
		
  	View mView;
  	//这里的View是PhoneWindow内建立的DecorView。
  	public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
                mView = view;

                //省略代码。。。

                // 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();
                
                //省略代码。。。
              	
              	//IPC通讯,通知WMS渲染。
                res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mInputChannel);

                //省略代码。。。
            }
        }
    }
  
  	@Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            //检查当前执行的线程是否是UI线程
            checkThread();
            mLayoutRequested = true;
            //处理DecorView的measure、layout、draw。
            scheduleTraversals();
        }
    }
    
    void checkThread() {
        if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
    }
}
复制代码

ViewRootImpl.setView()的伪代码有两句,但咱们只关心requestLayout。由于mWindowSession.addToDisplay()就是经过IPC通知WMS去渲染,咱们再去分析WMS意义已经不大了。requestLayout()方法首先会检查当前执行的线程是否是UI线程,随后调用scheduleTraversals()。scheduleTraversals会把本次请求封装成一个TraversalRunnable对象,这个对象最后会交给Handler去处理。最后ViewRootImpl.performTraversals()被调用。调用链以下:

final TraversalRunnable mTraversalRunnable = new TraversalRunnable();

void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        //省略代码。。。
        mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        //省略代码。。。
    }
}

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

void doTraversal() {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
				
      	//处理DecorView的measure、layout、draw。
        performTraversals();

        if (mProfile) {
            Debug.stopMethodTracing();
            mProfile = false;
        }
    }
}
复制代码

performTraversals()主要是处理View树的measure、layout、draw等流程。不清楚的同窗能够去看《Android开发艺术探索》第四章,我在这里就不继续深刻了。

总结

下面我回答文章前言部分提出的几个问题。

  • 为何要有设计Window?

    要理解Window须要从面向对象的角度出发。

    1. 假如没有Window,那Window管理View树的代码必然会放到Activity中。这样Activity就变得十分庞大,这与咱们前面说的Activity指挥官的角色相违背。
    2. 把View树的管理工做封装到Window后,在调用Dialog.show()、Dialog.hide()等Window切换时,Activity只须要负责Window的显示和隐藏便可。
    3. View的测量、布局、绘制只是在View树内进行的,把一个View树封装在一个Window中方便视图管理。
  • 子线程真的不能更新UI吗?

    更新视图时,线程检查是在ViewRootImpl的checkThread()中。ViewRootImpl的初始化是在Activity的onResume()方法以后。所以,若是有子线程在onResume以前更新UI是能够成功的。固然还有一种Hook ViewRootImpl的mThread的方法也能够更新UI。这里不作介绍了。

  • Activity的onCreate方法为何没法获取View的宽和高?

    这个问题和子线程不能更新UI的问题很像,也是方法执行时机的一个问题。View的measure、layout、draw。发生在Activity.onResume()以后,所以在onResume()以前都是没法获取View的宽、高等信息的。

相关文章
相关标签/搜索