View绘制流程一:布局是如何添加到界面上的

View和ViewGroup,View绘制流程解析 View是绘制在屏幕上的用户能与之交互的一个对象,它代表了用户界面组件的一块可绘制的空间块,每一个空间块在屏幕上占据一个长方 形区域。View 的绘制流程是从 ViewRootImpl 的 performTraversals() 方法开始的,它经过测量(measure)、放置(layout)和绘制(draw)三个过程才能把一个 View 绘制出来,measure() 方法用于测量 View 的宽高,layout() 用于确定 View 在父容器中的放置位置,draw() 负责做具体的绘制操作。 阅读详情


当我们打开一个App,便会看到一个个精美的页面,作为开发者的我们,有没有想过这些页面是如何添加到我们的界面上的?本片文章主要解决以下几个问题,如果你对以下问题都有明确的答案,可以不必浪费时间了。

  1. 什么是DecorView,与我们编写的布局文件的关系。
  2. DecorView何时被创建,如何被加载进布局。
  3. 什么是ViewRootImpl,与DecorView的关系
  4. DecorView如何实现与ViewRootImpl的关联
  5. View的绘制流程在何时被触发
  6. 为什么常说子线程不能更新UI

先放一张我总结的View绘制流程函数调用栈:
在这里插入图片描述

为了不放过一丝细节,我将View是如何走到绘制的流程中的每个方法进行了梳理,画出来的函数调用情况也着实令我一惊,下面我将具体讲解这幅图。

1.切入点:setContentView

这个方法想必大家不陌生,我们在Activity的onCreate方法中,调用这个方法,传入我们写好的布局,就会在我们的页面中显示,所以,在毫无头绪的情况下,这个方法是我们分析的切入点。

setContentView:

public void setContentView(@LayoutRes int layoutResID) {
    getWindow().setContentView(layoutResID);
    initWindowDecorActionBar();
}

我们可以看出,这个方法调用流程是getWindow().setContentView(),getWindow()是什么?

public Window getWindow() {
    return mWindow;
}

返回了mwindow,mwindow是一个Window,而Window是一个抽象类,只有一个具体实现类,那就是PhoneWindow,于是,我们就找到了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.
    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;
}

很明显,这里判断mContentParent为null,就进入了installDecor()这个方法,看这个方法的名字,就知道DecorView在此处创建:

private void installDecor() {
    mForceDecorInstall = false;
    if (mDecor == null) {
        // 重点1
        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 = generateLayout(mDecor);

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

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

        if (decorContentParent != null) {
            mDecorContentParent = decorContentParent;
            mDecorContentParent.setWindowCallback(getCallback());
            if (mDecorContentParent.getTitle() == null) {
                mDecorContentParent.setWindowTitle(mTitle);
            }

            final int localFeatures = getLocalFeatures();
            for (int i = 0; i < FEATURE_MAX; i++) {
                if ((localFeatures & (1 << i)) != 0) {
                    mDecorContentParent.initFeature(i);
                }
            }

            mDecorContentParent.setUiOptions(mUiOptions);

            if ((mResourcesSetFlags & FLAG_RESOURCE_SET_ICON) != 0 ||
                    (mIconRes != 0 && !mDecorContentParent.hasIcon())) {
                mDecorContentParent.setIcon(mIconRes);
            } else if ((mResourcesSetFlags & FLAG_RESOURCE_SET_ICON) == 0 &&
                    mIconRes == 0 && !mDecorContentParent.hasIcon()) {
                mDecorContentParent.setIcon(
                        getContext().getPackageManager().getDefaultActivityIcon());
                mResourcesSetFlags |= FLAG_RESOURCE_SET_ICON_FALLBACK;
            }
            if ((mResourcesSetFlags & FLAG_RESOURCE_SET_LOGO) != 0 ||
                    (mLogoRes != 0 && !mDecorContentParent.hasLogo())) {
                mDecorContentParent.setLogo(mLogoRes);
            }

            // Invalidate if the panel menu hasn't been created before this.
            // Panel menu invalidation is deferred avoiding application onCreateOptionsMenu
            // being called in the middle of onCreate or similar.
            // A pending invalidation will typically be resolved before the posted message
            // would run normally in order to satisfy instance state restoration.
            PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
            if (!isDestroyed() && (st == null || st.menu == null) && !mIsStartingWindow) {
                invalidatePanelMenu(FEATURE_ACTION_BAR);
            }
        } else {
            mTitleView = findViewById(R.id.title);
            if (mTitleView != null) {
                if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) {
                    final View titleContainer = findViewById(R.id.title_container);
                    if (titleContainer != null) {
                        titleContainer.setVisibility(View.GONE);
                    } else {
                        mTitleView.setVisibility(View.GONE);
                    }
                    mContentParent.setForeground(null);
                } else {
                    mTitleView.setText(mTitle);
                }
            }
        }

        if (mDecor.getBackground() == null && mBackgroundFallbackDrawable != null) {
            mDecor.setBackgroundFallback(mBackgroundFallbackDrawable);
        }

        // Only inflate or create a new TransitionManager if the caller hasn't
        // already set a custom one.
        if (hasFeature(FEATURE_ACTIVITY_TRANSITIONS)) {
            if (mTransitionManager == null) {
                final int transitionRes = getWindowStyle().getResourceId(
                        R.styleable.Window_windowContentTransitionManager,
                        0);
                if (transitionRes != 0) {
                    final TransitionInflater inflater = TransitionInflater.from(getContext());
                    mTransitionManager = inflater.inflateTransitionManager(transitionRes,
                            mContentParent);
                } else {
                    mTransitionManager = new TransitionManager();
                }
            }

            mEnterTransition = getTransition(mEnterTransition, null,
                    R.styleable.Window_windowEnterTransition);
            mReturnTransition = getTransition(mReturnTransition, USE_DEFAULT_TRANSITION,
                    R.styleable.Window_windowReturnTransition);
            mExitTransition = getTransition(mExitTransition, null,
                    R.styleable.Window_windowExitTransition);
            mReenterTransition = getTransition(mReenterTransition, USE_DEFAULT_TRANSITION,
                    R.styleable.Window_windowReenterTransition);
            mSharedElementEnterTransition = getTransition(mSharedElementEnterTransition, null,
                    R.styleable.Window_windowSharedElementEnterTransition);
            mSharedElementReturnTransition = getTransition(mSharedElementReturnTransition,
                    USE_DEFAULT_TRANSITION,
                    R.styleable.Window_windowSharedElementReturnTransition);
            mSharedElementExitTransition = getTransition(mSharedElementExitTransition, null,
                    R.styleable.Window_windowSharedElementExitTransition);
            mSharedElementReenterTransition = getTransition(mSharedElementReenterTransition,
                    USE_DEFAULT_TRANSITION,
                    R.styleable.Window_windowSharedElementReenterTransition);
            if (mAllowEnterTransitionOverlap == null) {
                mAllowEnterTransitionOverlap = getWindowStyle().getBoolean(
                        R.styleable.Window_windowAllowEnterTransitionOverlap, true);
            }
            if (mAllowReturnTransitionOverlap == null) {
                mAllowReturnTransitionOverlap = getWindowStyle().getBoolean(
                        R.styleable.Window_windowAllowReturnTransitionOverlap, true);
            }
            if (mBackgroundFadeDurationMillis < 0) {
                mBackgroundFadeDurationMillis = getWindowStyle().getInteger(
                        R.styleable.Window_windowTransitionBackgroundFadeDuration,
                        DEFAULT_BACKGROUND_FADE_DURATION_MS);
            }
            if (mSharedElementsUseOverlay == null) {
                mSharedElementsUseOverlay = getWindowStyle().getBoolean(
                        R.styleable.Window_windowSharedElementsUseOverlay, true);
            }
        }
    }
}

在重点1,生成一个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, this);
            if (mTheme != -1) {
                context.setTheme(mTheme);
            }
        }
    } else {
        context = getContext();
    }
    return new DecorView(context, featureId, this, getAttributes());
}

DecorView继承自FrameLayout,创建完DecorView之后,就调用了generateLayout(mDecor),去生成布局:

protected ViewGroup generateLayout(DecorView decor) {
    
	......
    // Inflate the window decor.

    int layoutResource;
    int features = getLocalFeatures();

    ......
    // 加载布局
    mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);

    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
    if (contentParent == null) {
        throw new RuntimeException("Window couldn't find content container view");
    }

    ......

    return contentParent;
}

这段代码很清晰了,mDecor.onResourcesLoaded(mLayoutInflater, layoutResource)使用Inflater去加载layoutResource,这个资源放在framework下,是一个LinearLayout布局的文件,其中包含ActionBar和ContentView,这个ContentView的id是@android:id/content,也是FrameLayout布局,在这段代码中也是通过findViewById绑定了这个View作为contentParent使用。

installDecor()方法介绍完之后,接着便有这样一行代码mLayoutInflater.inflate(layoutResID, mContentParent),第一个参数就是我们传入的自己的布局文件,也是我们最初在调用setContentView时传入的布局,mContentParent不用我多说,就是刚刚DecorView中的Content,这样,DecorView就作为我们的根布局,加载进了我们的界面,同时还加载了我们自己编写的精美的界面。一切都顺理成章,只剩两个问题,PhoneWindow从何而来?打开App怎么就回调了setContentView方法?

2.PhoneWindow从何而来

PhoneWindow从何而来不着急,当务之急是先找到setContentView是如何被回调的,否则以上的分析都是镜中月、水中花。setContentView是在我们回调onCreate不久之后就被调用了,所以找到onCreate方法在哪里被调用就好。对Android有一定了解的同学都知道,Android中有一个很重要的类是ActivityThread,这个类可以说是管理着Android中四大组件的生命周期,Activity中onCreate方法,一定是在这个类里被回调。前面我写过一篇针对Activity的文章,里面有提到Activity的启动流程,谈及Activity中生命周期函数被调用的流程。

具体流程就不再这里分析了,我们直接看handleLaunchActivity方法,这个方法就是启动Activity时回调的:

@Override
public Activity handleLaunchActivity(ActivityClientRecord r,
        PendingTransactionActions pendingActions, Intent customIntent) {
    // If we are getting ready to gc after going to the background, well
    // we are back active so skip it.
    unscheduleGcIdler();
    mSomeActivitiesChanged = true;

    if (r.profilerInfo != null) {
        mProfiler.setProfiler(r.profilerInfo);
        mProfiler.startProfiling();
    }

    // Make sure we are running with the most recent config.
    handleConfigurationChanged(null, null);

    if (localLOGV) Slog.v(
        TAG, "Handling launch of " + r);

    // Initialize before creating the activity
    if (!ThreadedRenderer.sRendererDisabled
            && (r.activityInfo.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
        HardwareRenderer.preload();
    }
    WindowManagerGlobal.initialize();

    // Hint the GraphicsEnvironment that an activity is launching on the process.
    GraphicsEnvironment.hintActivityLaunch();

    final Activity a = performLaunchActivity(r, customIntent);

    if (a != null) {
        r.createdConfig = new Configuration(mConfiguration);
        reportSizeConfigurations(r);
        if (!r.activity.mFinished && pendingActions != null) {
            pendingActions.setOldState(r.state);
            pendingActions.setRestoreInstanceState(true);
            pendingActions.setCallOnPostCreate(true);
        }
    } else {
        // If there was an error, for any reason, tell the activity manager to stop us.
        try {
            ActivityTaskManager.getService()
                    .finishActivity(r.token, Activity.RESULT_CANCELED, null,
                            Activity.DONT_FINISH_TASK_WITH_ACTIVITY);
        } catch (RemoteException ex) {
            throw ex.rethrowFromSystemServer();
        }
    }

    return a;
}

performLaunchActivity:

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    ActivityInfo aInfo = r.activityInfo;
    if (r.packageInfo == null) {
        r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo,
                Context.CONTEXT_INCLUDE_CODE);
    }

    ComponentName component = r.intent.getComponent();
    if (component == null) {
        component = r.intent.resolveActivity(
            mInitialApplication.getPackageManager());
        r.intent.setComponent(component);
    }

    if (r.activityInfo.targetActivity != null) {
        component = new ComponentName(r.activityInfo.packageName,
                r.activityInfo.targetActivity);
    }

    ContextImpl appContext = createBaseContextForActivity(r);
    Activity activity = null;
    try {
        java.lang.ClassLoader cl = appContext.getClassLoader();
        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) {
        if (!mInstrumentation.onException(activity, e)) {
            throw new RuntimeException(
                "Unable to instantiate activity " + component
                + ": " + e.toString(), e);
        }
    }

    try {
        Application app = r.packageInfo.makeApplication(false, mInstrumentation);

        if (localLOGV) Slog.v(TAG, "Performing launch of " + r);
        if (localLOGV) Slog.v(
                TAG, r + ": app=" + app
                + ", appName=" + app.getPackageName()
                + ", pkg=" + r.packageInfo.getPackageName()
                + ", comp=" + r.intent.getComponent().toShortString()
                + ", dir=" + r.packageInfo.getAppDir());

        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;
            }

            // Activity resources must be initialized with the same loaders as the
            // application context.
            appContext.getResources().addLoaders(
                    app.getResources().getLoaders().toArray(new ResourcesLoader[0]));

            appContext.setOuterContext(activity);
            // 重点1
            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,
                    r.assistToken);

            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;
            // 重点2
            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;
            mLastReportedWindowingMode.put(activity.getActivityToken(),
                    config.windowConfiguration.getWindowingMode());
        }
        r.setState(ON_CREATE);

        // updatePendingActivityConfiguration() reads from mActivities to update
        // ActivityClientRecord which runs in a different thread. Protect modifications to
        // mActivities to avoid race.
        synchronized (mResourcesManager) {
            mActivities.put(r.token, r);
        }

    } catch (SuperNotCalledException e) {
        throw e;

    } catch (Exception e) {
        if (!mInstrumentation.onException(activity, e)) {
            throw new RuntimeException(
                "Unable to start activity " + component
                + ": " + e.toString(), e);
        }
    }

    return activity;
}

在1处,我们调用了activity.attach方法:

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, IBinder assistToken) {
    attachBaseContext(context);

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

    mWindow = new PhoneWindow(this, window, activityConfigCallback);
    ......
}

进入这个方法不久,我们就把PhoneWindow给new出来了。这就解决了我们的一个问题,PhoneWindow从何而来。第二个问题onCreate何时回调,我们继续看代码,在重点2处,看到了这样的代码mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState),进去看看:

public void callActivityOnCreate(Activity activity, Bundle icicle,
        PersistableBundle persistentState) {
    prePerformCreate(activity);
    activity.performCreate(icicle, persistentState);
    postPerformCreate(activity);
}

回调Activity的performCreate,最后会回调我们的onCreate方法。这样我们的setContentView方法也就会被调用。

我们现在已经知道了PhoneWindow有内部类DecorView,是FramLayout布局,PhoneWindow在onCreate方法之前被创建好,DecorView会通过inflater在Framework层绑定一个线性布局作为我们的根布局,我们自己编写的布局会绑定到content中。这样View树就形成了,可以进行后续的绘制操作。

3.绘制入口:ViewRootImpl

我们的View要进行绘制,就要执行View的三大方法:measure、layout、draw。而DecorView是我们的根View,理论上将所有的View的绘制都从它开始。那DecorView的这三大方法又由谁来掉用呢?由ViewRootImpl调用。下面我们从源码验证一下。

当我们的onResume方法调用完成之后,我们的页面就呈现在我们面前,(Google开发者文档中写到哦那Start方法调用使Activity对用户可见,我查看源码并没有发现对View的绘制工作,反而在onResume之后找到了它。)前面提到ActivityThread中管理了Activity的生命周期,我们在其中找到了这样一段代码:

public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
        String reason) {
    // If we are getting ready to gc after going to the background, well
    // we are back active so skip it.
    unscheduleGcIdler();
    mSomeActivitiesChanged = true;

    // TODO Push resumeArgs into the activity for consideration
    // 进入onResume
    final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
    if (r == null) {
        // We didn't actually resume the activity, so skipping any follow-up actions.
        return;
    }
    if (mActivitiesToBeDestroyed.containsKey(token)) {
        // Although the activity is resumed, it is going to be destroyed. So the following
        // UI operations are unnecessary and also prevents exception because its token may
        // be gone that window manager cannot recognize it. All necessary cleanup actions
        // performed below will be done while handling destruction.
        return;
    }

    final Activity a = r.activity;

    if (localLOGV) {
        Slog.v(TAG, "Resume " + r + " started activity: " + a.mStartedActivity
                + ", hideForNow: " + r.hideForNow + ", finished: " + a.mFinished);
    }

    final int forwardBit = isForward
            ? WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION : 0;

    // If the window hasn't yet been added to the window manager,
    // and this guy didn't finish itself or start another activity,
    // then go ahead and add the window.
    boolean willBeVisible = !a.mStartedActivity;
    if (!willBeVisible) {
        try {
            willBeVisible = ActivityTaskManager.getService().willActivityBeVisible(
                    a.getActivityToken());
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }
    
    // 重点1
    if (r.window == null && !a.mFinished && willBeVisible) {
        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;
        l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
        l.softInputMode |= forwardBit;
        if (r.mPreserveWindow) {
            a.mWindowAdded = true;
            r.mPreserveWindow = false;
            // Normally the ViewRoot sets up callbacks with the Activity
            // in addView->ViewRootImpl#setView. If we are instead reusing
            // the decor view we have to notify the view root that the
            // callbacks may have changed.
            ViewRootImpl impl = decor.getViewRootImpl();
            if (impl != null) {
                impl.notifyChildRebuilt();
            }
        }
        if (a.mVisibleFromClient) {
            if (!a.mWindowAdded) {
                a.mWindowAdded = true;
                wm.addView(decor, l);
            } 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;
    }

    // Get rid of anything left hanging around.
    cleanUpPendingRemoveWindows(r, false /* force */);

    // The window is now visible if it has been added, we are not
    // simply finishing, and we are not starting another activity.
    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;
        }
        if (localLOGV) Slog.v(TAG, "Resuming " + r + " with isForward=" + isForward);
        ViewRootImpl impl = r.window.getDecorView().getViewRootImpl();
        WindowManager.LayoutParams l = impl != null
                ? impl.mWindowAttributes : 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);
            }
        }

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

    r.nextIdle = mNewActivities;
    mNewActivities = r;
    if (localLOGV) Slog.v(TAG, "Scheduling idle handler for " + r);
    Looper.myQueue().addIdleHandler(new Idler());
}

