征服Android面试官路漫漫(三):从源码深扒一下四大组件和-Context

throw new IllegalStateException(“Base context already set”);
}
mBase = base;
}

public Context getBaseContext() {
return mBase;
}

@Override
public AssetManager getAssets() {
return mBase.getAssets();
}

@Override
public Resources getResources() {
return mBase.getResources();
}


}

这是一个典型的 装饰者模式,也叫做 修饰模式,以下来自维基百科:

修饰模式,是面向对象编程领域中,一种动态地往一个类中添加新的行为的设计模式。就功能而言,修饰模式相比生成子类更为灵活,这样可以给某个对象而不是整个类添加一些功能。

通过使用修饰模式,可以在运行时扩充一个类的功能。原理是:增加一个修饰类包裹原来的类,包裹的方式一般是通过在将原来的对象作为修饰类的构造函数的参数。装饰类实现新的功能,但是,在不需要用到新功能的地方,它可以直接调用原来的类中的方法。修饰类必须和原来的类有相同的接口。

修饰模式是类继承的另外一种选择。类继承在编译时候增加行为,而装饰模式是在运行时增加行为。

当有几个相互独立的功能需要扩充时,这个区别就变得很重要。在有些面向对象的编程语言中,类不能在运行时被创建,通常在设计的时候也不能预测到有哪几种功能组合。这就意味着要为每一种组合创建一个新类。相反,修饰模式是面向运行时候的对象实例的,这样就可以在运行时根据需要进行组合。一个修饰模式的示例是JAVA里的Java I/O Streams的实现。

Context 是基本的抽象类,无论是实现类,还是装饰类,都直接或间接的实现它。ContextImpl 是 Context 的直接实现类,但各个组件并不是直接继承 ContextImpl,而是通过装饰类 ContextWrapper 来持有 ContextImpl。这是为什么呢?对于 Activity 和 Service 来说,它们都需要系统上下文运行环境,但它们又是不同的。Activity 需要显示到前台,它有页面,它需要主题,于是有了继承自 ContextWrapper 的 ContextThemeWrapper,扩展了功能,给 Activity 提供了主题。同时,Activity、Service、Application 这些具体组件本身又扩展出了不同的生命周期功能。

所以,装饰器模式通过组合和扩展装饰类,来给不同的具体对象提供了不同的功能扩展。

ActivityServiceApplication 最终都是继承自装饰类 ContextWrapper ,ContextWrapper 通过 attachBaseContext() 方法来获取实际做事的 ContextImpl 对象。所以这些组件的创建过程中,一定会在某一时机调用 attachBaseContext() 方法对 mBase 对象进行赋值,让我们从源码里面找找答案。

四大组件和 Context

Activity 和 Context

先说 Activity,Activity 的启动过程极其复杂,我们就直接从 ActivityThread 的 performLaunchActivity() 方法看起。

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
ActivityInfo aInfo = r.activityInfo;
if (r.packageInfo == null) {
// 1. 获取 LoadedApk 对象
r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo,
Context.CONTEXT_INCLUDE_CODE);
}

// 2. 创建 ContextImpl 对象
ContextImpl appContext = createBaseContextForActivity®;
Activity activity = null;
try {
java.lang.ClassLoader cl = appContext.getClassLoader();
// 3. 反射创建 Activity 对象
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);

} catch (Exception e) {

}

try {
// 4. 创建 Application 对象
Application app = r.packageInfo.makeApplication(false, mInstrumentation);

if (activity != null) {

appContext.setOuterContext(activity);
// 5. 绑定 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);


int theme = r.activityInfo.getThemeResource();
if (theme != 0) {
// 设置主题
activity.setTheme(theme);
}

// 6. 回调 onCreate()
if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
mInstrumentation.callActivityOnCreate(activity, r.state);
}

r.activity = activity;
}
r.setState(ON_CREATE);

mActivities.put(r.token, r);

} catch (SuperNotCalledException e) {
throw e;
} catch (Exception e) {

}
return activity;
}

整理一下大致的执行流程:

  1. 获取 LoadedApk 对象,表示加载过的 Apk ,通常一个 App 对应着一个 LoadedApk
  2. 通过 createBaseContextForActivity() 方法创建 ContextImpl 对象
  3. 反射创建 Activity 对象
  4. 创建 Application 对象,这里也是用的反射。如果开发者没有声明自己的 Application 的话,就是默认的 androoid.app.Application
  5. 调用 activity.attach() ,这个方法很重要,后面详细说
  6. 回调 onCreate()

接着就是 Activity 正常的生命周期流程了。

重点看一下 createBaseContextForActivity() 方法和 attach() 方法。

