关于android:总结UI原理和高级的UI优化方式

43次阅读

共计 50623 个字符,预计需要花费 127 分钟才能阅读完成。

不晓得 UI 原理如何做 UI 优化?

本文内容分为三个局部,UI 原理、LayoutInflater 原理、UI 优化,篇幅有点长,能够抉择本人喜爱的章节进行浏览,每一个局部最初都有小结。

置信大家多多少少看过一些 Activity 启动源码剖析的文章,也能大略说出Activity 启动流程,例如这种答复:

AMS 负责管理系统所有 Activity,所以利用 startActivity 最终会通过 Binder 调用到 AMS 的 startActivity 办法,AMS 启动一个 Activity 之前会做一些查看,例如权限、是否在清单文件注册等,而后就能够启动了,AMS 是一个零碎服务,在独自过程,所以要将生命周期通知利用,又波及到跨过程调用,这个跨过程同样采纳 Binder,媒介是通过 ActivityThread 的外部类 ApplicationThread,AMS 将生命周期跨过程传到 ApplicationThread,而后 ApplicationThread 再分发给 ActivityThread 外部的 Handler,这时候生命周期曾经回调到利用主线程了,回调 Activity 的各个生命周期办法。

还能够细分,比方Activity、Window、DecorView 之间的关系,这个其实也应该难度不大,又忽然想到,setContentView 为什么要放在 onCreate 中?,放在其它办法里行不行,能不能放在 onAttachBaseContext 办法里?其实,这些问题能够在源码中找到答案。

本文参考 Android 9.0 源码,API 28。

注释

写一个 Activity,咱们个别都是通过在 onCreate 办法中调用 setContentView 办法设置咱们的布局,有没有想过这个问题,设置完布局,界面就会开始绘制吗?

一、从生命周期源码剖析 UI 原理

Activity 启动流程大抵如下:

  1. Context -> startActivity
  2. AMS -> startActivity
  3. 过程不存在则告诉 Zygote 启动过程,启动完过程,执行 ActivityThread 的 main 办法,进入 loop 循环,通过 Handler 散发音讯。
  4. ApplicationThread -> scheduleLaunchActivity
  5. ActivityThread -> handleLaunchActivity
  6. 其它生命周期回调

从第 4 点开始剖析

1.1 ActivityThread

1.1.1 外部类 ApplicationThread

AMS 暂且先不剖析,AMS 启动 Activity 会通过 ApplicationThread 告诉到 ActivityThread,启动 Activity 从 ApplicationThread 开始说起,看下 scheduleLaunchActivity 办法

1.1.2 ApplicationThread#scheduleLaunchActivity

public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
            ActivityInfo info, Configuration curConfig, Configuration overrideConfig,
            CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,
            int procState, Bundle state, PersistableBundle persistentState,
            List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
            boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) {ActivityClientRecord r = new ActivityClientRecord();
        r.token = token;
        r.ident = ident;
        r.intent = intent;
        ...
        sendMessage(H.LAUNCH_ACTIVITY, r);
    }

sendMessage 最终封装一个 ActivityClientRecord 对象到 msg,调用mH.sendMessage(msg);,mH 是一个 Handler,间接看解决局部吧,

1.2 ActivityThread 的外部类 H

private class H extends Handler {public void handleMessage(Message msg) {switch (msg.what) {
            case LAUNCH_ACTIVITY: {Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
                final ActivityClientRecord r = (ActivityClientRecord) msg.obj;

                r.packageInfo = getPackageInfoNoCheck(r.activityInfo.applicationInfo, r.compatInfo);
                //1、从 msg.obj 获取 ActivityClientRecord 对象,调用 handleLaunchActivity 解决音讯
                handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
            } break;
            ...

    }


}

下面是 Handler 很根底的货色,应该都能看懂,正文 1,启动 Activity 间接进入 handleLaunchActivity 办法

1.3 ActivityThread#handleLaunchActivity

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
    ...
    // Make sure we are running with the most recent config.
    handleConfigurationChanged(null, null);

    // Initialize before creating the activity // 初始化 WindowManagerGlobal
    WindowManagerGlobal.initialize();

    //1. 启动一个 Activity,波及到创立 Activity 对象,最终返回 Activity 对象
    Activity a = Activity a = performLaunchActivity(r, customIntent);

    if (a != null) {r.createdConfig = new Configuration(mConfiguration);
        reportSizeConfigurations(r);
        Bundle oldState = r.state;
    //2. Activity 进入 onResume 办法
        handleResumeActivity(r.token, false, r.isForward,
                !r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);

        if (!r.activity.mFinished && r.startsNotResumed) {
        //3. 如果没能在前台显示,就进入 onPuse 办法
            performPauseActivityIfNeeded(r, reason);

        }
    } else {
        // If there was an error, for any reason, tell the activity manager to stop us.
    //4. activity 启动失败,则告诉 AMS finish 掉这个 Activity
        try {ActivityManagerNative.getDefault()
                .finishActivity(r.token, Activity.RESULT_CANCELED, null,
                        Activity.DONT_FINISH_TASK_WITH_ACTIVITY);
        } catch (RemoteException ex) {throw ex.rethrowFromSystemServer();
        }
    }
}

正文 1:performLaunchActivity,开始启动 Activity 了
正文 2:Activity 进入 Resume 状态,handleResumeActivity
正文 3:如果没能在前台显示,那么进入 pause 状态,performPauseActivityIfNeeded
正文 4,如果启动失败,告诉 AMS 去 finishActivity。

次要看 performLaunchActivity(1.3.1)和 handleResumeActivity(1.3.2)

1.3.1 ActivityThread#performLaunchActivity

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {

    //1 创立 Activity 对象
    activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);

    //2 调用 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, window);

    //3. 回调 onCreate
    mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
    
    //4. 回调 onStart
    activity.performStart();
    
    
    if (r.state != null || r.persistentState != null) {
    //5. 如果有保留状态,则调用 onRestoreInstanceState 办法,例如 Activity 被异样杀死,重写 onSaveInstanceState 保留的一些状态
        mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state,
                                r.persistentState);
    }

    //6. 回调 onPostCreate,这个办法根本没用过
    mInstrumentation.callActivityOnPostCreate(activity, r.state);

}

从这里能够看出 Activity 几个办法调用程序:

  1. Activity#attach
  2. Activity#onCreate
  3. Activity#onStart
  4. Activity#nnRestoreInstanceState
  5. Activity#onPostCreate

咱们次要来剖析 attach 办法 和 最相熟的 onCreate 办法

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) {
    //1、回调 attachBaseContext
    attachBaseContext(context);

    //2、创立 PhoneWindow
    mWindow = new PhoneWindow(this, window);
    mWindow.setWindowControllerCallback(this);
    mWindow.setCallback(this);
    mWindow.setOnWindowDismissedCallback(this);
    ...
    
    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;

attach 办法次要关注两点:
1. 会调用 attachBaseContext 办法;
2. 创立 PhoneWindow,赋值给 mWindow,这个前面会常常遇到。

2. Activity#onCreate

大家有没有思考过,为什么 setContentView 要放在 onCreate 办法中?
不能放在 onStart、onResume 中咱们大略是晓得的,因为按 Home 键再切回来会回调生命周期 onStart、onResume,setContentView 屡次调用是没必要的。那如果是放在 attachBaseContext 外面行不行?
看下 setContentView 外面的逻辑

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

public Window getWindow() {return mWindow;}

getWindow 返回的是 mWindow,mWindow 是在 Activity 的 attach 办法初始化的,下面刚刚剖析过 attach 办法,mWindow 是一个 PhoneWindow 对象。所以 setContentView 必须放在 attachBaseContext 之后。

4. PhoneWindow#setContentView
public void setContentView(int layoutResID) {if (mContentParent == null) {
    //1、创立 DecorView
        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 {
    //2、mContentParent 是 DecorView 中的 FrameLayout,将咱们的布局增加到这个 FrameLayout 里
        mLayoutInflater.inflate(layoutResID, mContentParent);
    }
    ...
}

installDecor 办法是创立 DecorView,看一下次要代码

正文 1:installDecor,创立根布局 DecorView,
正文 2:mLayoutInflater.inflate(layoutResID, mContentParent); 将 xml 布局渲染到 mContentParent 里,第二节会重点剖析 LayoutInflater 原理。

先看正文 1

5. PhoneWindow#installDecor
private void installDecor() {
    mForceDecorInstall = false;
    if (mDecor == null) {
    //1. 创立 DecorView
        mDecor = generateDecor(-1);
        mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
        mDecor.setIsRootNamespace(true);
        if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
        }
    } else {
    // 2. mDecor 不为空,就是创立过,只需设置 window
        mDecor.setWindow(this);
    }
    if (mContentParent == null) {//3. 找到 DecorView 内容局部,findViewById(ID_ANDROID_CONTENT)
        mContentParent = generateLayout(mDecor);

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

    //4. 找到 DecorView 的根 View,给标题栏局部,设置图标和题目啥的
        final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(R.id.decor_content_parent);

    //5. 设置题目图标啥的
        if (decorContentParent != null) {
            mDecorContentParent = decorContentParent;
            mDecorContentParent.setWindowCallback(getCallback());
            if (mDecorContentParent.getTitle() == null) {mDecorContentParent.setWindowTitle(mTitle);
            }

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

            ...
        } 
        
        ...
    }
}

installDecor 次要做了两件事,一个是创立 DecorView,一个是依据主题,填充 DecorView 中的一些属性,比方默认就是标题栏 + 内容局部

先看正文 1:generateDecor 创立 DecorView

6. PhoneWindow#generateDecor
protected DecorView generateDecor(int featureId) {
    ...
    return new DecorView(context, featureId, this, getAttributes());
}
7. DecorView 构造方法
DecorView(Context context, int featureId, PhoneWindow window,
        WindowManager.LayoutParams params) {super(context);
    ...

    updateAvailableWidth();

    // 设置 window
    setWindow(window);
}

创立 DecorView 传了一个 PhoneWindow 进去。

installDecor 办法外面正文 2,如果判断 DecorView 曾经创立过的状况下,间接调用mDecor.setWindow(this);

再看 installDecor 办法正文 3

if (mContentParent == null) {mContentParent = generateLayout(mDecor);
        ...

给 mContentParent 赋值,看下 generateLayout 办法

8. PhoneWindow#generateLayout
 protected ViewGroup generateLayout(DecorView decor) {// 1. 窗口各种属性设置,例如 requestFeature(FEATURE_NO_TITLE);
    ...
    if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {requestFeature(FEATURE_NO_TITLE);
    }
    ...
    if (a.getBoolean(R.styleable.Window_windowFullscreen, false)) {setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN & (~getForcedWindowFlags()));
    }
    ...
    
    //2. 获取 windowBackground 属性,默认窗口背景
    if (mBackgroundDrawable == null) {if (mBackgroundResource == 0) {
                mBackgroundResource = a.getResourceId(R.styleable.Window_windowBackground, 0);
            }
            ...
    }

    ...
    // 3. 获取 DecorView 外面 id 为 R.id.content 的布局
    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
    ...
    if (getContainer() == null) {
    //4. 在正文 2 的时候曾经获取了窗口背景属性 id,这里转换成 drawable,并且设置给 DecorView
        final Drawable background;
        if (mBackgroundResource != 0) {background = getContext().getDrawable(mBackgroundResource);
        } else {background = mBackgroundDrawable;}
        mDecor.setWindowBackground(background);

        

    }


    return contentParent;

}

正文 1:首先是窗口属性设置,例如咱们的主题属性设置了没有题目,则走:requestFeature(FEATURE_NO_TITLE);, 全屏则走setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN & (~getForcedWindowFlags()));