我们看到,进入这个方法不久后就调用了onResume,之后就走到了重点1,从PhoneWindow中取出DecorView,赋值给decor,然后创建出ViewManager,调用了ViewManager的addView方法,这个类由WindowManagerImpl实现:

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

调用WindowManagerGlobal的addView方法:

public void addView(View view, ViewGroup.LayoutParams params,
        Display display, Window parentWindow, int userId) {
    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");
    }

    final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
    if (parentWindow != null) {
        parentWindow.adjustLayoutParamsForSubWindow(wparams);
    } else {
        // If there's no parent, then hardware acceleration for this view is
        // set from the application's hardware acceleration setting.
        final Context context = view.getContext();
        if (context != null
                && (context.getApplicationInfo().flags
                        & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
            wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
        }
    }

    ViewRootImpl root;
    View panelParentView = null;

    synchronized (mLock) {
        // Start watching for system property changes.
        if (mSystemPropertyUpdater == null) {
            mSystemPropertyUpdater = new Runnable() {
                @Override public void run() {
                    synchronized (mLock) {
                        for (int i = mRoots.size() - 1; i >= 0; --i) {
                            mRoots.get(i).loadSystemProperties();
                        }
                    }
                }
            };
            SystemProperties.addChangeCallback(mSystemPropertyUpdater);
        }

        int index = findViewLocked(view, false);
        if (index >= 0) {
            if (mDyingViews.contains(view)) {
                // Don't wait for MSG_DIE to make it's way through root's queue.
                mRoots.get(index).doDie();
            } else {
                throw new IllegalStateException("View " + view
                        + " has already been added to the window manager.");
            }
            // The previous removeView() had not completed executing. Now it has.
        }

        // If this is a panel window, then find the window it is being
        // attached to for future reference.
        if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
                wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
            final int count = mViews.size();
            for (int i = 0; i < count; i++) {
                if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
                    panelParentView = mViews.get(i);
                }
            }
        }
		// 1.
        root = new ViewRootImpl(view.getContext(), display);

        view.setLayoutParams(wparams);

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

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

这个类管理这所有的View与ViewRootImpl,我们在2处也可以看出来,它将创建出的DecorView和ViewRootImpl添加到了内部的ArrayList中。在3处,我们调用了ViewRootImpl的setView方法:

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

            ......

            requestLayout();
            
            ......

            view.assignParent(this);
            
			......
        }
    }
}

这个方法相当长,我将里面最关键的两段代码留了下来,第一个就是requestLayout,第二个是view.assignParent(this),先说第二个,从名字就可以看出来,这个方法将ViewRootImpl设置为DecorView的parent,这样,DecorView的绘制就由ViewRootImpl调用。再来重点看requestLayout:

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

先经过checkThread():

void checkThread() {
    if (mThread != Thread.currentThread()) {
        throw new CalledFromWrongThreadException(
                "Only the original thread that created a view hierarchy can touch its views.");
    }
}