private ContextImpl createBaseContextForActivity(ActivityClientRecord r) {

ContextImpl appContext = ContextImpl.createActivityContext(
this, r.packageInfo, r.activityInfo, r.token, displayId, r.overrideConfig);

return appContext;
}

调用了 ContextImpl.createActivityContext() 方法。

static ContextImpl createActivityContext(ActivityThread mainThread,
LoadedApk packageInfo, ActivityInfo activityInfo, IBinder activityToken, int displayId,
Configuration overrideConfiguration) {

// 创建 ContextImpl 对象
ContextImpl context = new ContextImpl(null, mainThread, packageInfo, activityInfo.splitName,
activityToken, null, 0, classLoader);

final ResourcesManager resourcesManager = ResourcesManager.getInstance();
context.setResources(resourcesManager.createBaseActivityResources(activityToken,
packageInfo.getResDir(),
splitDirs,
packageInfo.getOverlayDirs(),
packageInfo.getApplicationInfo().sharedLibraryFiles,
displayId,
overrideConfiguration,
compatInfo,
classLoader));
context.mDisplay = resourcesManager.getAdjustedDisplay(displayId,
context.getResources());
return context;
}

装饰类 ContextWrapper 真正需要的 ContextImpl 对象现在已经创建出来了,但是还没有绑定到 Activity 。继续看 Activity.attach() 方法,注意attach() 方法的第一个参数就是刚刚创建出来的 ContextImpl 对象。

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()
attachBaseContext(context);

// 创建 PhoneWindow
mWindow = new PhoneWindow(this, window, activityConfigCallback);

mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);

}

你对 attachBaseContext() 方法应该还有印象。ContextWrapper 正是通过这个方法给 mBase 对象赋值,拿到真正的 ContextImpl 对象。到这里,整个逻辑就通顺了。

注意 attach() 方法中的 setWindowManager() 方法中的 mToken 参数,这决定了 Application Context 无法创建和显示 Dialog 。后续会进行详细分析。

再回头看看文章开头的问题。

Log.e(“context”, "getApplication in Activity: " + getApplication().getClass().getName());
Log.e(“context”, "getApplicationContext in Activity: " + getApplicationContext().getClass().getName());
Log.e(“context”, "getBaseContext in Activity: " + getBaseContext().getClass().getName());

第一个 getApplication() ,看下源码就知道了:

public final Application getApplication() {
return mApplication;
}

getApplication() 返回的是当前的 Application 对象。开发者没有声明自己实现的 Application 的话,就是系统默认的 android.app.Application

第二个 getApplicationContext(),它并不是 Activity 中的方法,而是 ContextWrapper 的。直接看源码:

@Override
public Context getApplicationContext() {
return mBase.getApplicationContext();
}

调用的是 ContextImpl.getApplicationContext() 。

@Override
public Context getApplicationContext() {
return (mPackageInfo != null) ?
mPackageInfo.getApplication() : mMainThread.getApplication();
}

所以返回的同样是 Application 对象。

第三个,getBaseContext() ,同样是 ContextWrapper 中的方法:

public Context getBaseContext() {
return mBase;
}

所以这里返回的是 ContextImpl 对象。

最后的打印语句是:

E/context: getApplication in Activity: luyao.android.App
E/context: getApplicationContext in Activity: luyao.android.App
E/context: getBaseContext in Activity: android.app.ContextImpl

关于 Activity 就说这么多了。下面来看看 Service 。

Service 和 Context

Service 其实和 Activity 的整体流程基本一致,创建服务的主要逻辑在 ActivityThread.handleCreateService() 方法中。这里我就不贴源码了,简单叙述一下:

  1. 创建 LoadedApk 对象
  2. 反射创建 Service 对象
  3. 调用 ContextImpl.createAppCntext() 创建 ContextImpl 对象
  4. 创建 Application 对象
  5. 调用 service.attach() 进行绑定
  6. 回调 service 的 onCreate() 方法

直接看一下 Service.attach() 方法:

public final void attach(
Context context,
ActivityThread thread, String className, IBinder token,
Application application, Object activityManager) {
attachBaseContext(context);

}

又看到了熟悉的 attachBaseContext() 方法。

Activity 和 Service 都是继承自 ContextWrapper 的,最后都是通过 attachBaseContext() 对 ContextImpl 类型的 mBase 赋值。而 ContentProvider 和 BroadcastReceiver 都没有继承 Context,所以它们获取 Context 的方式会有一点不一样。

ContentProvider 和 Context

先来看 ContentProvider,创建 Provider 的逻辑在 Activity.installProvider() 方法中:

private ContentProviderHolder installProvider(Context context,
ContentProviderHolder holder, ProviderInfo info,
boolean noisy, boolean noReleaseNeeded, boolean stable) {
ContentProvider localProvider = null;
IContentProvider provider;

// 创建 LoadedApk 和 ContextImpl
c = context.createPackageContext(ai.packageName,Context.CONTEXT_INCLUDE_CODE);

try {

// 创建 ContentProvider
localProvider = packageInfo.getAppFactory()
.instantiateProvider(cl, info.name);
provider = localProvider.getIContentProvider();

// 绑定 Context
localProvider.attachInfo(c, info);
} catch (java.lang.Exception e) {

}


return retHolder;
}

最后在 ContentProvider.attachInfo() 方法中进行了 ContextImpl 的赋值操作。

private void attachInfo(Context context, ProviderInfo info, boolean testing) {
if (mContext == null) {
// 给 mContext 赋值
mContext = context;

// 回调 onCreate()
ContentProvider.this.onCreate();
}
}

这样 ContentProvider 也能拿到 Context 对象了。

BroadcastReceiver 和 Context

最后就是 BroadcastReceiver 了,对应 ActivityThread.handleReceiver()方法:

private void handleReceiver(ReceiverData data) {

// 创建 LoadedApk 对象
LoadedApk packageInfo = getPackageInfoNoCheck(
data.info.applicationInfo, data.compatInfo);

Application app;
BroadcastReceiver receiver;
ContextImpl context;
try {
// 创建 Application 对象
app = packageInfo.makeApplication(false, mInstrumentation);
// 创建 ContextImpl 对象
context = (ContextImpl) app.getBaseContext();

// 创建 BroadcastReceiver 对象
receiver = packageInfo.getAppFactory()
.instantiateReceiver(cl, data.info.name, data.intent);
} catch (Exception e) {

}

try {

// 回调 onReceive()
receiver.onReceive(context.getReceiverRestrictedContext(),
data.intent);
} catch (Exception e) {

} finally {
sCurrentBroadcastIntent.set(null);
}


}

大多数步骤和 Activity 还是类似的,只是到最后回调 onReceive() 方法的时候,才会把 ContextImpl 对象传过去。注意,这里并不是直接返回原生的 ContextImpl 对象,而是调用 context.getReceiverRestrictedContext()返回一个 受限制 的 ReceiverRestrictedContext,你无法使用这个 Context 对象启动 Service 。

这不正是 装饰者模式 的体现?想给广播的 Context 对象加点限制,那就再来一个装饰类 ReceiverRestrictedContext ,它继承了 ContextWrapper , 重写部分方法以限制应用场景。通过增加和组合装饰类,而不是增加子类,来实现功能扩展。

Application 和 Context

四大组件说完了,别忘了 Application 也是 Context 的间接子类。

Application 的创建时机得从应用进程的创建开始说起。Zygote 进程在接收到客户端请求创建应用进程的 socket 请求之后,会 fork 出子进程,并反射调用 ActivityThread 的静态 main() 方法。接着是 AMS 和客户端的一系列 Binder 调用以及 Handler 通信,最终主线程在收到 BIND_APPLICATION 消息之后回调 handleBindApplication() 方法,到这里就是我们需要的逻辑了:

private void handleBindApplication(AppBindData data){

// 获取 ContextImpl
final ContextImpl appContext = ContextImpl.createAppContext(this, data.info);

// 创建 Application 对象
app = data.info.makeApplication(data.restrictedBackupMode, null);

// 调用 Application 的 onCreate() 方法
mInstrumentation.callApplicationOnCreate(app);
}

你可能会疑惑怎么没有回调 attBaseContext() 方法,别急,看看 LoadedApk.makeApplication() 方法是如何创建 Application 的。

public Application makeApplication(boolean forceDefaultAppClass,
Instrumentation instrumentation) {

// 创建 ContextImpl
ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
// 反射创建 Application
app = mActivityThread.mInstrumentation.newApplication(
cl, appClass, appContext);
appContext.setOuterContext(app);
}

通过 Instrumentation.newApplication() 方法创建 Application 。

public Application newApplication(ClassLoader cl, String className, Context context)
throws InstantiationException, IllegalAccessException,
ClassNotFoundException {
// 反射创建
Application app = getFactory(context.getPackageName())
.instantiateApplication(cl, className);
// 重点
app.attach(context);
return app;

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助

因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
链图片转存中…(img-vZuvj41K-1715906447976)]

[外链图片转存中…(img-ouFi9C7l-1715906447980)]

[外链图片转存中…(img-6vQEyStP-1715906447982)]

[外链图片转存中…(img-ybm2Zkgp-1715906447983)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

  • 11
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值