Window视图机制

1.Window

Activity中的相关变量

    /**  Activity.java */
    public class Activity extends  ... {
        private Instrumentation mInstrumentation;
        private IBinder mToken;
        ...
        private Application mApplication;
        private Window mWindow;
        private WindowManager mWindowManager;
        View mDecor = null;
        ...
    }

Activity的启动是由Activity中通过H这个handler调用的,实现代码在ActivityThread中,代码如下。

    /**  ActivityThread.java  */
    /**  Core implementation of activity launch. */
    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 {
            ...
            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 (SuperNotCalledException e) {
                ...
        } catch (Exception e) {
                ...
        }
        return activity;
    }

剔除大部分代码我们可以看到,Activity的实例化通过反射的方式实现的,Activity在onCreate之前,调用了attach方法进行初始化。下面是attach的部分代码

    /**Activity.java**/
    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) {
        ...
        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();

        mMainThread = aThread;
        mInstrumentation = instr;
        mToken = token;
        mIdent = ident;
        mApplication = application;
        mIntent = intent;
        mReferrer = referrer;
        mComponent = intent.getComponent();
        mActivityInfo = info;
        mTitle = title; 
        mParent = parent;
        ...
        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);
        ...
    }

在attach方法里,会实例化一个PhoneWindow对象赋值给mWindow变量,并将获取到的WindowManager传递给mWindow。

2.页面布局

我们在Activity的onCreate里,都会通过setContentView()设置界面布局,我们来看看setContentView干了啥。

    /**Activity.java*/
    public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);//1
        initWindowDecorActionBar();
    }

    /**PhoneWindow.java*/
    @Override
    public void setContentView(int layoutResID) {
        if (mContentParent == null) {
            installDecor();//2
        } 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);//3
        }
        ...
    }

    private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {
            mDecor = generateDecor(-1);
            ...
        } else {
            mDecor.setWindow(this);
        }
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);
            ...
        }
    }

    protected DecorView generateDecor(int featureId) {
        Context context;
        if (mUseDecorContext) {
            Context applicationContext = getContext().getApplicationContext();
            if (applicationContext == null) {
                context = getContext();
            } else {
                context = new DecorContext(applicationContext, getContext());
                if (mTheme != -1) {
                    context.setTheme(mTheme);
                }
            }
        } else {
            context = getContext();
        }
        return new DecorView(context, featureId, this, getAttributes());
    }

1.setContentView在activity类中,会调用PhoneWindow的setContentView方法
2.PhoneWindow会检查mContentParent,如果没有,会通过generateDecor()方法创建出DecorView,进而通过generateLayout方法得到mContentParent。DecorView是一个继承自FrameLayout的子类,在generateDecor()中,通过new关键字来实例化的。
3.注释3处的方法我们都不陌生,将mContentParent作为ViewGroup,从xml文件中创建布局,mContentParent就是我们布局文件的父布局了。