还有其余很多属性判断,这里只列出两个代表性的,为什么说代表性呢,因为我咱们平时要让 Activity 全屏,去掉标题栏,能够在主题里设置对应属性,还能够通过代码设置,也就是在 onCreate 办法里 setContentView 之前调用

getWindow().requestFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN);

正文 2:获取窗口背景属性 id,这个 id 就是 style.xml 外面咱们定义的主题下的一个属性

<style name="AppThemeWelcome" parent="Theme.AppCompat.Light.DarkActionBar">
    <item name="colorPrimary">@color/colorPrimary</item>
    ...

    <item name="android:windowBackground">@mipmap/logo</item> // 就是这个窗口背景属性

</style>

正文 3,这个 ID\_ANDROID\_CONTENT 定义在 Window 中,
public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content; 找到 id 对应的 ViewGroup 叫 contentParent,最终是要返回回去。

正文 4:在正文 2 的时候曾经获取了窗口背景属性 id,这里转换成 drawable,并且设置给 DecorView,DecorView 是一个 FrameLayout,所以咱们在主题中设置的默认背景图,最终是设置给 DecorView

generateLayout 办法剖析完了,次要做的事件是读取主题里配置的一堆属性,而后给 DecorView 设置一些属性,例如背景,还有获取 DecorView 里的内容布局,作为办法返回值。

回到 installDecor 办法正文 4,填充 DecorContentParent 内容

