Android源码解析系列1——Activity启动和界面加载

文章详细剖析了Android中Activity的启动过程,包括handleLaunchActivity方法中调用的生命周期方法(onCreate、onStart、onPostCreate、onResume),以及setContentView如何加载自定义布局。作者还强调了理解和学习Activity生命周期和UI自定义View的重要性。
摘要由CSDN通过智能技术生成

在scheduleLaunchActivity方法中,首先创建了一个ActivityClientRecord对象,并对其进行赋值。

ActivityClientRecord:用于记录与Activity相关的数据。

然后通过Handler,将线程由Binder线程切换到主线程。最终调用到ActivityThread的handleLaunchActivity方法。

public final class ActivityThread {
// 省略部分代码

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
// 省略部分代码

Activity a = performLaunchActivity(r, customIntent);

if (a != null) {
// 省略部分代码
handleResumeActivity(r.token, false, r.isForward,
!r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);

if (!r.activity.mFinished && r.startsNotResumed) {
// The activity manager actually wants this one to start out paused, because it
// needs to be visible but isn’t in the foreground. We accomplish this by going
// through the normal startup (because activities expect to go through onResume()
// the first time they run, before their window is displayed), and then pausing it.
// However, in this case we do -not- need to do the full pause cycle (of freezing
// and such) because the activity manager assumes it can just retain the current
// state it has.
performPauseActivityIfNeeded(r, reason);
// 省略部分代码
}
}
}
}

这里,我们暂时不管performLaunchActivity方法中做了什么,仅分析后续代码。后续代码中,调用了handleResumeActivity,猜测它应该会调用Activity的onResume方法。

根据Activity生命周期推测到:

performLaunchActivity方法里,一定会依次调用Activity的onCreateonStart方法。

带着这个思路,开始分析performLaunchActivity方法。

public final class ActivityThread {
// 省略部分代码

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
// 省略部分代码

Activity activity = null;
try {
java.lang.ClassLoader cl = appContext.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
// 省略部分代码
} catch (Exception e) { /* 省略部分代码 */ }

try {
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
// 省略部分代码

if (activity != null) {
// 省略部分代码

Window window = null;
if (r.mPendingRemoveWindow != null && r.mPreserveWindow) {
window = r.mPendingRemoveWindow;
r.mPendingRemoveWindow = null;
r.mPendingRemoveWindowManager = null;
}
appContext.setOuterContext(activity);
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor, window, r.configCallback);
// 省略部分代码

if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
mInstrumentation.callActivityOnCreate(activity, r.state);
}
// 省略部分代码

if (!r.activity.mFinished) {
activity.performStart();
r.stopped = false;
}
if (!r.activity.mFinished) {
activity.mCalled = false;
if (r.isPersistable()) {
mInstrumentation.callActivityOnPostCreate(activity, r.state,
r.persistentState);
} else {
mInstrumentation.callActivityOnPostCreate(activity, r.state);
}
// 省略部分代码
}
}
r.paused = true;

// 保存ActivityClientRecord
mActivities.put(r.token, r);

} catch { /* 省略catch相关代码 */ }

return activity;
}

上述代码主要执行了以下操作:

  1. 创建Activity对象

调用InstrumentationnewActivity方法,通过反射创建Activity对象。

  1. 初始化Activity

调用Activity对象的attach方法,用于初始化Activity的一些数据,同时会为Activity设置Window对象。 注意:Activity的Window对象,与传入的Window对象不是同一个对象。这也意味着:每个Activity都有各自的Window对象

public class Activity extends … {
// 省略部分代码
private Window mWindow;
private WindowManager mWindowManager;

final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor,
Window window, ActivityConfigCallback activityConfigCallback) {
attachBaseContext(context);

mFragments.attachHost(null /parent/);

mWindow = new PhoneWindow(this, window, activityConfigCallback);
// 省略部分代码

mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
// 省略部分代码

mWindowManager = mWindow.getWindowManager();
// 省略部分代码
}
// 省略部分代码
}

  1. 调用3个生命周期方法

1、Instrumentation.callActivityOnCreate方法,该方法中会调用activity.performCreate()方法。
2、activity.performStart()方法。
3、Instrumentation.callActivityOnPostCreate方法,该方法中会调用activity.onPostCreate()方法。

查看里面的源码,确实依次调用了onCreateonStartonPostCreate方法,验证了我们之前对performLaunchActivity的猜想。

总结一下

handleLaunchActivity方法里,会回调以下生命周期:

onCreate() -> onStart() -> onPostCreate() -> onResume()

注意:如果ActivityClientRecord.startsNotResumed = true时,生命周期流程将会变为:

onCreate() -> onStart() -> onPostCreate() -> onResume() -> onPause()


二、界面加载

通过上节内容的介绍,我们知道在handleLaunchActivity方法里,会回调Activity的onCreate()生命周期方法。

而一般我们在创建Activity时,会在onCreate()中调用setContentView来设置布局文件。

下面,我们开始分析,我们自定义的布局文件是如何被加载出来的。

2.1、设置布局

首先分析Activity中的setContentView方法的源码。

public class Activity extends … {
// 省略部分代码

public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
// 省略部分代码
}

根据前面的分析可知,这里的getWindow()方法返回的是该Activity所特有的Window对象,它在attach方法中被赋值。

而Window是一个抽象类,通过查看类上的注解可知,它只有一个名为PhoneWindow的子类,所以我们直接看PhoneWindow的setContentView方法。

