View 加载流程

前言

这篇文章主要是从Activity的setContentView()设置我们视图ContentView切入点来分析我们的View是如何加载到的过程,来学习View的加载流程。

Activiy的启动初始化

首先我们先看下Activity启动做了那些事情,下面这张图是Activity启动的时候做了什么事情。

Activity启动的时候会调用ActivityThread#handleLaunchActivity()方法,核心方法还是ActivityThread#performLaunchActivity()(为什么是调用这个方法,了解ASM Activity启动流程的会知道,如果不清楚的话也不影响下面的流程)

public Activity handleLaunchActivity(ActivityClientRecord r,
        PendingTransactionActions pendingActions, Intent customIntent) {
    .....
    // 这里会调用performLaunchActivity
    final Activity a = performLaunchActivity(r, customIntent);

    .....
      
    return a;
}

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    .....

    ContextImpl appContext = createBaseContextForActivity(r);
    Activity activity = null;
    try {
        java.lang.ClassLoader cl = appContext.getClassLoader();
    	// 1.通过mInstrumentation创建Activity
        activity = mInstrumentation.newActivity(
                cl, component.getClassName(), r.intent);
        StrictMode.incrementExpectedActivityCount(activity.getClass());
        r.intent.setExtrasClassLoader(cl);
        r.intent.prepareToEnterProcess();
        if (r.state != null) {
            r.state.setClassLoader(cl);
        }
    } catch (Exception e) {
        .....
    }

    try {
        ......

        if (activity != null) {
            CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
            Configuration config = new Configuration(mCompatConfiguration);
            if (r.overrideConfig != null) {
                config.updateFrom(r.overrideConfig);
            }
            if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity "
                    + r.activityInfo.name + " with config " + config);
            Window window = null;
            if (r.mPendingRemoveWindow != null && r.mPreserveWindow) {
                window = r.mPendingRemoveWindow;
                r.mPendingRemoveWindow = null;
                r.mPendingRemoveWindowManager = null;
            }
            appContext.setOuterContext(activity);
          	//2.调用activity的attach方法并创建PhoneWindow
            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 (customIntent != null) {
                activity.mIntent = customIntent;
            }
            r.lastNonConfigurationInstances = null;
            checkAndBlockForNetworkAccess();
            activity.mStartedActivity = false;
            int theme = r.activityInfo.getThemeResource();
            if (theme != 0) {
                activity.setTheme(theme);
            }

            activity.mCalled = false;
          	//5.通过mInstrumentation调用Activity onCreate 生命周期
            if (r.isPersistable()) {
                mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
            } else {
                mInstrumentation.callActivityOnCreate(activity, r.state);
            }
            if (!activity.mCalled) {
                throw new SuperNotCalledException(
                    "Activity " + r.intent.getComponent().toShortString() +
                    " did not call through to super.onCreate()");
            }
            r.activity = activity;
        }
        r.setState(ON_CREATE);

        mActivities.put(r.token, r);

    } catch (SuperNotCalledException e) {
        throw e;

    } 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*/);
    //3.在Activiy里面创建PhoneWindow成员变量
    mWindow = new PhoneWindow(this, window, activityConfigCallback);
    
    ....
    
    //4.给PhoneWindow设置WindowManagerImpl实例
    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());
    }
    //WindowManagerImpl实例给mWindowManager局部变量赋值
    mWindowManager = mWindow.getWindowManager();
    mCurrentConfig = config;

    mWindow.setColorMode(info.colorMode);

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

复制代码

这里核心步骤有5步:

  • 通过mInstrumentation创建Activity实例
  • 执行activity的attach方法
  • 创建PhoneWindow并赋值给activity变量mWindow
  • 将WindowManangerImpl实例传递给PhoneWindow
  • 通过mInstrumentation调用Activity走onCreate生命周期

在这里Activity的启动过程中相关初始化就完成了,接下来我们看下onCreate 里 setContentView相关初始化

View 加载初始化

接下来我们直接看调用onCreate方法之后流程,首先先上图

setContentView
public void setContentView(@LayoutRes int layoutResID) {
  	// 获取PhoneWindow 设置contentView
    getWindow().setContentView(layoutResID);
    initWindowDecorActionBar();
}
复制代码

在onCreate方法里面我们会调用setContentView方法,这里面实际调用的是PhoneWindow的setContentView

@Override
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.
  
  	// 这里mContentParent 就是我们视图的parent View,如果不存在我们需要加载出来
    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 {
        // 加载contentView 到mContentParent中去
        mLayoutInflater.inflate(layoutResID, mContentParent);
    }
  	....
}
复制代码