9. 填充 DecorContentParent
// 这个是 DecorView 的外层布局,也就是 titlebar 那一层
final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(R.id.decor_content_parent);

        if (decorContentParent != null) {
    
            mDecorContentParent = decorContentParent;
            mDecorContentParent.setWindowCallback(getCallback());
            
            // 有该属性的话就进行相干设置,例如题目、图标
            mDecorContentParent.setWindowTitle(mTitle);
            ...
            mDecorContentParent.setIcon(mIconRes);
            ...
            mDecorContentParent.setIcon(getContext().getPackageManager().getDefaultActivityIcon());
            ...
            mDecorContentParent.setLogo(mLogoRes);
            ...

这是 installDecor 的最初一步,设置题目、图标等属性。

setContentView 如果发现 DecorView 不存在则创立 DecorView,当初 DecorView 创立实现,那么要将咱们写的布局增加到 DecorView 中去了,怎么做呢,看 PhonwWPhonwWindowindow#setContentView 办法的正文 2,mLayoutInflater.inflate(layoutResID, mContentParent);,这个 mContentParent 下面曾经剖析过了,是 generateLayout 办法返回的,就是 DecorView 外面 id 为 content 的布局。

所以接下来的一步 LayoutInflater.inflate 就是将咱们写的布局增加到 DecorView 的内容局部,怎么增加?这个就波及到解析 xml,转换成对应的 View 对象了,怎么转换?

LayoutInflater 原理放在第二节,这样章节比拟清晰。mLayoutInflater.inflate(layoutResID, mContentParent);执行完,xml 布局就被解析成 View 对象,增加到 DecorView 中去了。画一张图

此时 DecorView 并不能显示到屏幕,因为 PhoneWindow 并没有被应用,只是筹备好了而已。1.3 持续剖析第二点

1.3.2 ActivityThread#handleResumeActivity

final void handleResumeActivity(IBinder token,
        boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {ActivityClientRecord r = mActivities.get(token);            
    ...
    //1、先回调 Activity 的 onResume 办法
    r = performResumeActivity(token, clearHide, reason);

    if (r != null) {
        final Activity a = r.activity;

        ...
        if (r.window == null && !a.mFinished && willBeVisible) {r.window = r.activity.getWindow();
            View decor = r.window.getDecorView();
            decor.setVisibility(View.INVISIBLE);
            //2、wm 是以后 Activity 的 WindowManager
            ViewManager wm = a.getWindowManager();
            WindowManager.LayoutParams l = r.window.getAttributes();
            a.mDecor = decor;
            ...
            if (a.mVisibleFromClient && !a.mWindowAdded) {
                a.mWindowAdded = true;
                // 3. 增加 decor 到 WindowManager
                wm.addView(decor, l);
            }

次要关注的就是下面的代码,
正文 1:performResumeActivity,次要是回调 Activity 的 onResume 办法。
正文 2:从 Activity 开始跟踪这个 wm,其实是 WindowManagerImpl,
正文 3:调用 WindowManagerImpl 的 addView 办法,最终调用的是 WindowManagerGlobal 的 addView 办法,将 DecorView 增加到 WindowManagerGlobal

从这个程序能够看出,在 Activity 的 onResume 办法回调之后,才会将 decorView 增加到 window,看下 addView 的逻辑

1 WindowManagerImpl#addView
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
...    
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {applyDefaultToken(params);
    mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}

WindowManagerGlobal#addView

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


    ViewRootImpl root;
    View panelParentView = null;

    synchronized (mLock) {
        ... 
    //1、创立 ViewRootImpl
        root = new ViewRootImpl(view.getContext(), display);

        view.setLayoutParams(wparams);

    //2、增加到 ArrayList 进行治理
        mViews.add(view);
    //3.mRoots 寄存 ViewRootImpl 对象
        mRoots.add(root);
        mParams.add(wparams);
    }

    // do this last because it fires off messages to start doing things
    try {
    // 4. 调用 ViewRootImpl 的 setView
        root.setView(view, wparams, panelParentView);
    }

入参 view 是 DecorView
正文 1:创立 ViewRootImpl,
正文 2:将 decorView 增加进到 ViewRootImpl 中去,ViewRootImpl 和 DecorView 的关系建设。
正文 3:将 ViewRootImpl 保留到 ArrayList
正文 4:调用 ViewRootImpl 的 setView 办法,这个很要害。

ViewRootImpl 的 setView 办法,第一个参数是 DecorView,第二个参数是 PhoneWindow 的属性,第三个参数是父窗口,如果以后是子窗口,那么须要传父窗口,否则传 null,默认 null 即可。

看晕了没,从新梳理一下 resume 的整个流程:

  1. ActivityThread 调用 handleResumeActivity(这个办法源头是 AMS 回调的)
  2. 回调 Activity 的 onResume
  3. 将 DecorView 增加到 WindowManagerGlobal
  4. 创立 ViewRootImpl,调用 setView 办法,跟 DecorView 绑定

接下来次要关注 ViewRootImpl 的 setView 办法

1.4 ViewRootImpl 剖析

看下构造方法

1.4.1 ViewRootImpl 结构

public ViewRootImpl(Context context, Display display) {
    mContext = context;
    //1、WindowSession 是 IWindowSession,binder 对象,可跨过程跟 WMS 通信
    mWindowSession = WindowManagerGlobal.getWindowSession();
    mDisplay = display;
    ...
    // 2、创立 window,是一个 binder 对象
    mWindow = new W(this);
    ...
    //3、mAttachInfo 很相熟吧,在这里创立的,View.post 跟这个非亲非故
    mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this);
    ...
    //4、Choreographer 在这里初始化
    mChoreographer = Choreographer.getInstance();
    mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
    loadSystemProperties();}

正文 1:创立 mWindowSession,这是一个 Binder 对象,能够跟 WMS 通信。
正文 2:mWindow 实例化,次要是接管窗口状态扭转的告诉。
正文 3:mAttachInfo 创立,view.post(runable) 能获取宽高的问题跟这个 mAttachInfo 的创立无关。
正文 4:mChoreographer 初始化,之前文章讲过 Choreographer,不再反复讲。

mWindowSession 的创立简略来看一下,因为接下来很多中央都要用到它来跟 WMS 通信。

1.4.2 mWindowSession 是干嘛的

mWindowSession = WindowManagerGlobal.getWindowSession();

看下 WindowManagerGlobal 是怎么创立 Session 的

1. WindowManagerGlobal.getWindowSession()
public static IWindowSession getWindowSession() {synchronized (WindowManagerGlobal.class) {if (sWindowSession == null) {
            try {InputMethodManager imm = InputMethodManager.getInstance();
                // 这里获取的是 WMS 的 Binder 代理
                IWindowManager windowManager = getWindowManagerService();
                // 通过代理调用 WMS 的 openSession 办法返回一个 Session(也是一个 Binder 代理)sWindowSession = windowManager.openSession(new IWindowSessionCallback.Stub() {
                            @Override
                            public void onAnimatorScaleChanged(float scale) {ValueAnimator.setDurationScale(scale);
                            }
                        },
                        imm.getClient(), imm.getInputContext());
            } catch (RemoteException e) {throw e.rethrowFromSystemServer();
            }
        }
        return sWindowSession;
    }
}

拿到 WMS 的 binder 代理,通过代理调用 WMS 的 openSession 办法。看下 WMS 的 openSession 办法

2. WindowManagerService#openSession
public IWindowSession openSession(IWindowSessionCallback callback, IInputMethodClient client,
        IInputContext inputContext) {if (client == null) throw new IllegalArgumentException("null client");
    if (inputContext == null) throw new IllegalArgumentException("null inputContext");
    // 创立一个 Session,把 WMS 传给 Session,Session 就有 WMS 的援用
    Session session = new Session(this, callback, client, inputContext);
    return session;
}

创立一个 Session 返回,它也是一个 Binder 对象,领有跨过程传输能力

3. Session
final class Session extends IWindowSession.Stub
    implements IBinder.DeathRecipient {

通过剖析,mWindowSession 是一个 领有跨过程能力的 Session 对象,领有 WMS 的援用,前面 ViewRootImpl 跟 WMS 交互都是通过这个 mWindowSession

看下 ViewRootImpl 的 setView 办法

1.4.2 ViewRootImpl#setView

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {synchronized (this) {if (mView == null) {
        // 1.DecorView 赋值给 mView
            mView = view;
            ...
        //2. 会调用 requestLayout
            requestLayout();
            ...
            try {
                mOrigWindowType = mWindowAttributes.type;
                mAttachInfo.mRecomputeGlobalAttributes = true;
                collectViewAttributes();
        //3.WindowSession, 将 window 增加到屏幕,有返回值
                res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                        getHostVisibility(), mDisplay.getDisplayId(),
                        mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                        mAttachInfo.mOutsets, mInputChannel);
            } 
            ...
        //4. 增加 window 失败解决
            if (res < WindowManagerGlobal.ADD_OKAY) {
                ...
                switch (res) {
                    case WindowManagerGlobal.ADD_BAD_APP_TOKEN:
                    case WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN:
                        throw new WindowManager.BadTokenException(
                                "Unable to add window -- token" + attrs.token
                                + "is not valid; is your activity running?");
                    case WindowManagerGlobal.ADD_NOT_APP_TOKEN:
                        throw new WindowManager.BadTokenException(
                                "Unable to add window -- token" + attrs.token
                                + "is not for an application");
                    case WindowManagerGlobal.ADD_APP_EXITING:
                        throw new WindowManager.BadTokenException(
                                "Unable to add window -- app for token" + attrs.token
                                + "is exiting");
                    case WindowManagerGlobal.ADD_DUPLICATE_ADD:
                        throw new WindowManager.BadTokenException(
                                "Unable to add window -- window" + mWindow
                                + "has already been added");
                    case WindowManagerGlobal.ADD_STARTING_NOT_NEEDED:
                        // Silently ignore -- we would have just removed it
                        // right away, anyway.
                        return;
                    case WindowManagerGlobal.ADD_MULTIPLE_SINGLETON:
                        throw new WindowManager.BadTokenException("Unable to add window"
                                + mWindow + "-- another window of type"
                                + mWindowAttributes.type + "already exists");
                    case WindowManagerGlobal.ADD_PERMISSION_DENIED:
                        throw new WindowManager.BadTokenException("Unable to add window"
                                + mWindow + "-- permission denied for window type"
                                + mWindowAttributes.type);
                    case WindowManagerGlobal.ADD_INVALID_DISPLAY:
                        throw new WindowManager.InvalidDisplayException("Unable to add window"
                                + mWindow + "-- the specified display can not be found");
                    case WindowManagerGlobal.ADD_INVALID_TYPE:
                        throw new WindowManager.InvalidDisplayException("Unable to add window"
                                + mWindow + "-- the specified window type"
                                + mWindowAttributes.type + "is not valid");
                }
                throw new RuntimeException("Unable to add window -- unknown error code" + res);
            }

            ...
            //5. 这里,给 DecorView 设置 parent 为 ViewRootImpl
            view.assignParent(this);
            ...
        }
    }
}

setView 整顿如下:
正文 1. 给 mView 赋值为 DecorView
正文 2. 调用 requestLayout,被动发动绘制申请
正文 3. WindowSession 是一个 Binder 对象,mWindowSession.addToDisplay(…),将 PhoneWindow 增加到 WMS 去了
正文 4. 增加 window 失败的解决
正文 5:view.assignParent(this); 给 DecorView 设置 parent 为 ViewRootimpl

正文 2 调用 requestLayout,会申请 vsyn 信号,而后下一次 vsync 信号来的时候,会调用 performTraversals 办法,这个上一篇文章剖析过了,performTraversals 次要是执行 View 的 measure、layout、draw。

正文 3,mWindowSession.addToDisplay 这一句要重点剖析,

1.4.2.1 mWindowSession.addToDisplay

mWindowSession 的创立下面曾经剖析过了。

1.4.2.2 Session#addToDisplay
public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
        int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets,
        Rect outOutsets, InputChannel outInputChannel) {
    // 调用 WMS 的 addWindow
    return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
            outContentInsets, outStableInsets, outOutsets, outInputChannel);
}

Session 外面有 WMS 的利用,这里能够看到 addToDisplay 办法就是调用了 WMS 的 addWindow 办法。

1.4.2.3 WindowManagerService#addWindow

WMS 的 addWindow 办法代码比拟多,不打算详细分析,外部波及到窗口 权限校验、token 校验、创立窗口对象、增加窗口到 Windows 列表、确定窗口地位 等。读者能够自行查看源码。

到这里能够晓得的是,ViewRootImpl 跟 WMS 通信的媒介就是 Session:

ViewRootImpl -> Session -> WMS

回来 ViewRootImpl 的 setView 办法,正文 2 调用 requestLayout 办法,最终会申请 vsync 信号,而后等下一个 vsync 信号降临,就会通过 Handler 执行 performTraversals 办法。所以依照执行程序,performTraversals 办法是在增加 Window 之后才调用。

1.5 ViewRootImpl#performTraversals

private void performTraversals() {

    ... // 1、relayoutWindow 办法是将 mSurface 跟 WMS 关联
    relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
     ...
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    ...
    performLayout(lp, mWidth, mHeight);
     ...
    performDraw();}

performTraversals 外面执行 View 的三个办法,这个大家可能都晓得,可是怎么跟屏幕关联上的呢,performDraw 办法外面能找到答案~

看下 performDraw

private void performDraw() {draw(fullRedrawNeeded);
...
}


private boolean draw(boolean fullRedrawNeeded) {
        // 这个 mSurface 是在定义的时候 new 的 
        Surface surface = mSurface;
        // 只剖析软件绘制
        if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
                        scalingRequired, dirty, surfaceInsets)) {return false;}
        return useAsyncReport;
    }
  1. mSurface 在定义的时候就 new 了public final Surface mSurface = new Surface();,然而只是一个空壳,其实在 performTraversals 的正文 1 外面做了一件事,调用 relayoutWindow 这个办法
relayoutWindow
private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
            boolean insetsPending) throws RemoteException {

        ...

        int relayoutResult = mWindowSession.relayout(mWindow, mSeq, params,
                (int) (mView.getMeasuredWidth() * appScale + 0.5f),
                (int) (mView.getMeasuredHeight() * appScale + 0.5f), viewVisibility,
                insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, frameNumber,
                mWinFrame, mPendingOverscanInsets, mPendingContentInsets, mPendingVisibleInsets,
                mPendingStableInsets, mPendingOutsets, mPendingBackDropFrame, mPendingDisplayCutout,
                mPendingMergedConfiguration, mSurface);

        return relayoutResult;
    }

外面调用了 mWindowSession.relayout 办法,将 mSurface 传过来,间接看 Session 的 relayout 办法

public int relayout(IWindow window, int seq, WindowManager.LayoutParams attrs,
            int requestedWidth, int requestedHeight, int viewFlags, int flags, long frameNumber,
            Rect outFrame, Rect outOverscanInsets, Rect outContentInsets, Rect outVisibleInsets,
            Rect outStableInsets, Rect outsets, Rect outBackdropFrame,
            DisplayCutout.ParcelableWrapper cutout, MergedConfiguration mergedConfiguration,
            Surface outSurface) {
        // 调用 WMS 的 relayoutWindow 办法
        int res = mService.relayoutWindow(this, window, seq, attrs,
                requestedWidth, requestedHeight, viewFlags, flags, frameNumber,
                outFrame, outOverscanInsets, outContentInsets, outVisibleInsets,
                outStableInsets, outsets, outBackdropFrame, cutout,
                mergedConfiguration, outSurface);
        Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
        if (false) Slog.d(TAG_WM, "<<<<<< EXITING relayout to"
                + Binder.getCallingPid());
        return res;
    }

最终调用 WMS 的 relayoutWindow 办法,WMS 会对这个 mSurface 做一些事件,使得这个 mSurface 跟以后的 PhoneWindow 关联起来。

performDraw -> draw -> drawSoftware

持续看 ViewRootImpl 的 drawSoftware 办法

ViewRootImpl#drawSoftware
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
            boolean scalingRequired, Rect dirty, Rect surfaceInsets) {

        // Draw with software renderer.
        final Canvas canvas;
        //1、从 mSurface 获取一个 Canvas
        canvas = mSurface.lockCanvas(dirty);
        //2、View 绘制
        mView.draw(canvas);
        //3、绘制完提交到 Surfece,并且开释画笔
        surface.unlockCanvasAndPost(canvas);

    }

drawSoftware 办法的外围就三句代码:

  1. lockCanvas 从 Surface 获取一个 canvas。
  2. draw,用这个 canvas 去执行 View 的绘制。
  3. unlockCanvasAndPost,将绘制的内容提交到 Surface。

mView.draw(canvas); 大家都比拟相熟了,mView 是 DecorView,是一个 FrameLayout,这里就是遍历子 view,调用 draw 办法了,最终就是调用 Canvas.draw。
整个 View 树 draw 完,就会将绘制的内容提交到 Surface,之后这个 Surface 会被 SurfaceFlinger 治理。

解读一下这种图:

  • Surface:每个 View 都是由某一个窗口治理,每个窗口关联一个 Surface
  • Canvas:通过 Surface 的 lockCanvas 办法获取一个 Canvas,Canvas 能够了解为 Skia 底层接口的封装,调用 Canvas 的 draw 办法,相当于调用 Skia 库的绘制办法。
  • Graphic Buffer:SurfaceFlinger 会帮咱们托管一个 Buffer Queue,咱们从 Buffer Queue 获取一个 Graphic Buffer,而后通过 Canvas 以及 Skia 将绘制的数据栅格化到下面。
  • SurfaceFlinger:通过 Swap Buffer 把前台 Graphic Buffer 提交给 SurfaceFlinger,最初通过硬件合成器 Hardware Composer 合成并输入到显示屏。

UI 原理总结

  1. Activity 的 attach 办法里创立 PhoneWindow。
  2. onCreate 办法里的 setContentView 会调用 PhoneWindow 的 setContentView 办法,创立 DecorView 并且把 xml 布局解析而后增加到 DecorView 中。
  3. 在 onResume 办法执行后,会创立 ViewRootImpl,它是最顶级的 View,是 DecorView 的 parent,创立之后会调用 setView 办法。
  4. ViewRootImpl 的 setView 办法,会将 PhoneWindow 增加到 WMS 中,通过 Session 作为媒介。setView 办法外面会调用 requestLayout,发动绘制申请。
  5. requestLayout 一旦发动,最终会调用 performTraversals 办法,外面将会调用 View 的三个 measure、layout、draw 办法,其中 View 的 draw 办法须要一个传一个 Canvas 参数。
  6. 最初剖析了软件绘制的原理,通过 relayoutWindow 办法将 Surface 跟以后 Window 绑定,通过 Surface 的 lockCanvas 办法获取 Surface 的的 Canvas,而后 View 的绘制就通过这个 Canvas,最初通过 Surface 的 unlockCanvasAndPost 办法提交绘制的数据,最终将绘制的数据交给 SurfaceFlinger 去提交给屏幕显示。

二、LayoutInflater 原理

后面剖析 setContentView 的时候,最终会调用 mLayoutInflater.inflate(layoutResID, mContentParent); 将布局文件的 View 增加到 DecorView 的 content 局部。这一节就来剖析 LayoutInflater 的原理~

LayoutInflater 是在 PhoneWindow 构造方法里创立的

public PhoneWindow(Context context) {super(context);
    mLayoutInflater = LayoutInflater.from(context);
}

LayoutInflater 是一个零碎服务

public static LayoutInflater from(Context context) {LayoutInflater LayoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    return LayoutInflater;
}

inflate 办法有很多重载,看带有资源 id 参数的

2.1 LayoutInflater#inflate(@LayoutRes int resource, @Nullable ViewGroup root)

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {return inflate(resource, root, root != null);
}


//root 不为空,attachToRoot 就为 true,示意最终解析进去的 View 要 add 到 root 中去。public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {final Resources res = getContext().getResources();
    //1. 将资源 id 转换成 XmlResourceParser   
    final XmlResourceParser parser = res.getLayout(resource);
    try {
        //2. 调用另一个重载办法
        return inflate(parser, root, attachToRoot);
    } finally {parser.close();
    }
}    

正文 1 是将资源 id 转换成 XmlResourceParser,XmlResourceParser 是啥?

public interface XmlResourceParser extends XmlPullParser, AttributeSet, AutoCloseable {
    /**
     * Close this interface to the resource.  Calls on the interface are no
     * longer value after this call.
     */
    public void close();}

继承 XmlPullParser,这是一个规范 XmlPullParser 接口,同时继承 AttributeSet 接口以及当用户实现资源读取时调用的 close 接口,简略来说就是一个 xml 资源解析器,用于解析 xml 资源。

对于 XmlPullParser 原理能够参考文末链接。

2.2【扩大】XML 解析有哪些形式

SAX(Simple API XML)

是一种基于事件的解析器,事件驱动的流式解析形式是,从文件的开始程序解析到文档的完结,不可暂停或倒退

DOM

即对象文档模型,它是将整个 XML 文档载入内存(所以效率较低,不举荐应用),每一个节点当做一个对象

Pull

Android 解析布局文件所应用的形式。Pull 与 SAX 有点相似,都提供了相似的事件,如开始元素和完结元素。不同的是,SAX 的事件驱动是回调相应办法,须要提供回调的办法,而后在 SAX 外部主动调用相应的办法。而 Pull 解析器并没有强制要求提供触发的办法。因为他触发的事件不是一个办法,而是一个数字。它使用方便,效率高。

没用过没关系,然而要晓得 Pull 解析的长处:
Pull 解析器玲珑轻便,解析速度快,简略易用,非常适合在 Android 挪动设施中应用,Android 零碎外部在解析各种 XML 时也是用 Pull 解析器,Android 官网举荐开发者们应用 Pull 解析技术。

参考 https://www.cnblogs.com/guolingyun/p/6148462.html

下面 inflate 办法正文 1 解析完 xml,失去 XmlResourceParser 对象,

解析一个 xml,咱们能够间接应用 getContext().getResources().getLayout(resource);,返回一个 XmlResourceParser 对象。

正文 2,调用另一个 inflate 的重载办法,XmlResourceParser 作为入参

