总结UI原理和高级的UI优化方式

不知道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().get
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值