上面的代码有两个核心步骤

  • installDecor 初始化 DecorView 和mContentParent
  • 将contentView加载到mContentParent中去
installDecor 初始化 DecorView 和mContentParent
private void installDecor() {
    mForceDecorInstall = false;
  	// 1.创建DecorView
    if (mDecor == null) {
        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) {
      	// 2.初始化mContentParent
        mContentParent = generateLayout(mDecor);

        // Set up decor part of UI to ignore fitsSystemWindows if appropriate.
        mDecor.makeOptionalFitsSystemWindows();

        final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(
                R.id.decor_content_parent);
				....
    }
}

// 创建我们的decorView
protected DecorView generateDecor(int featureId) {
    // System process doesn't have application context and in that case we need to directly use
    // the context we have. Otherwise we want the application context, so we don't cling to the
    // activity.
    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());
}

protected ViewGroup generateLayout(DecorView decor) {
    // Apply data from current theme.
  
		....
    
    // The rest are only done if this window is not embedded; otherwise,
    // the values are inherited from our container.
      
    ....
      
    // Inflate the window decor.
		// 3. 这一块代码是给我们decor分配布局资源逻辑,这里的资源都是系统自带的资源,具体用哪个资源文件取决于你的activity 的 theme
    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 if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
        if (mIsFloating) {
            TypedValue res = new TypedValue();
            getContext().getTheme().resolveAttribute(
                    R.attr.dialogTitleIconsDecorLayout, res, true);
            layoutResource = res.resourceId;
        } else {
            layoutResource = R.layout.screen_title_icons;
        }
        // XXX Remove this once action bar supports these features.
        removeFeature(FEATURE_ACTION_BAR);
        // System.out.println("Title Icons!");
    } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
            && (features & (1 << FEATURE_ACTION_BAR)) == 0) {
        // Special case for a window with only a progress bar (and title).
        // XXX Need to have a no-title version of embedded windows.
        layoutResource = R.layout.screen_progress;
        // System.out.println("Progress!");
    } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
        // Special case for a window with a custom title.
        // If the window is floating, we need a dialog layout
        if (mIsFloating) {
            TypedValue res = new TypedValue();
            getContext().getTheme().resolveAttribute(
                    R.attr.dialogCustomTitleDecorLayout, res, true);
            layoutResource = res.resourceId;
        } else {
            layoutResource = R.layout.screen_custom_title;
        }
        // XXX Remove this once action bar supports these features.
        removeFeature(FEATURE_ACTION_BAR);
    } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
        // If no other features and not embedded, only need a title.
        // If the window is floating, we need a dialog layout
        if (mIsFloating) {
            TypedValue res = new TypedValue();
            getContext().getTheme().resolveAttribute(
                    R.attr.dialogTitleDecorLayout, res, true);
            layoutResource = res.resourceId;
        } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
            layoutResource = a.getResourceId(
                    R.styleable.Window_windowActionBarFullscreenDecorLayout,
                    R.layout.screen_action_bar);
        } else {
            layoutResource = R.layout.screen_title;
        }
        // System.out.println("Title!");
    } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
        layoutResource = R.layout.screen_simple_overlay_action_mode;
    } else {
        // Embedded, so no decoration is needed.
        layoutResource = R.layout.screen_simple;
        // System.out.println("Simple!");
    }

    mDecor.startChanging();
  	// 4.加载布局到decorView中
    mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
	 	// 5.获取contentParent
    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
    
  	....

    return contentParent;
}

复制代码

上面就是整个的View初始化的流程了,view通过LayoutInflater解析XML创建View,如果有兴趣了解LayoutInflater如何解析XML的可以单独看LayoutInflater相关的文章,这里就不再赘述了。上面1、2、3、5步都没问题,接下来我们重点看下第4步 decorView 添加rootView做了什么

DecorView onResourcesLoaded 做了什么?
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
    ....

    mDecorCaptionView = createDecorCaptionView(inflater);
  	// 1.初始化我们的root view layout
    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.
      	// 2.添加root view 添加到DecorView里
        addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    }
    mContentRoot = (ViewGroup) root;
    initializeElevation();
}

public void addView(View child, int index, LayoutParams params) {
    if (DBG) {
        System.out.println(this + " addView");
    }

    if (child == null) {
        throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
    }

    // 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);
}
复制代码

第一步我们通过LayoutInfater初始化了rootView,第二步将rootView添加到DecorView中,因为我们的DecorView是FramLayout的子类,好这样我们的View就通过这样的一个流程添加到了DecorView里面了。