2.3 LayoutInflater#inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot)

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {synchronized (mConstructorArgs) {

        final Context inflaterContext = mContext;
    // 获取 xml 中的属性,存到一个 Set 里
        final AttributeSet attrs = Xml.asAttributeSet(parser);
        Context lastContext = (Context) mConstructorArgs[0];
        mConstructorArgs[0] = inflaterContext;
        View result = root;

        try {
            // Look for the root node.
            int type;
        //1、寻找开始标签
            while ((type = parser.next()) != XmlPullParser.START_TAG &&
                    type != XmlPullParser.END_DOCUMENT) {// Empty}

        //2. 遍历一遍没找到开始标签,抛异样
            if (type != XmlPullParser.START_TAG) {throw new InflateException(parser.getPositionDescription()
                        + ": No start tag found!");
            }

        //3. 开始标签的名字,例如根布局是个 LinearLayout,那么这个 name 就是 LinearLayout
            final String name = parser.getName();
            
        //4、如果根布局是 merge 标签
            if (TAG_MERGE.equals(name)) {
                //merge 标签必须要有附在一个父布局上,rootView 是空,那么抛异样
                if (root == null || !attachToRoot) {
                    throw new InflateException("<merge /> can be used only with a valid"
                            + "ViewGroup root and attachToRoot=true");
                }

        // 遇到 merge 标签调用这个 rInflate 办法(上面会剖析)rInflate(parser, root, inflaterContext, attrs, false);
            } else {
                //5、不是 merge 标签,调用 createViewFromTag,通过标签创立一个根 View
                // Temp is the root view that was found in the xml
                final View temp = createViewFromTag(root, name, inflaterContext, attrs);

                ViewGroup.LayoutParams params = null;

                if (root != null) {
                    
            // 这个办法外面是解析 layout_width 和 layout_height 属性,创立一个 LayoutParams
                    // Create layout params that match root, if supplied
                    params = root.generateLayoutParams(attrs);
                    if (!attachToRoot) {
                    // 如果只是单纯解析,不附加到一个父 View 去,那么根 View 设置 LayoutParams,否则,应用内部 View 的宽高属性
                        temp.setLayoutParams(params);
                    }
                }

        //6. 调用这个办法,解析子 View
                // Inflate all children under temp against its context.
                rInflateChildren(parser, temp, attrs, true);


                // We are supposed to attach all the views we found (int temp)
                // to root. Do that now.
                if (root != null && attachToRoot) {root.addView(temp, params);
                }

        // 如果不附加到 root,那么返回这个 temp,result 默认是 root
                // Decide whether to return the root that was passed in or the
                // top view found in xml.
                if (root == null || !attachToRoot) {result = temp;}
            }

        }


        return result;
    }
}

这个办法按程序来剖析,不是很简单,一步步看下,

正文 1 到 3:从 XmlPullParser 外面解析出开始标签,例如,而后取出标签的名字 LinearLayout

正文 4:如果根标签是 merge 标签,那么再判断,merge 标签必须要附丽到 rootview,否则抛异样。merge 标签的应用就不用说了,必须放在根布局。

根布局 merge 标签的解析是调用 rInflate(parser, root, inflaterContext, attrs, false);办法,看一下

2.4 merge 标签解析:rInflate

void rInflate(XmlPullParser parser, View parent, Context context,
        AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {final int depth = parser.getDepth();
    int type;

    while (((type = parser.next()) != XmlPullParser.END_TAG ||
            parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {if (type != XmlPullParser.START_TAG) {continue;}

        final String name = parser.getName();
        
        
        if (TAG_REQUEST_FOCUS.equals(name)) { 
        // 1、requestFocus 标签
            parseRequestFocus(parser, parent);
        } else if (TAG_TAG.equals(name)) {     
        // 2、tag 标签
            parseViewTag(parser, parent, attrs);
        } else if (TAG_INCLUDE.equals(name)) {  
        // 3、include 标签
            if (parser.getDepth() == 0) {throw new InflateException("<include /> cannot be the root element");
            }
            parseInclude(parser, context, parent, attrs);
        } else if (TAG_MERGE.equals(name)) {  
        //4、merge 标签下不能有 merge 标签
            throw new InflateException("<merge /> must be the root element");
        } else {                             
        //5、其它失常标签,调用 createViewFromTag 创立 View 对象
            final View view = createViewFromTag(parent, name, context, attrs);
            final ViewGroup viewGroup = (ViewGroup) parent;
            final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
        //6、递归解析子 View
            rInflateChildren(parser, view, attrs, true);
            viewGroup.addView(view, params);
        }
    }

    if (finishInflate) {parent.onFinishInflate();
    }
}

merge 标签的解析,次要是以下这些状况:

<?xml version="1.0" encoding="utf-8"?>
<merge
    xmlns:android="http://schemas.android.com/apk/res/android">

    <requestFocus></requestFocus>  //requestFocus

    <tag android:id="@+id/tag"></tag>  //tag

    <include layout="@layout/activity_main"/>  //include

    <merge>  // 谬误,merge 标签必须在根布局
        <TextView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:textColor="@color/colorAccent"
            android:text="123"/>
    </merge>

    <TextView  // 失常标签
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:textColor="@color/colorAccent"
        android:text="123"/>

</merge>

2.4.1 解析 requestFocus 标签

调用父 View 的 requestFocus 办法

private void parseRequestFocus(XmlPullParser parser, View view)
        throws XmlPullParserException, IOException {view.requestFocus(); // 这个 view 是 parent,也就是父布局申请聚焦

    consumeChildElements(parser);  // 跳过 requestFocus 标签
}


final static void consumeChildElements(XmlPullParser parser)
        throws XmlPullParserException, IOException {
    int type;
    final int currentDepth = parser.getDepth();
    // 遇到 END_TAG 退出循环,在此处也就是跳过 requestFocus 标签
    while (((type = parser.next()) != XmlPullParser.END_TAG ||
            parser.getDepth() > currentDepth) && type != XmlPullParser.END_DOCUMENT) {// Empty}
}

2.4.2 解析 tag 标签

给父布局设置 tag

private void parseViewTag(XmlPullParser parser, View view, AttributeSet attrs)
        throws XmlPullParserException, IOException {final Context context = view.getContext();
    final TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ViewTag);
    final int key = ta.getResourceId(R.styleable.ViewTag_id, 0);
    final CharSequence value = ta.getText(R.styleable.ViewTag_value);
    view.setTag(key, value); // 这个 view 是 parent,给父布局设置 tag
    ta.recycle();

    consumeChildElements(parser);  // 跳过 tag 标签
}

能够看到 requestFocus 标签和 tag 标签只是给父 View 设置属性。

2.4.3 解析 include 标签

private void parseInclude(XmlPullParser parser, Context context, View parent,
        AttributeSet attrs) throws XmlPullParserException, IOException {
    int type;

    // 父 View 必须是 ViewGroup
    if (parent instanceof ViewGroup) {
       
        ...
    //1、解析 layout 属性值 id,解析不到默认 0
        int layout = attrs.getAttributeResourceValue(null, ATTR_LAYOUT, 0);
        ...
    //2、查看 layout 属性值,是 0 就抛异样
        if (layout == 0) {final String value = attrs.getAttributeValue(null, ATTR_LAYOUT);
            throw new InflateException("You must specify a valid layout"
                    + "reference. The layout ID" + value + "is not valid.");
        } else {
        //3、解析 layout 属性对应的布局
            final XmlResourceParser childParser = context.getResources().getLayout(layout);
        // 之后就跟解析后面一样,判断是否有 merge 标签,有就执行 rInflate,没有就执行 createViewFromTag
            try {final AttributeSet childAttrs = Xml.asAttributeSet(childParser);

                while ((type = childParser.next()) != XmlPullParser.START_TAG &&
                        type != XmlPullParser.END_DOCUMENT) {// Empty.}

                if (type != XmlPullParser.START_TAG) {throw new InflateException(childParser.getPositionDescription() +
                            ": No start tag found!");
                }

                final String childName = childParser.getName();

                if (TAG_MERGE.equals(childName)) {
                    // The <merge> tag doesn't support android:theme, so
                    // nothing special to do here.
                    rInflate(childParser, parent, context, childAttrs, false);
                } else {
                    final View view = createViewFromTag(parent, childName,
                            context, childAttrs, hasThemeOverride);
            // 父 View,最终解析子 View 会增加到父 View 里。final ViewGroup group = (ViewGroup) parent;

                    final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Include);
                    final int id = a.getResourceId(R.styleable.Include_id, View.NO_ID);
            // 获取 visibility 属性
                    final int visibility = a.getInt(R.styleable.Include_visibility, -1);
                    a.recycle();
                    ViewGroup.LayoutParams params = null;
                    try {params = group.generateLayoutParams(attrs);
                    } catch (RuntimeException e) {// Ignore, just fail over to child attrs.}
                    if (params == null) {params = group.generateLayoutParams(childAttrs);
                    }
                    view.setLayoutParams(params);

                    // 解析子 View
                    rInflateChildren(childParser, view, childAttrs, true);

                    if (id != View.NO_ID) {view.setId(id);
                    }
            // 设置 visibility 属性
                    switch (visibility) {
                        case 0:
                            view.setVisibility(View.VISIBLE);
                            break;
                        case 1:
                            view.setVisibility(View.INVISIBLE);
                            break;
                        case 2:
                            view.setVisibility(View.GONE);
                            break;
                    }

                    group.addView(view);
                }
            } finally {childParser.close();
            }
        }
    } else {throw new InflateException("<include /> can only be used inside of a ViewGroup");
    }

    LayoutInflater.consumeChildElements(parser);
}

解析 include 标签步骤比拟多,以 <include layout="@layout/activity_main"/>为例:

首先,include 标签的父 View 必须是 ViewGroup,
正文 1 和正文 2 是查看 include 标签有没有 layout 属性,属性值不能是 0,属性值对应 layout/activity_main 这个布局 id,

正文 3,xml 解析,解析 layout/activity_main 布局,context.getResources().getLayout(layout)很相熟了,而后就跟最开始的解析布局差不多了,如果遇到 merge 标签,就调用 rInflate 办法去解析 merge 标签,否则,就跟 inflate 办法逻辑一样,调用 createViewFromTag 创立一个 View 对象,而后再调用 rInflateChildren 解析子 View,还有就是给 View 设置 visibility 属性。这部分逻辑跟 inflate 办法外面一样,所以放到前面剖析。

2.4.4 子标签是 merge

merge 标签下不能有 merge 标签,merge 标签必须在 xml 根节点

2.4.5 递归解析子 View,rInflateChildren

final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
        boolean finishInflate) throws XmlPullParserException, IOException {
    // 递归调用解析 merge 标签的办法,parent 换了而已
    rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
}

merge 标签解析完了,回到 2.3 LayoutInflater#inflate 看正文 5

2.5 LayoutInflater#inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot)

        //5、不是 merge 标签,调用 createViewFromTag,通过标签创立一个根 View
                // Temp is the root view that was found in the xml
                final View temp = createViewFromTag(root, name, inflaterContext, attrs);

                ViewGroup.LayoutParams params = null;

                if (root != null) {
                    
            // 这个办法外面是解析 layout_width 和 layout_height 属性,返回一个 LayoutParams
                    params = root.generateLayoutParams(attrs);
                    if (!attachToRoot) {
                        // 如果只是单纯解析,不附加到一个父 View 去,那么根 View 设置 LayoutParams,否则,应用内部 View 的宽高属性
                        temp.setLayoutParams(params);
                    }
                }

        //6、调用这个办法,解析子 View
                // Inflate all children under temp against its context.
                rInflateChildren(parser, temp, attrs, true);


                // We are supposed to attach all the views we found (int temp)
                // to root. Do that now.
                if (root != null && attachToRoot) {root.addView(temp, params);
                }

        // 如果不附加到 root,那么返回这个 temp,result 默认是 root
                // Decide whether to return the root that was passed in or the
                // top view found in xml.
                if (root == null || !attachToRoot) {result = temp;}

正文 5,这里的逻辑跟解析 include 标签里的 layout 逻辑根本是一样的,通过createViewFromTag 办法, 传 View 的名称,去创立一个 View 对象

2.6 LayoutInflater#createViewFromTag

View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
        boolean ignoreThemeAttr) {

    //1、如果是 view 标签,取出 class 属性
    if (name.equals("view")) {name = attrs.getAttributeValue(null, "class");
    }

    ...

    //2、TAG_1995:blink,如果是 blink 标签,就返回一个 BlinkLayout,会闪动的意思
    if (name.equals(TAG_1995)) {
        // Let's party like it's 1995!
        return new BlinkLayout(context, attrs);
    }

    try {
        View view;
        if (mFactory2 != null) {
        // 3、通过 mFactory2 创立 View
            view = mFactory2.onCreateView(parent, name, context, attrs);
        } else if (mFactory != null) {
        // 4、通过 mFactory 创立 View
            view = mFactory.onCreateView(name, context, attrs);
        } else {view = null;}

        if (view == null && mPrivateFactory != null) {
        // 5、通过 mPrivateFactory 创立 View
            view = mPrivateFactory.onCreateView(parent, name, context, attrs);
        }

        if (view == null) {final Object lastContext = mConstructorArgs[0];
            mConstructorArgs[0] = context;
            try {
        // 6、通过 onCreateView 办法创立 View
                if (-1 == name.indexOf('.')) {view = onCreateView(parent, name, attrs);
                } else {view = createView(name, null, attrs);
                }
            } finally {mConstructorArgs[0] = lastContext;
            }
        }

        return view;
    } 
}

正文 1:如果是 view 标签,例如<view class="android.widget.TextView"></view>,则取出 class 属性,也就是android.widget.TextView

正文 2:如果标签是 blink,则创立 BlinkLayout 返回,看下 BlinkLayout 是啥?

2.6.1 BlinkLayout

private static class BlinkLayout extends FrameLayout {
    private static final int MESSAGE_BLINK = 0x42;
    private static final int BLINK_DELAY = 500;

    private boolean mBlink;
    private boolean mBlinkState;
    private final Handler mHandler;

    public BlinkLayout(Context context, AttributeSet attrs) {super(context, attrs);
        mHandler = new Handler(new Handler.Callback() {
            @Override
            public boolean handleMessage(Message msg) {if (msg.what == MESSAGE_BLINK) {if (mBlink) {
                        mBlinkState = !mBlinkState;
                        makeBlink(); // 循 mBlink 为 true 则循环调用}
                    invalidate();
                    return true;
                }
                return false;
            }
        });
    }

    private void makeBlink() {Message message = mHandler.obtainMessage(MESSAGE_BLINK);
        mHandler.sendMessageDelayed(message, BLINK_DELAY); // 延时 500 毫秒
    }

    @Override
    protected void onAttachedToWindow() {super.onAttachedToWindow();

        mBlink = true; // 标记位 true
        mBlinkState = true;

        makeBlink();}

    @Override
    protected void onDetachedFromWindow() {super.onDetachedFromWindow();

        mBlink = false;
        mBlinkState = true;

        mHandler.removeMessages(MESSAGE_BLINK);
    }

外部通过 Handler,onAttachedToWindow 的时候 mBlink 是 true,调用 makeBlink 办法后,500 毫秒之后 Handler 解决,因为 mBlink 为 true,所以又会调用 makeBlink 办法。所以这是一个每 500 毫秒就会扭转状态刷新一次的 FrameLayout,示意没用过这玩意。

持续看 createViewFromTag 办法的正文 3、通过 mFactory2 创立 View

2.6.2 Factory2

public interface Factory2 extends Factory {public View onCreateView(View parent, String name, Context context, AttributeSet attrs);
}

public interface Factory {public View onCreateView(String name, Context context, AttributeSet attrs);
}

Factory2 是一个接口,mFactory2 不为空,那么最终会通过这个接口的实现类去创立 View。

AppCompatDelegateImpl 中实现了这个 Factory2 接口,什么时候应用 AppCompatDelegateImpl?还有就是 mFactory2 是什么时候在哪里赋值的?

看下 AppCompatActivity 源码就明确了

2.6.3 AppCompatActivity

protected void onCreate(@Nullable Bundle savedInstanceState) {AppCompatDelegate delegate = this.getDelegate(); //1、获取代理
    delegate.installViewFactory(); //2、初始化 Factory
    delegate.onCreate(savedInstanceState);
    ..
}

正文 1:获取代理

public AppCompatDelegate getDelegate() {if (this.mDelegate == null) {this.mDelegate = AppCompatDelegate.create(this, this);
    }

    return this.mDelegate;
}

AppCompatDelegate.create 其实返回的就是 AppCompatDelegateImpl

public static AppCompatDelegate create(Activity activity, AppCompatCallback callback) {return new AppCompatDelegateImpl(activity, activity.getWindow(), callback);
}

onCreate 正文 2:delegate.installViewFactory(),看 AppCompatDelegateImpl 外面

public void installViewFactory() {LayoutInflater layoutInflater = LayoutInflater.from(this.mContext);
    // 如果 getFactory 空,就设置 Factory2
    if (layoutInflater.getFactory() == null) {LayoutInflaterCompat.setFactory2(layoutInflater, this);
    }

}

LayoutInflaterCompat#setFactory2

public static void setFactory2(@NonNull LayoutInflater inflater, @NonNull Factory2 factory) {inflater.setFactory2(factory); // 设置 factory2
    //AppCompact 是兼容包,sdk 21 以下应该是没有提供 setFactory2 这个办法,须要强制通过反射设置 factory2,if (VERSION.SDK_INT < 21) {Factory f = inflater.getFactory();
        if (f instanceof Factory2) {
        // 强制设置 Factory2
            forceSetFactory2(inflater, (Factory2)f);
        } else {forceSetFactory2(inflater, factory);
        }
    }

}

LayoutInflaterCompat#forceSetFactory2

只看要害代码如下:

private static void forceSetFactory2(LayoutInflater inflater, Factory2 factory) {
    // 反射拿到 LayoutInflater 的 mFactory2 字段
    sLayoutInflaterFactory2Field = LayoutInflater.class.getDeclaredField("mFactory2");
    sLayoutInflaterFactory2Field.setAccessible(true);

    // 强制设置
    sLayoutInflaterFactory2Field.set(inflater, factory);

    
}

这里简略说一下 AppCompact 是干嘛的,简略来说就是高版本的一些个性,失常状况下低版本设施是看不到的,所以谷歌提供兼容包,v4、v7、以及当初举荐替换 androidx,目标是为了让高 api 的一些个性,在低版本也能享受到,只有继承 AppCompactActivity,低版本设施也能有高版本 api 的个性。

通过下面剖析,针对 view = mFactory2.onCreateView(parent, name, context, attrs); 这句代码,
咱们来看 AppCompatDelegateImplonCreateView 办法就对了,有理有据,你不要说你写界面都是继承 Activity,我会鄙视你。

2.6.4 AppCompatDelegateImpl#onCreateView

public final View onCreateView(View parent, String name, Context context, AttributeSet attrs) {return this.createView(parent, name, context, attrs);
}

public View createView(View parent, String name, @NonNull Context context, @NonNull AttributeSet attrs) {if (this.mAppCompatViewInflater == null) {TypedArray a = this.mContext.obtainStyledAttributes(styleable.AppCompatTheme);
    //1、咱们能够在主题中配置自定义的 Inflater
        String viewInflaterClassName = a.getString(styleable.AppCompatTheme_viewInflaterClass);
        if (viewInflaterClassName != null && !AppCompatViewInflater.class.getName().equals(viewInflaterClassName)) {
            try {Class viewInflaterClass = Class.forName(viewInflaterClassName);
                this.mAppCompatViewInflater = (AppCompatViewInflater)viewInflaterClass.getDeclaredConstructor().newInstance();
            } catch (Throwable var8) {Log.i("AppCompatDelegate", "Failed to instantiate custom view inflater" + viewInflaterClassName + ". Falling back to default.", var8);
                this.mAppCompatViewInflater = new AppCompatViewInflater();}
        } else {
        // 2、如果没有指定,默认创立 AppCompatViewInflater
            this.mAppCompatViewInflater = new AppCompatViewInflater();}
    }
    ...
    //3、最初通过 AppCompatViewInflater 的 createView 办法创立 View
    return this.mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext, IS_PRE_LOLLIPOP, true, VectorEnabledTintResources.shouldBeUsed());
}

正文 1 和正文 2,阐明咱们能够在主题中配置 Inflater,能够自定义创立 View 的规定

<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
    <item name="colorPrimary">@color/colorPrimary</item>
    <item name="viewInflaterClass">android.support.v7.app.AppCompatViewInflater</item> // 指定 viewInflaterClass
</style>

正文 3 AppCompatViewInflater 的 createView 办法就是真正去创立 View 对象了。

2.6.5 AppCompatViewInflater#createView、

final View createView(View parent, String name, @NonNull Context context, @NonNull AttributeSet attrs, boolean inheritContext, boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
    ...
    switch(name.hashCode()) {
    ...
    case -938935918:
        if (name.equals("TextView")) {var12 = 0;}
        break;

    case 2001146706:
        if (name.equals("Button")) {var12 = 2;}
    }
    ... 省略很多 case

    // 下面曾经对 var12 赋值,走对应的办法
    switch(var12) {
    case 0:
    // 0 是 TextView 标签
        view = this.createTextView(context, attrs);
        this.verifyNotNull((View)view, name);
        break;
    case 1:
        view = this.createImageView(context, attrs);
        this.verifyNotNull((View)view, name);
        break;
    case 2:
        view = this.createButton(context, attrs);
        this.verifyNotNull((View)view, name);
        break;
    ... 省略很多 case

    default:
        view = this.createView(context, name, attrs);
    }

    if (view == null && originalContext != context) {
        // 如果下面都没有创立,createViewFromTag 解救一些
        view = this.createViewFromTag(context, name, attrs);
    }

    if (view != null) {this.checkOnClickListener((View)view, attrs);
    }

    return (View)view;
}

对应 View 走对应的创立办法,遇到 TextView,调用 createTextView 办法,

protected AppCompatTextView createTextView(Context context, AttributeSet attrs) {return new AppCompatTextView(context, attrs);
}

看到这里就明确了,只有应用 AppCompactActivity,布局里写 TextView,最终创立的是 AppCompatTextView。

其它 ImageView、Button 同理,就不再剖析,读者能够本人查看源码。

如果走完所有 case 没有创立胜利,没关系,还能够解救一下,调用 createViewFromTag,增加前缀,而后通过反射创立,看一下

2.6.6 AppCompatViewInflater#createViewFromTag

private View createViewFromTag(Context context, String name, AttributeSet attrs) {
    //1、如果是 view 标签,获取 class 属性,就是类的全门路
    if (name.equals("view")) {name = attrs.getAttributeValue((String)null, "class");
    }

    View view;
    try {this.mConstructorArgs[0] = context;
        this.mConstructorArgs[1] = attrs;
        View var4;
        // 如果是 API 提供的控件,这个条件会成立
        if (-1 == name.indexOf(46)) {//sClassPrefixList 定义:private static final String[] sClassPrefixList = new String[]{"android.widget.", "android.view.", "android.webkit."};
            for(int i = 0; i < sClassPrefixList.length; ++i) {
        //createViewByPrefix,传 View 标签名和前缀
                view = this.createViewByPrefix(context, name, sClassPrefixList[i]);
                if (view != null) {
                    View var6 = view;
                    return var6;
                }
            }

            var4 = null;
            return var4;
        }

    // 自定义的控件,不须要前缀,传 null
        var4 = this.createViewByPrefix(context, name, (String)null);
        return var4;
    } catch (Exception var10) {view = null;} finally {this.mConstructorArgs[0] = null;
        this.mConstructorArgs[1] = null;
    }

    return view;
}

在布局 xml 里写的 TextView,类全门路是 android.widget.TextView,所以要增加前缀 android.widget. 能力通过全门路反射创立 TextView 对象,sClassPrefixList 中定义的前缀有三个:private static final String[] sClassPrefixList = new String[]{"android.widget.", "android.view.", "android.webkit."};,原生控件定义在这几个目录下。

通过 View 名字加上前缀(如果是咱们的自定义 View,前缀 null)来创立 View,调用 createViewByPrefix 办法

2.6.7 AppCompatViewInflater#createViewByPrefix

private View createViewByPrefix(Context context, String name, String prefix) throws ClassNotFoundException, InflateException {Constructor constructor = (Constructor)sConstructorMap.get(name);

    try {
    // 如果没有缓存
        if (constructor == null) {
        // 最终通过 ClassLoader 去加载一个类,类有前缀的话会在这里做拼接
            Class<? extends View> clazz = context.getClassLoader().loadClass(prefix != null ? prefix + name : name).asSubclass(View.class);
            constructor = clazz.getConstructor(sConstructorSignature);
        // 缓存类结构器
            sConstructorMap.put(name, constructor);
        }

        constructor.setAccessible(true);
        return (View)constructor.newInstance(this.mConstructorArgs);
    } catch (Exception var6) {return null;}
}

最终通过 ClassLoader 去加载一个类,asSubclass(View.class); 是强转换成 View 对象,

这里用到反射去创立一个 View 对象,思考到反射的性能,所以缓存了类的结构器,key 是类名,也就是说第一次须要反射加载 Class 对象,之后创立雷同的 View,间接从缓存中取出该 View 的结构器,调用 newInstance 办法实例化。

看到这里,可能大家会有一个问题了,为什么只剖析 AppCompat,如果主题不应用 AppCompat,那么流程又是怎么的?

这里之所以剖析 AppCompat,因为它其实曾经蕴含了默认的解决在外面,比方 TextView,如果创立 AppCompatTextView 失败,则通过反射区创立android.widget.TextView,默认流程也差不多是这样的,不信?

再回到 LayoutInflater#createViewFromTag

2.7 LayoutInflater#createViewFromTag

View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
        boolean ignoreThemeAttr) {

    //1、如果是 view 标签,取出 class 属性
    if (name.equals("view")) {name = attrs.getAttributeValue(null, "class");
    }

    ...

    //2、TAG_1995:blink,如果是 blink 标签,就返回一个 BlinkLayout,会闪动的意思
    if (name.equals(TAG_1995)) {
        // Let's party like it's 1995!
        return new BlinkLayout(context, attrs);
    }

    try {
        View view;
        if (mFactory2 != null) {
        // 3、通过 mFactory2 创立 View
            view = mFactory2.onCreateView(parent, name, context, attrs);
        } else if (mFactory != null) {
        // 4、通过 mFactory 创立 View
            view = mFactory.onCreateView(name, context, attrs);
        } else {view = null;}

        if (view == null && mPrivateFactory != null) {
        // 5、通过 mPrivateFactory 创立 View
            view = mPrivateFactory.onCreateView(parent, name, context, attrs);
        }

        if (view == null) {final Object lastContext = mConstructorArgs[0];
            mConstructorArgs[0] = context;
            try {
        // 6、通过 onCreateView 办法创立 View
                if (-1 == name.indexOf('.')) {
                    // 原生控件没有.  调用这个 onCreateView 办法创立
                    view = onCreateView(parent, name, attrs);
                } else {
                    // 自定义控件全名有. 所以前缀传 null
                    view = createView(name, null, attrs);
                }
            } finally {mConstructorArgs[0] = lastContext;
            }
        }

        return view;
    } 
}

正文 3、4、5 都是通过代理类去创立,剖析了 mFactory2,mFactory 也是相似的,这里就不再剖析了,
如果没有代理类或者代理类没能力创立,那么走默认的创立办法,也就就是 正文 6,LayoutInflater#onCreateView(String name, AttributeSet attrs),简略看下吧

2.7.1 LayoutInflater#onCreateView(String name, AttributeSet attrs)

protected View onCreateView(String name, AttributeSet attrs)
        throws ClassNotFoundException {
    //1、传前缀,不思考 support 包,android 提供的原生控件都是 android.view. 结尾
    return createView(name, "android.view.", attrs);
}

public final View createView(String name, String prefix, AttributeSet attrs)
        throws ClassNotFoundException, InflateException {
    //2、读取缓存
    Constructor<? extends View> constructor = sConstructorMap.get(name);
    Class<? extends View> clazz = null;

    try {

        // 没有有缓存,通过反射创立 View
        if (constructor == null) {
            // Class not found in the cache, see if it's real, and try to add it
            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);
        } else {
            // 有缓存的逻辑
            ...

        }

        Object[] args = mConstructorArgs;
        args[1] = attrs;

        final View view = constructor.newInstance(args);
    // 如果是 ViewStub
        if (view instanceof ViewStub) {
            // Use the same context when inflating ViewStub later.
            final ViewStub viewStub = (ViewStub) view;
            viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
        }
        return view;

    }
}

1、传前缀 android.view.,原生控件在这个包下。
2、通过反射创立 View,反射耗性能,所以采纳缓存机制。
3、如果遇到 viewStub,则给它设置一个 LayoutInflater

下面剖析的是一层 View 的创立流程,第二层 View 怎么创立的呢?

看 rInflateChildren(parser, temp, attrs, true)

2.8 rInflateChildren(parser, temp, attrs, true);

final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
        boolean finishInflate) throws XmlPullParserException, IOException {rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
}

