从布局文件到屏幕显示

在 Android App 的日常开发中,经常需要新建一个 Activity、定义一个布局文件,然后在 Activity 的 onCreate 方法中利用 setContentView 方法渲染布局文件,然后就可以通过 findViewById 等方法取得各种 View 对象的引用。

那么,系统是如何将布局文件转换成对象并显示到屏幕上的?

叫你学android

布局文件到对象

上面我们说过,通过 setContentView 方法将布局文件交给了系统,开始了渲染之旅。经过几层的代理调用,最终执行到 AppCompatDelegateImplV9.javasetContentView方法,调用了 LayoutInflater 将布局文件渲染成 view 树。

@Override
public void setContentView(int resId) {
    ensureSubDecor();
    ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
    contentParent.removeAllViews();
    LayoutInflater.from(mContext).inflate(resId, contentParent);
    mOriginalWindowCallback.onContentChanged();
}

在 LayoutInflater 中,使用 XmlPullParser 读取 xml 布局文件,循环读取 view 类名,利用反射技术实例化对应的对象。

LayoutInlater.java

public final View createView(String name, String prefix, AttributeSet attrs) throws ClassNotFoundException, InflateException {
    Constructor<? extends View> constructor = sConstructorMap.get(name);
    if (constructor != null && !verifyClassLoader(constructor)) {
        constructor = null;
        sConstructorMap.remove(name);
    }
    Class<? extends View> clazz = null;

    if (constructor == null) {
            clazz = mContext.getClassLoader().loadClass(
                    prefix != null ? (prefix + name) : name).asSubclass(View.class);

            if (mFilter != null && clazz != null) {
                boolean allowed = mFilter.onLoadClass(clazz);
                if (!allowed) {
                    failNotAllowed(name, prefix, attrs);
                }
            }
            constructor = clazz.getConstructor(mConstructorSignature);
            constructor.setAccessible(true);
            sConstructorMap.put(name, constructor);
        }

        if (mConstructorArgs[0] == null) {
            mConstructorArgs[0] = mContext;
        }
        Object[] args = mConstructorArgs;
        args[1] = attrs;

        final View view = constructor.newInstance(args);
        return view;
    }
}

执行完 setContentView 后,整个 View 树都被实例化, 等待 onCreate 方法执行完成,回到 ActivityThread.java 中的 handleLaunchActivity 方法中,执行 handleResumeActivity 部分:

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
    handleConfigurationChanged(null, null);
    WindowManagerGlobal.initialize();

    Activity a = performLaunchActivity(r, customIntent);

    if (a != null) {
        r.createdConfig = new Configuration(mConfiguration);
        reportSizeConfigurations(r);
        Bundle oldState = r.state;

        handleResumeActivity(r.token, false, r.isForward,
                !r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);

        if (!r.activity.mFinished && r.startsNotResumed) {
            performPauseActivityIfNeeded(r, reason);
        }
    } else {
        try {
            ActivityManager.getService()
                .finishActivity(r.token, Activity.RESULT_CANCELED, null,
                        Activity.DONT_FINISH_TASK_WITH_ACTIVITY);
        } catch (RemoteException ex) {
            throw ex.rethrowFromSystemServer();
        }
    }
}

在这部分代码中,通过一系列的调用,最终会在 ViewRootImpl.javasetView 方法将布局添加到 ViewRootImp 中,然后调用 requestLayout 开始执行显示过程。

setContentView时序图

对象到显示

在白纸上画图需要回答三个问题:

经过上面的步骤,我们已经有了整个 view 树,并且调用了requestLayout 向主线程发送了一个“开始绘制”的信号。

ViewRootImpl.java

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

目标代码在 mTraversalRunnable 中:

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

再经过一系列的调用之后,到达一个800多行的方法 performTraversals 中。

在这个方法中,会先后调用 performMeasureperformLayoutperformDraw

这三个方法又分别执行了:

mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
final View host = mView;
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
draw(fullRedrawNeeded);

至此,程序将在整个 view 树依次执行我们自定义 view 时必不可少的 onMeasureonLayoutonDraw 方法。

performTraversals

ps: 为了展示简便,代码和时序图有删减。

评论

退出登录