那么这时候是不是以为整个流程都结束了呢?

**注意:**其实这里只是Activity、PhoneWindow、decorView、contentView 4者之间关联起来,我们的View也就是decorView并没有真正的加入到Window中来。如下图

所以接下来才是我们decorView真正的加载到Window的流程

DecorView 加载到Window

所以我们继续回到ActivityThread 的 handleResumeActivity 方法,这个方法会在 handleLaunchActivity 之后执行,至于为什么要回到这个方法呢,这里就需要设计到ASM启动流程的知识了。

接着我们继续看下面 handleResumeActivity 方法

@Override
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
        String reason) {
    .....
    if (r.window == null && !a.mFinished && willBeVisible) {
        r.window = r.activity.getWindow();
        View decor = r.window.getDecorView();
        decor.setVisibility(View.INVISIBLE);
      	// 1.获取我们的WindowManagerImpl
        ViewManager wm = a.getWindowManager();
        WindowManager.LayoutParams l = r.window.getAttributes();
        a.mDecor = decor;
        l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
        l.softInputMode |= forwardBit;
        if (r.mPreserveWindow) {
            a.mWindowAdded = true;
            r.mPreserveWindow = false;
            ViewRootImpl impl = decor.getViewRootImpl();
            if (impl != null) {
                impl.notifyChildRebuilt();
            }
        }
        if (a.mVisibleFromClient) {
            if (!a.mWindowAdded) {
                a.mWindowAdded = true;
              	// 2.将decorView 添加到我们的 WindowManagerImpl 中
                wm.addView(decor, l);
            } else {
                a.onWindowAttributesChanged(l);
            }
        }
    } else if (!willBeVisible) {
        if (localLOGV) Slog.v(TAG, "Launch " + r + " mStartedActivity set");
        r.hideForNow = true;
    }
		....
}
复制代码

第一步里面的WindowManagerImpl 是在activity attach 的时候通过调用(WindowManager)context.getSystemService(Context.WINDOW_SERVICE) 设置到PhoneWindow中。

第二步 将decorView 添加到WindowManagerImpl 中

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

mGlobal 是WindowManagerGlobal类的单例,我们接着往下看

public void addView(View view, ViewGroup.LayoutParams params,
        Display display, Window parentWindow) {

  	....
      
    ViewRootImpl root;
    View panelParentView = null;

    synchronized (mLock) {
        // Start watching for system property changes.
        ......
          
				//1.这里开始创建我们的ViewRootImpl
        root = new ViewRootImpl(view.getContext(), display);
				//2.decorView 设置LayoutParams,这个时候还未给decorView设置parent 所以不会触发 绘制流程
        view.setLayoutParams(wparams);

        mViews.add(view);
        mRoots.add(root);
        mParams.add(wparams);

        // do this last because it fires off messages to start doing things
        try {
          	//3.关键的步骤,将DecorView真正的添加到ViewRootImpl中
            root.setView(view, wparams, panelParentView);
        } catch (RuntimeException e) {
            // BadTokenException or InvalidDisplayException, clean up.
            if (index >= 0) {
                removeViewLocked(index, true);
            }
            throw e;
        }
      
      	.....
    }
}
复制代码

上面三个核心步骤:

  • 第一步创建我们的ViewRootImpl
  • 第二步给decorView 设置LayoutParams,这个时候还未给decorView设置parent 所以不会触发 绘制流程
  • 第三步关键的步骤,将DecorView真正的添加到ViewRootImpl中

我们接着看ViewRootImpl的setView方法

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    synchronized (this) {
        if (mView == null) {
            mView = view;
          
						// 这里省略大片代码
            ....
            mAdded = true;
            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.
            
          	//1.调用requestLayout 触发绘制流程
            requestLayout();
          
          	....
              
        		//3.将ViewRootImpl 设置为DecorView的parent,之后view的requestLayout 和 initialdate 会触发绘制流程
            view.assignParent(this);
          
          	....
        }
    }
}

@Override
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        checkThread();
        mLayoutRequested = true;
      	//2.post一个绘制视图的队列任务
        scheduleTraversals();
    }
}

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

final TraversalRunnable mTraversalRunnable = new TraversalRunnable();

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

复制代码
  • 调用requestLayout 在主线程中handler post一个队列任务来触发绘制流程。注意在Activity的onCreate 中调用View 的measureHeight 为0 ,就是因为这时候还没有走绘制流程
  • 将ViewRootImpl 设置为DecorView的parent,之后view的requestLayout 和 initialdate 会触发绘制流程
分类:
Android
标签: