0
点赞
收藏
分享

微信扫一扫

征服Android面试官路漫漫(三),rrxjava原理

Sikj_6590 2022-01-31 阅读 29

调用了 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 对象。到这里,整个逻辑就通顺了。

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

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;

}

重点就在 Application.attach() 方法。

final void attach(Context context) {

attachBaseContext(context);

mLoadedApk = ContextImpl.getImpl(context).mPackageInfo;

}

在这里调用了 attachBaseContext() 方法进行赋值,也验证了 attachBaseContext() 的确比 onCreate() 先调用。

为什么 Application 的 Context 不可以创建 Dialog ?


使用 Application 的 Context 创建 Dialog 并显示,会报如下错误:

Caused by: android.view.WindowManager$BadTokenException: Unable to add window – token null is not valid; is your activity running?

at android.view.ViewRootImpl.setView(ViewRootImpl.java:951)

at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:387)

at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:96)

at android.app.Dialog.show(Dialog.java:344)

注意错误信息 token null is not valid ,还记得文章前面说到 Activity 和 Context 的时候,有这么一段代码:

mWindow.setWindowManager(

(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),

mToken, mComponent.flattenToString(),

(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);

注意其中的 mToken 参数不为 null ,是不是就说明了 Application 的 token 参数为空呢?

本来准备接着说说这个问题,但可能造成文章篇幅过长,所以 之后会单独来唠唠这个问题。

如何进阶Android?

有些东西你不仅要懂,而且要能够很好地表达出来,能够让面试官认可你的理解,例如Handler机制,这个是面试必问之题。有些晦涩的点,或许它只活在面试当中,实际工作当中你压根不会用到它,但是你要知道它是什么东西。

对于程序员来说,要学习的知识内容、技术有太多太多,要想不被环境淘汰就只有不断提升自己,从来都是我们去适应环境,而不是环境来适应我们!

最后我在这里分享一下这段时间从朋友,大佬那里收集到的一些2019-2020BAT 面试真题解析,里面内容很多也很系统,包含了很多内容:Android 基础、Java 基础、Android 源码相关分析、常见的一些原理性问题等等,可以很好地帮助我们深刻理解Android相关知识点的原理以及面试相关知识。

1、确定好方向,梳理成长路线图

不用多说,相信大家都有一个共识:无论什么行业,最牛逼的人肯定是站在金字塔端的人。所以,想做一个牛逼的程序员,那么就要让自己站的更高,成为技术大牛并不是一朝一夕的事情,需要时间的沉淀和技术的积累。

关于这一点,在我当时确立好Android方向时,就已经开始梳理自己的成长路线了,包括技术要怎么系统地去学习,都列得非常详细。

知识梳理完之后,就需要进行查漏补缺,所以针对这些知识点,我手头上也准备了不少的电子书和笔记,这些笔记将各个知识点进行了完美的总结。

2、通过源码来系统性地学习

只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。

真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。

3、阅读前辈的一些技术笔记

4、刷题备战,直通大厂

历时半年,我们整理了这份市面上最全面的安卓面试题解析大全

包含了腾讯、百度、小米、阿里、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。熟悉本文中列出的知识点会大大增加通过前两轮技术面试的几率。

如何使用它?

1.可以通过目录索引直接翻看需要的知识点,查漏补缺。

要让自己站的更高,成为技术大牛并不是一朝一夕的事情,需要时间的沉淀和技术的积累。

关于这一点,在我当时确立好Android方向时,就已经开始梳理自己的成长路线了,包括技术要怎么系统地去学习,都列得非常详细。

知识梳理完之后,就需要进行查漏补缺,所以针对这些知识点,我手头上也准备了不少的电子书和笔记,这些笔记将各个知识点进行了完美的总结。

2、通过源码来系统性地学习

只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。

真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。

3、阅读前辈的一些技术笔记

4、刷题备战,直通大厂

历时半年,我们整理了这份市面上最全面的安卓面试题解析大全

包含了腾讯、百度、小米、阿里、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。熟悉本文中列出的知识点会大大增加通过前两轮技术面试的几率。

如何使用它?

1.可以通过目录索引直接翻看需要的知识点,查漏补缺。

举报

相关推荐

面试官:三栏布局你会几种?

0 条评论