我们发现这个方法跟线程有关,线程?难道常说子线程能更新UI与这个方法有关?的确如此,通常我们都是在主线程调用WindowManagerGlobal的addView方法,生成的ViewRootImpl中的mThread自然也是主线程,一旦我们在子线程中更改UI,当requestLayout走到ViewRootImpl的requestLayout时自然报错。

因此,我们只要在程序走到view.assignParent(this)之前,DecorView还未将parent设置成ViewRootImpl,自然不会报错了。或者,在子线程中调用WindowManagerGlobal的addView方法去添加一个View,这时,自然也可以在子线程中更改UI。

接着继续将scheduleTraversals():

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

这里设置了同步屏障消息,它的作用是使下次Looper轮询的时候优先处理异步消息,所以这行代码之后就发送了一个异步消息给Handler(有关Handler的东西我会单独一篇博客来总结)。去处理了mTraversalRunnable这个Runnable接口的实现类:

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;
        }
    }
}

在这里移除同步屏障,防止一直阻塞Looper,接着调用performTraversals(),这个方法里会调用三大方法:performMeasure、performLayout、performDraw,每个方法里都会调用ViewRootImpl的measure、layout,draw,然后逐层调用完成绘制。

到此我也把最初那张图讲完了,文章开始的六个问题相信你也可以解决了,希望看完这篇文章的你有所收获,同时留下自己宝贵的意见。

Android自定义View之(一)View绘制流程详解——向源码要答案 前言 View作为整个app的颜值担当,在Android体系中占有重要的地位。深入理解Android View的绘制流程,对正确使用View来构建赏心悦目的外观,以及用自定义View来设计理想中的酷炫效果等方面,有着极其重要的帮助作用,所以将View的绘制流程作为自定义View系列文章的第一篇。当然,View的绘制流程原理,在现实的工作中是成为高级工程师路上必须克服的障碍;在面试中... 阅读详情

相关推荐

面试一问:关于 View测量、布局及绘制原理

前言 2020年2月22.距离新年已经过去了大半个月了,依旧的出不了门,依旧的躲在家里一日三餐,依旧的在家办公,也不知道下周会不会复工,再次汇总手中各种保存整理的笔记 相关内容后续GitHub更新,想冲击金三银四的小伙伴可以找找看看,欢迎star (顺手留下GitHub链接,需要获取相关面试等内容的可以自己去找) https://github.com/xiangjiana/Android-MS ...

Android_SE的博客 793

Android中View绘制流程

很多人在刚接触这个行业的时候或者是在遇到瓶颈期的时候,总会遇到一些问题,比如学了一段时间感觉没有方向感,不知道该从哪里入手去学习,对此我整理了一些资料如果你熟练掌握以下列出的知识点,相信将会大大增加你通过前两轮技术面试的几率!这些内容都供大家参考,互相学习。①「Android面试真题解析大全」PDF完整高清版+②「Android面试知识体系」学习思维导图压缩包加入社区》https://bbs.youkuaiyun.com/forums/4304bb5a486d4c3ab8389e65ecb71ac0。

2401_87029450的博客 946

AI市场风起云涌,ai浏览器是最佳的落地项目,现在ai市场的ai浏览器竞争加剧,得ai浏览器者得天下!

从场景覆盖度来看,AI 浏览器已突破 “网页浏览” 的传统边界,成为连接个人生活、工作、消费的 “超级入口”,这是其他 AI 应用难以比拟的落地优势。在个人生活场景,AI 浏览器可基于用户行为数据主动提供服务 —— 当用户浏览马拉松赛事信息时,浏览器能自动生成 “训练计划 + 装备推荐 + 能量补给方案”,并关联预订链接;在工作场景,Opera Neon 浏览器已实现 “网页内容提取 - 代码生成 - 报告撰写” 的全流程辅助,开发者可直接在浏览器内完成网站开发初稿;

zhang9880000的博客 1086

一文了解View的绘制

View的绘制原理

nahfang的博客 842

点云数据增强及预处理

数据增强: 1. random world flip:沿着x或者y轴,翻转点云 点云的x或者y,要翻转 3d box中心的x或者y,要翻转 3d box的朝向要翻转 point segmentation label,点云的相对顺序没有发生变化 2. random_world_rotation:沿着点云原点,旋转点云 点,x,y,绕z轴旋转 3d box,绕z轴旋转,中心和朝向都要变化 point segmentation label,点云的相对顺序没有发生变化 3. random_world_scaling

huang_victor的博客 4011

YonSuite如何新增用户并分配角色角色授权图文教程