public class PhoneWindow extends Window implements MenuBuilder.Callback {
// 省略部分代码
ViewGroup mContentParent;

@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()方法,后续将传入的布局资源,加载到mContentParent中。

所以这里可以肯定:installDecor()方法,主要作用就是为了创建mContentParent对象。

public class PhoneWindow extends Window implements MenuBuilder.Callback {
// 省略部分代码
private DecorView mDecor;
ViewGroup mContentParent;

private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
mDecor = generateDecor(-1);
// 省略部分代码
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
// 省略后续设置icon、title等代码
}
}
}

嗯,这里确实通过generateLayout方法创建了mContentParent对象,但在创建之前,先创建了一个DecorView对象,并将其作为参数传入generateLayout方法里。

DecorView,我们只需要知道它继承至FrameLayout即可,因为此时分析它的细节,对于我们并没太大帮助。

那我们分析generateLayout做了什么:

public class PhoneWindow extends Window implements MenuBuilder.Callback {
// 省略部分代码
public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;

protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.

// 省略部分代码:从主题文件中读取内容,并设置对应的Flag

// Inflate the window decor.

int layoutResource;
int features = getLocalFeatures();
//省略部分代码:通过features,为layoutResource设置不同布局资源id

mDecor.startChanging();
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);

ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
//省略部分代码:为mDecor设置background、elevation、title、titleColor等数据

mDecor.finishChanging();

return contentParent;
}
}

主要做了以下内容:

  1. 从主题中获取数据,并应用到当前Window中。
  2. 根据features,让mDecor加载不同的布局文件。
  3. 获得mContentParent对象(id为com.android.internal.R.id.content)。

这里我们可以得出以下信息:

  1. 不同的主题会让Window加载不同的布局到DecorView中。
  2. setContentView方法,实际是将自定义的布局文件,加载到mContentParent中。

至此,我们可以简单总结一下setContentView的流程:

1、首先,Activity中的Window对象会创建一个DecorView
2、然后根据不同的主题,让DecorView加载不同的布局资源。
3、获取这些布局资源中的mContentParent,它的id为com.android.internal.R.id.content
4、最后将自定义的布局加载到mContentParent中。

2.2、渲染布局

在上述setContentView的流程中,所有的布局资源都已加载完毕,而布局的加载又会涉及到addView方法。

一般来说,调用addView方法,都会间接调用到requestLayout()invalidate(true)方法,造成界面重新布局和刷新。

而我们也知道:

Activity在onCreate时,界面并不会被加载出来。

这里仿佛出现了矛盾,那我们再分析分析,看看为什么调用了addView方法后,界面却没有被加载出来。

public abstract class ViewGroup extends View implements ViewParent, ViewManager {
// 省略部分代码

public void addView(View child, int index, LayoutParams params) {
// 省略部分代码
requestLayout();
invalidate(true);
addViewInner(child, index, params, false);
}
// 省略部分代码
}

public class View implements … {
// 省略部分代码

public void requestLayout() {
// 省略部分代码
if (mParent != null && !mParent.isLayoutRequested()) {
mParent.requestLayout();
}
// 省略部分代码
}

public void invalidate(boolean invalidateCache) {
invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}

void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
boolean fullInvalidate) {
// 省略部分代码
if (skipInvalidate()) {
return;
}
// 省略后续代码
}

private boolean skipInvalidate() {
return (mViewFlags & VISIBILITY_MASK) != VISIBLE && mCurrentAnimation == null &&
(!(mParent instanceof ViewGroup) ||
!((ViewGroup) mParent).isViewTransitioning(this));
}
// 省略部分代码
}

哦,原来此时DecorView没有父容器,导致这里只会执行添加操作,而不会去重新布局和刷新。


那什么时候,界面才会被加载出来呢?

只要学过Android生命周期的人,都知道:

当Activity处于onCreate时,是不可见的。
当Activity处于onStart时,可见,但不可交互。
当Activity处于onResume时,才是可见可交互的。

那我们看看ActivityperformStart方法实现:

public class Activity extends … {
// 省略部分代码

final void performStart() {
mActivityTransitionState.setEnterActivityOptions(this, getActivityOptions());
mFragments.noteStateNotSaved();
mCalled = false;

结尾

最后小编想说:不论以后选择什么方向发展,目前重要的是把Android方面的技术学好,毕竟其实对于程序员来说,要学习的知识内容、技术有太多太多,要想不被环境淘汰就只有不断提升自己,从来都是我们去适应环境,而不是环境来适应我们!

当程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。

想要拿高薪实现技术提升薪水得到质的飞跃。最快捷的方式,就是有人可以带着你一起分析,这样学习起来最为高效,所以为了大家能够顺利进阶中高级、架构师,我特地为大家准备了一套高手学习的源码和框架视频等精品Android架构师教程,保证你学了以后保证薪资上升一个台阶。

当你有了学习线路,学习哪些内容,也知道以后的路怎么走了,理论看多了总要实践的。

高级UI,自定义View

UI这块知识是现今使用者最多的。当年火爆一时的Android入门培训,学会这小块知识就能随便找到不错的工作了。

不过很显然现在远远不够了,拒绝无休止的CV,亲自去项目实战,读源码,研究原理吧!

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

1714209748929)]

高级UI,自定义View

UI这块知识是现今使用者最多的。当年火爆一时的Android入门培训,学会这小块知识就能随便找到不错的工作了。

不过很显然现在远远不够了,拒绝无休止的CV,亲自去项目实战,读源码,研究原理吧!

[外链图片转存中…(img-SnUlEg4Z-1714209748930)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值