rInflateChildren 办法外部调用 rInflate,rInflate 办法下面其实曾经剖析过了,跟解析 merge 标签流程是一样的。到这里根本把整个 xml 解析成 View 对象的流程剖析完了,须要简略小结一下,不然都忘了后面讲啥了。

LayoutInflater 小结

xml 布局的解析,文字总结如下:

1、采纳 Pull 解析,将布局 id 解析成 XmlResourceParser 对象,简略介绍几种 xml 解析形式:SAX、DOM 和 Pull 解析。
2、因为第一步曾经将 xml 解析进去,所以间接取出标签的 View 名字,如果是 merge 标签,调用 rInflate 办法,如果是一般的失常的 View,就通过 createViewFromTag 这个办法创立一个 View 对象。
3、介绍 rInflate 这个办法是如何解析各个标签的

  • requestFocus 标签
  • tag 标签
  • include 标签
  • view 标签
  • blink 标签
  • merge 标签

次要是对以上这些标签(不分先后)的解决,解决完

xml 中第二层 View 的创立最终也是调用 rInflate 这个办法,属于递归创立,创立 View 的程序是深度优先。

3、介绍 createViewFromTag 办法创立 View 的形式,如果是 blink 标签,间接 new new BlinkLayout() 返回,简略介绍了 BlinkLayout,尽管没用过它。