用友YonSuite产品基础教学

ct341688的博客 622

文件管理搜不到Android 里的文件,Android:在原始文件夹中添加文件后窗口找不到内容容器视图...

正在获取窗口找不到内容容器视图。这个错误询问应用程序何时将检查权限Android:在原始文件夹中添加文件后窗口找不到内容容器视图我注意到,对于穿着应用程序,我正在将穿着apk复制到原始文件夹中。它有大约2.5 Mb。添加这个我得到这次崩溃java.lang.RuntimeException: Window couldn't find content container viewat com.and...

weixin_39930276的博客 948

Java:使用Java调用打印机进行打印(JPG、PDF和Word三种文件格式)

目录 一、Java的打印简介 二、Java打印实现 2.1 JPG图片文件格式打印实现 2.2 PDF文件格式打印实现 2.3 Word文件格式打印实现 2.3.1 Word文件采用jacob插件进行打印实现。 2.3.2 先将word转化为pdf文件,然后打印pdf(lz使用) 三、总结 一、Java的打印简介 在我们的实际工作中,经常需要实现打印功能。但由于历史原因,Jav...

vatxiongxiaohui的专栏 8万+

从源码探索Activity布局加载过程

众所周知,Activity是承载布局的重要组件,是通过setContentView()方法来设置xml布局文件的,那么我们来提出几个问题 1.布局文件到底是怎么加载到Activity并显示到屏幕的呢? 2.我们给activity设置了主题,它对应的样式代码是在哪设置的呢? 下面我们跟踪源码来探索一下。 首先,我们点开setContentView()方法 publi...

qq_27246079的博客 408

【STM32】433MHz OOK信号软解码实战:从EV1527协议到嵌入式实现

本文详细介绍了基于STM32微控制器实现433MHz OOK信号软解码的完整实战过程。文章从EV1527协议解析入手,重点阐述了利用STM32的GPIO和定时器进行信号采样、脉宽还原、同步头识别以及数据位判决的核心算法,并提供了对抗无线干扰的容错优化策略,帮助开发者深入理解并掌握低成本无线通信的嵌入式实现方案。

weixin_29299807的博客 786

QGIS实用功能:加载天地图与下载指定区域遥感影像

本文聚焦 QGIS 软件实用功能,详细介绍在 QGIS 中加载天地图及其他常用底图(如 Google Maps、Bing Maps)的完整流程,涵盖天地图插件安装、密钥获取与配置、插件拓展使用等操作。同时,以 上海市瑞金二路街道为例,分步讲解下载指定区域遥感影像的方法,包括图层选择、坐标系设置、分辨率调整等关键参数配置。此外,还介绍了影像精准裁剪技巧,为 GIS 数据获取与处理提供实用指导,助力用户高效使用 QGIS 开展地理信息相关工作。

weixin_43679228的博客 7449

setContentView那些事

刨根问底setContentView在平时的android开发中,经常会使用到Activity#setContentView方法来设置我们自己的布局,那么setContentView中到底做了什么,我们的布局 是怎么加载并显示到手机屏幕上的,这就是今天要讨论的内容,看下Activity#setContentView方法public void setContentView(@LayoutRes in

mockingbirds的专栏 2733

Ansys Lumerical实战:5步搞定Mach-Zehnder调制器多物理场仿真(附眼图分析技巧)

本文详细介绍了使用Ansys Lumerical进行行波马赫-曾德尔调制器多物理场仿真的完整流程。通过CHARGE、MODE和INTERCONNECT三大模块的协同工作,从电学参数提取、光学射频特性分析到系统级眼图评估,分步解析了高效仿真与性能优化的核心技巧,为硅光调制器设计提供了实战指南。

weixin_29056781的博客 546

Android View绘制流程

前言     在学习安卓的时候免不了和布局文件打交道,一直都想弄清楚内部究竟是个什么原理,于是有了这篇文章。     这篇文章形成与多个大神的文章集和,算是帮大家简单过滤一下关键信息,相信看完后能够基本上了解绘制流程是个什么样子。(无安卓基础的话可能有点难懂hhh) View绘制流程     View 的绘制流程分为三步:在自定义View的时候一般需要重写父类的onMeasure()、onLayout()、onDraw()三个方法,来完成视图的展示过程。当然,这三个暴露给开发者重写的方法只不过是整个绘制流程

weixin_41607932的博客 1万+

大数据Alluxio面试题及参考答案