继续看看generateLayout()方法

    //PhoneWindow.java
    protected ViewGroup generateLayout(DecorView decor) {
        ...
        int layoutResource;
        int features = getLocalFeatures();
        // System.out.println("Features: 0x" + Integer.toHexString(features));
        if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
            layoutResource = R.layout.screen_swipe_dismiss;
            setCloseOnSwipeEnabled(true);
            ...        
        } else {
            // Embedded, so no decoration is needed.
            layoutResource = R.layout.screen_simple;//1
            // System.out.println("Simple!");
        }

        mDecor.startChanging();
        ...
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);//2
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        if (contentParent == null) {
            throw new RuntimeException("Window couldn't find content container view");
        }
        
        // Remaining setup -- of background and title -- that only applies
        // to top-level windows.
        if (getContainer() == null) {//3
            final Drawable background;
            if (mBackgroundResource != 0) {
                background = getContext().getDrawable(mBackgroundResource);
            } else {
                background = mBackgroundDrawable;
            }
            mDecor.setWindowBackground(background);
            final Drawable frame;
            if (mFrameResource != 0) {
                frame = getContext().getDrawable(mFrameResource);
            } else {
                frame = null;
            }
            mDecor.setWindowFrame(frame);
            mDecor.setElevation(mElevation);
            mDecor.setClipToOutline(mClipToOutline);

            if (mTitle != null) {
                setTitle(mTitle);
            }
            if (mTitleColor == 0) {
                mTitleColor = mTextColor;
            }
            setTitleColor(mTitleColor);
        }
        mDecor.finishChanging();
        return contentParent;
    }

    //DecorView.java
    void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
        if (mBackdropFrameRenderer != null) {
            loadBackgroundDrawablesIfNeeded();
            mBackdropFrameRenderer.onResourcesLoaded(
                    this, mResizingBackgroundDrawable, mCaptionBackgroundDrawable,
                    mUserCaptionBackgroundDrawable, getCurrentColor(mStatusColorViewState),
                    getCurrentColor(mNavigationColorViewState));
        }

        mDecorCaptionView = createDecorCaptionView(inflater);
        final View root = inflater.inflate(layoutResource, null);
        if (mDecorCaptionView != null) {
            if (mDecorCaptionView.getParent() == null) {
                addView(mDecorCaptionView,
                        new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
            }
            mDecorCaptionView.addView(root,
                    new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
        } else {

            // Put it below the color views.
            addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        }
        mContentRoot = (ViewGroup) root;
        initializeElevation();
    }

    //screen_simple.xml
    
        
        
    

1.PhoneWindow会根据features中的值,假如有设置,会给对应的布局,实现相关的特性,例如第一个是用来实现侧滑关闭当前Activity的功能。如果都没设置的话,会给screen_simple.xml,布局代码在下方。
2.DecorView通过onResourcesLoaded方法,将给定的布局添加到DecorView中。其中id为content就是contentParent。我们写的页面布局都将加入到这个contentParent中。
3.window可以有container,这个container也是个window。只有最顶层的window才会设置背景和标题。

小结:

每一个Activity都有个PhoneWindow对象,并且这个PhoneWindow对象有个DecorView视图对象,Activity的视图都挂在DecorView的mContentParent节点下。那么视图是如何更新的呢,我们继续看。

3.页面更新流程

在ActivityThread内部类H的handleMessage方法中,在EXECUTE_TRANSACTION这个分支中,对应的Activity的生命周期,在处理ResumeActivityItem的execute方法时,会调用ActivityThread的handleResumeActivity方法。我们看看该方法的实现

    //ActivityThread.java
    @Override
    public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
            String reason) {
        ...
        final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
        ...
        final Activity a = r.activity;
        ...
        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) {//1
            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;
            ...
            if (a.mVisibleFromClient) {
                if (!a.mWindowAdded) {
                    a.mWindowAdded = true;
                    wm.addView(decor, l);//2
                } else {
                    // The activity will get a callback for this {@link LayoutParams} change
                    // earlier. However, at that time the decor will not be set (this is set
                    // in this method), so no action will be taken. This call ensures the
                    // callback occurs with the decor set.
                    a.onWindowAttributesChanged(l);
                }
            }

            // If the window has already been added, but during resume
            // we started another activity, then don't yet make the
            // window visible.
        } else if (!willBeVisible) {
            if (localLOGV) Slog.v(TAG, "Launch " + r + " mStartedActivity set");
            r.hideForNow = true;
        }
        ...
        if (!r.activity.mFinished && willBeVisible && r.activity.mDecor != null && !r.hideForNow) {
            if (r.newConfig != null) {
                performConfigurationChangedForActivity(r, r.newConfig);
                if (DEBUG_CONFIGURATION) {
                    Slog.v(TAG, "Resuming activity " + r.activityInfo.name + " with newConfig "
                            + r.activity.mCurrentConfig);
                }
                r.newConfig = null;
            }
            WindowManager.LayoutParams l = r.window.getAttributes();
            if ((l.softInputMode
                    & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION)
                    != forwardBit) {
                l.softInputMode = (l.softInputMode
                        & (~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION))
                        | forwardBit;
                if (r.activity.mVisibleFromClient) {
                    ViewManager wm = a.getWindowManager();
                    View decor = r.window.getDecorView();
                    wm.updateViewLayout(decor, l);//3
                }
            }

            r.activity.mVisibleFromServer = true;
            mNumVisibleActivities++;
            if (r.activity.mVisibleFromClient) {
                r.activity.makeVisible();
            }
        }
        ...
    }

1.ActivityClientRecord r,这是个记录Activity相关信息的对象,该对象在Activity对象实例化之前被创建出来,并且该类的参数用来实例化Activity。可以说先有的r,然后activity被创建出来,然后activity在attach的时候拥有了window。然后这时候在这里,r还有没window,所以会从注释1进去,赋值window成员变量。因为是第一次,所以在注释2处,调用wm.addView(decor,l)。随后会调用注释3处的wm.updateViewLayout(decor, l)
2.wm是一个WindowManagerImpl实例(在Window类里面赋值),addView和updateViewLayout两个方法在WindowManagerImpl中是交给WindowManagerGlobal这个单例处理。在WindowManagerGlobal中,addView时,创建一个与根view相对应的ViewRootImpl。
3.在updateViewLayout时,ViewRootImpl会调用setLayoutParams,调用scheduleTraversals,借助于mChoreographer去执行doTraversal()方法,该方法会执行view的measure,layout,draw方法。使用mThreadedRenderer渲染,使用mWindowSession与WindowManagerService通信。

参考
Android源码分析-Activity的启动过程
Android视图框架Activity,Window,View,ViewRootImpl理解

你可能感兴趣的:(Window视图机制)