4、失常状况下,createViewFromTag 办法创立 View 的几个逻辑

  • mFactory2 不为空就调用 mFactory2.onCreateView 办法创立
  • mFactory 不为空就调用 mFactory.onCreateView 办法创立
  • 上述创立失败,就用默认 mPrivateFactory 创立
  • 上述仍然没创立胜利,就调用 LayoutInflater 的 CreateView 办法,给 View 增加 android.view. 前缀,而后通过反射去创立一个 View 对象,思考到反射耗性能,所以第一次创立胜利就会缓存类的结构器 Constructor,之后创立雷同 View 只有调用结构器的 newInstance 办法。

5、介绍 Factory2 接口的实现类 AppCompatDelegateImpl,AppCompatActivity 外部简直所有生命周期办法都交给 AppCompatDelegateImpl 这个代理去做,并且给 LayoutInflater 设置了 Factory2,所以创立 View 的时候优先通过 Factory2 接口去创立,也就是通过 AppCompatDelegateImpl 这个实现类去创立。

6、介绍 AppCompatDelegateImpl 创立 View 的形式,如果布局文件是 TextView,则创立的是兼容类 AppCompactTextView,其它控件同理。创立失败则回退到失常的创立流程,增加前缀,例如 android.widget.TextView,而后通过反射去创立。7、如果没有应用兼容包,例如继承 Activity,那么也是通过反射去创立 View 对象,自定义 View 不加前缀。