Alluxio 在大数据生态里的定位是作为数据编排层,能在不同计算框架和存储系统之间起到桥梁的作用。它的核心功能在于加速数据访问,让大数据应用能够更高效地使用数据。Alluxio 把数据存储和数据处理分离开来,使计算框架可以在不考虑底层存储细节的情况下访问数据,这样就提高了系统的灵活性和可扩展性。Alluxio 和 HDFS 既存在关联又有区别。从关联方面来看,HDFS 是一种广泛使用的分布式文件系统,很多大数据应用都将其作为主要的数据存储系统。Alluxio 可以和 HDFS 集成,把 HDFS 当作底层

大模型大数据攻城狮的专栏 864

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

View的绘制流程

View绘制的三大流程主要指:measure(测量)、layout(布局)、draw(绘制)。measure过程中确定View的尺寸(即宽高),layout过程中确定View的位置(即上下左右的位置),draw过程确定View显示的内容。在开发中,通常都是在Activity的onCreate()中调用setContentView(R.layout.custom_layout)来实现想要的页面布局。...

走向远方 2373

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

Android View绘制流程详解

除了使用系统提供的View,我们还可以自定义View来实现特定的绘制效果。自定义View的绘制过程与系统View的绘制流程类似,只需要在onLayout()和onDraw()方法中实现自己的逻辑即可。自定义View的绘制过程可以通过以下步骤来完成:重写方法,根据自己的需求计算出View的测量宽度和高度。重写onLayout()方法,根据测量结果和父容器的布局参数,确定View在父容器中的位置和大小。重写onDraw()方法,根据需要绘制自定义的内容。在onDraw()方法中,可以使用Canvas。

jiet_h的博客 2924

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

View绘制流程分析

想要弄清楚View是怎么绘制的得先弄明白View是怎么创建出来的。我们先来看下View的创建流程。总结如下所示:Activity执行onResume之后再ActivityThread中执行Activity的makeVisible方法。View的绘制流程包含了测量大小,测量位置,绘制三个流程;Activity的界面绘制是从mDoctor即根View开始的,也就是从mDoctor的测量大小,测量位置,绘制三个流程;

杨充 1289

Android View 绘制流程之四:绘制流程触发机制

Android View 绘制流程之四:绘制流程触发机制一.View状态的改变(包括Background的drawable状态改变)1.setEnable()2.setPressed()3.setSelected()4.refreshDrawableState()二.View焦点的改变1.findFoucs()2.getFocusedChild()3.hasFocus()/hasFocusable...

依生依世 2190

12_View的绘制流程

12_View的绘制流程 一.View是如何被添加到屏幕窗口上的 当在Activity的onCreate方法中调用setContentView(R.layout.activity_main)时,会调用Activity的setContentView方法,在Activity的setContentView方法中会调用PhoneWindow的setContentView方法,在PhoneWindow的setContentView方法中会调用installDecor方法,installDecor方法会创建一个Dec

gzx的博客 2155

Android O: View的绘制流程(三):布局和绘制

View的布局和绘制

橡果生长记的博客 1589

Android View 绘制流程之二:layout布局

Android View 绘制流程之二:layout布局一.layout整体流程二.常见onLayout实现1.FrameLayout的onLayout2.LinearLayout的onLayout3.RelativeLayout的onLayout layout(int left,int top,int right,int bottom)方法是View测绘系统的第二步,布局过程,是在measure...

依生依世 723

Android View的绘制流程

Android View的绘制流程

YK 1603

谈谈 View 绘制流程

注:本文使用 sdk 23 作为源码参考。 前言 ViewRootImpl#performTraversals() ViewRootImpl#performMeasure() ViewRootImpl#performLayout() ViewRootImpl#performDraw() some tips onDraw() dispatchDraw() 前言 关于 View 的绘制流

joker 879

AndroidView绘制流程一(View添加流程)

在Activity中,调用setContentView()方法,即可将自定义的View或者布局资源xml文件,添加至界面,那么setContentView()方法具体执行了哪些操作,view又是如何添加上去的呢。 进入Activity的setContentView()方法,发现其调用了getWindow().setContentView()方法,getWindow()返回的是Window对象,而该...

lp598876762的博客 285
上一篇: 探究Fragment(内含懒加载实现)
下一篇: View绘制流程二:测量、布局、绘制
<天各一方>
博客等级 码龄6年 17粉丝 17原创
评论 4
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值