三、UI 优化

所谓 UI 优化,就是拆解渲染过程的耗时,找到瓶颈的中央,加以优化。

后面剖析了 UI 原理,Activity、Window、DecorView、ViewRootImpl 之间的关系,以及 XML 布局文件是如何解析成 View 对象的。

耗时的中央:

  • View 的创立在主线程,包含 measure、layout、draw,界面简单的时候,这一部分可能会很耗时。
  • 解析 XML,反射创立 VIew 对象,这一过程的耗时。

上面介绍一些罕用的 UI 优化形式~

3.1 惯例形式

  1. 缩小 UI 层级、应用 merge、Viewstub 标签优化
  2. 优化 layout 开销、RelativeLayout 和带有 weight 的 Linearlayout 会测量屡次, 能够尝试应用 ConstraintLayout 来代替。
  3. 背景优化,剖析 DecorView 创立的时候,发现 DecorView 会设置一个默认背景,能够对立将 DecorView 背景设置为通用背景,其它父控件就无需设置背景,防止反复绘制。

3.2 xml 转换成代码

应用 xml 编写布局,很不便,然而最终要通过 LayoutInflater 的 inflate 办法,将 xml 解析进去并递归 + 反射去创立 View 对象,布局比较复杂的时候,这一部分会十分耗时。

应用代码创立能够缩小 xml 递归解析和反射创立 View 的这部分耗时。 当然,如果将 xml 都换成代码来写,开发效率将不忍直视,并且代码可读性也是个问题。

掌阅开源的一个库,编译期主动将 xml 转换成 java 代码,X2C

它的原理是采纳 APT(Annotation Processor Tool)+ JavaPoet 技术来实现编译期间【注解】-【解注解】->【翻译 xml】->【生成 java】整个流程的操作

即在编译生成 APK 期间,将须要翻译的 layout 翻译生成对应的 java 文件,这样对于开发人员来说写布局还是写原来的 xml,但对于程序来说,运行时加载的是对应的 java 文件。

侵入性极低,去除注解则回退到原生的运行时解析形式。当然,有些状况是不反对转换的,比方 merge 标签,编译期没法确定它的 parent。

3.3 异步创立 View

通过子线程创立 View,缩小主线程耗时。

private void threadNewView() {new Thread(){
            @Override
            public void run() {mSplashView = LayoutInflater.from(MainActivity.this).inflate(R.layout.activity_splash,null);
            }
        }.start();}

当然,这种形式须要解决同步问题,并且没有从源头上解决创立 View 耗时,只是将这部分耗时放到线程去做。UI 更新的操作还是要切换到主线程,不然会触发 ViewRootImpl 的 checkThread 检测。

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

3.4 复用 View

复用 View 这个应该比拟常见了,像 RecyclerView 的四级缓存,目标就是通过复用 View 缩小创立 View 的工夫。

咱们能够在 onDestroy 办法将 View 的状态革除,而后放入缓存。在 onCreate 的时候去命中缓存,设置状态。

3.5 异步布局:Litho

失常状况下 measure、layout、draw 都是在主线程执行的,最终绘制操作是在 draw 办法,而 measure、layout 只是做一些数据筹备,齐全能够放到子线程去做。

Litho 的原理就是将 measure、layout 放到子线程:github.com/facebook/li…

长处:

  1. 将 measure、layout、放到子线程去做,缩小主线程耗时。
  2. 应用本人的布局引擎,缩小 View 层级,界面扁平化。
  3. 优化 RecyclerView,进步缓存命中率。

毛病:

  1. 无奈在 AS 中预览。
  2. 应用本人的布局引擎,有一点的应用老本。

3.6 Flutter:自绘引擎

Flutter 是一个跨平台 UI 框架,外部集成了 Skia 图像库,本人接管了图像绘制流程,性能直逼原生,是时候制订打算学习一波了~

UI 优化总结

后面解析了 UI 原理之后,咱们晓得 UI 渲染次要波及到 xml 的解析、View 的 measure、layout、draw 这些耗时的阶段。UI 优化形式总结如下:

  • 惯例形式。
  • xml 的解析和反射创立 View 耗时,采纳 new 代替 xml。
  • 复用 View
  • 异步创立 View,将创立 View 的这部分工夫放到子线程。
  • 将 measure、layout 放到子线程,代表:Litho

本文转自 https://juejin.cn/post/6844903974294781965,如有侵权,请分割删除。

相干视频:

Android 高级 UI 性能优化——FlowLayout 流式布局我的项目实战_哔哩哔哩_bilibili

Android 高级 UI 性能优化——LayoutInflater.inflate 函数意义与参数阐明_哔哩哔哩_bilibili

Android 高级 UI 性能优化——ViewPager 嵌套 Fragment UI 模式性能优化_哔哩哔哩_bilibili

正文完
 0