移动开发最新Jetpack 全家桶之 App Startup 看完源码后真不是你们说的那样(2),2024年最新面试官可以带手机吗

《960全网最全Android开发笔记》

《379页Android开发面试宝典》

《507页Android开发相关源码解析》

因为文件太多,全部展示会影响篇幅,暂时就先列举这些部分截图

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

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

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

<uses-sdk

android:minSdkVersion=“14”

android:targetSdkVersion=“30” />

<provider

android:name=“androidx.startup.InitializationProvider”

android:authorities=“${applicationId}.androidx-startup”

android:exported=“false”

tools:node=“merge” />

可以看到,其巧妙的利用了 merge node 进行多个模块的合并,并且其提供了唯一的androidx.startup.InitializationProvider进行统管。我们每个Initializer<T>都是其节点内部的一个 meta-data 项,如下:

<meta-data android:name=“com.example.OverAarExampleInitializer”

android:value=“androidx.startup” />

这些 meta-data 最终都会在androidx.startup.InitializationProvider中进行解析调用,由于 app 启动时 InitializationProvider 会被自动调用,所以接下来我们将流程转到 InitializationProvider,如下:

//【工匠若水 加微信 yanbo373131686 联系我,关注微信公众号:码农每日一题 未经允许严禁转载 https://blog.csdn.net/yanbober】

/**

  • InitializationProvider 用来在 onCreate 中扫描遍历发现 Initializer

  • @hide

*/

@RestrictTo(RestrictTo.Scope.LIBRARY)

public final class InitializationProvider extends ContentProvider {

@Override

public boolean onCreate() {

Context context = getContext();

if (context != null) {

AppInitializer.getInstance(context).discoverAndInitialize();

} else {

throw new StartupException(“Context cannot be null”);

}

return true;

}

//query、getType、insert、delete、update 实现都是抛出 IllegalStateException(“Not allowed.”);,不再给出。

}

很显然,我们需要将视野挪到AppInitializer.getInstance(context).discoverAndInitialize();的实现源码,AppInitializer 类的代码也就是 App Startup 框架的精华了,如下:

public final class AppInitializer {

private static final String SECTION_NAME = “Startup”;

private static volatile AppInitializer sInstance;

private static final Object sLock = new Object();

//已初始化 Initializer 集合

final Map<Class<?>, Object> mInitialized;

//被自动发现的 Initializer 列表

final Set<Class<? extends Initializer<?>>> mDiscovered;

final Context mContext;

/**

  • 非 public 构造方法,让 context 赋值为 getApplicationContext

*/

AppInitializer(@NonNull Context context) {

mContext = context.getApplicationContext();

mDiscovered = new HashSet<>();

mInitialized = new HashMap<>();

}

/**

  • double check 单例模式

  • 对外使用 AppInitializer.getInstance(ctx) 获取 单例的 AppInitializer 实例对象

*/

@NonNull

@SuppressWarnings(“UnusedReturnValue”)

public static AppInitializer getInstance(@NonNull Context context) {

if (sInstance == null) {

synchronized (sLock) {

if (sInstance == null) {

sInstance = new AppInitializer(context);

}

}

}

return sInstance;

}

/**

  • 初始化一个指定 Initializer 的 SDK,返回值为 Initializer 接口中 create 方法的返回值

*/

@NonNull

public T initializeComponent(@NonNull Class<? extends Initializer> component) {

return doInitialize(component, new HashSet<Class<?>>());

}

/**

  • 判断指定的 Initializer 实现 SDK 是否被饥汉式初始化过(即是否被自动初始化方式初始化过)。

  • 这里的饥汉式初始化和单例模式道理一样,即一开始不管三七二十一先把对象 new 出来,

  • 而不是第一次使用时才 new;对应到这里就是是否有被 AppInitializer.getInstance(context).discoverAndInitialize() 处理过。

*/

public boolean isEagerlyInitialized(@NonNull Class<? extends Initializer<?>> component) {

// If discoverAndInitialize() was never called, then nothing was eagerly initialized.

return mDiscovered.contains(component);

}

/**

  • 真正的初始化实现方法

  • 第一个参数为 Initializer 接口的实现 class 对象;

  • 第二个参数为一个初始化临时集合,用来递归去重对比,防止初始化存在循环依赖导致死循环;

*/

@NonNull

T doInitialize(

@NonNull Class<? extends Initializer<?>> component,

@NonNull Set<Class<?>> initializing) {

synchronized (sLock) {

boolean isTracingEnabled = Trace.isEnabled();

try {

if (isTracingEnabled) {

// Use the simpleName here because section names would get too big otherwise.

Trace.beginSection(component.getSimpleName());

}

//如果这次递归依赖初始化存在循环依赖,即已经初始化过,则抛出异常

if (initializing.contains(component)) {

String message = String.format(

“Cannot initialize %s. Cycle detected.”, component.getName()

);

throw new IllegalStateException(message);

}

Object result;

//如果这次递归依赖 Initializer 从来没被初始化过,则开始初始化

if (!mInitialized.containsKey(component)) {

//先加入这次递归依赖初始化的临时 set 中

initializing.add(component);

try {

//实例化一个对应的 Initializer 对象

Object instance = component.getDeclaredConstructor().newInstance();

Initializer<?> initializer = (Initializer<?>) instance;

//得到对应的 Initializer 的 dependencies 方法的返回值

List<Class<? extends Initializer<?>>> dependencies =

initializer.dependencies();

//这里没判断 null,所以 Initializer 的 dependencies 方法一定不能返回 null,即便没依赖也得返回一个 emptyList 集合。

if (!dependencies.isEmpty()) {

//遍历当前 Initializer 的 dependencies 依赖 Initializer 列表

for (Class<? extends Initializer<?>> clazz : dependencies) {

//如果依赖的 Initializer 还没有被初始化过,则递归调用初始化

if (!mInitialized.containsKey(clazz)) {

doInitialize(clazz, initializing);

}

}

}

if (StartupLogger.DEBUG) {

StartupLogger.i(String.format(“Initializing %s”, component.getName()));

}

//调用当次 Initializer 的 create

result = initializer.create(mContext);

if (StartupLogger.DEBUG) {

StartupLogger.i(String.format(“Initialized %s”, component.getName()));

}

//递归回退 Initializer 的 dependencies 和 create 被调用后可以从临时集合移除。

initializing.remove(component);

//将初始化过的 Initializer 放入单例全局的已初始化集合中,方便下一次快速使用

mInitialized.put(component, result);

} catch (Throwable throwable) {

throw new StartupException(throwable);

}

} else {

//单例对象中已经记录过对应 Initializer 的初始化实例则直接返回已初始化对象

result = mInitialized.get(component);

}

return (T) result;

} finally {

Trace.endSection();

}

}

}

/**

  • 被 androidx.startup.InitializationProvider 的 onCreate 自动调用。

  • 去清单文件扫描 meta-data 信息然后初始化。

*/

void discoverAndInitialize() {

try {

Trace.beginSection(SECTION_NAME);

ComponentName provider = new ComponentName(mContext.getPackageName(),

InitializationProvider.class.getName());

ProviderInfo providerInfo = mContext.getPackageManager()

.getProviderInfo(provider, GET_META_DATA);

Bundle metadata = providerInfo.metaData;

//startup=“androidx.startup”,所以每个 InitializationProvider 里的 meta-data 的 value 必须是这个值。

String startup = mContext.getString(R.string.androidx_startup);

//获取 AndroidManifest.xml 的 InitializationProvider 中 meta-data 信息

if (metadata != null) {

Set<Class<?>> initializing = new HashSet<>();

//得到 InitializationProvider 里面所有的 android:name=“com.example.OverAarExampleInitializer” 等注册的 Initializer 实现类

Set keys = metadata.keySet();

for (String key : keys) {

String value = metadata.getString(key, null);

//确保每个 meta-data 都是 android:value=“androidx.startup”

if (startup.equals(value)) {

//得到每一个 Initializer 的 class 对象,譬如 com.example.OverAarExampleInitializer.class

Class<?> clazz = Class.forName(key);

if (Initializer.class.isAssignableFrom(clazz)) {

Class<? extends Initializer<?>> component =

(Class<? extends Initializer<?>>) clazz;

//把每一个 meta-data 发现的 Initializer 实现添加到 mDiscovered Set 集合中。

mDiscovered.add(component);

if (StartupLogger.DEBUG) {

StartupLogger.i(String.format(“Discovered %s”, key));

}

//循环初始化每一个 Initializer 实现类

doInitialize(component, initializing);

}

}

}

}

} catch (PackageManager.NameNotFoundException | ClassNotFoundException exception) {

throw new StartupException(exception);

} finally {

Trace.endSection();

}

}

}

到此整个 App Startup 就算真相大白了,上面分析了自动流程下的源码。对于上一个小节中一般手动用法的原理我们也就不用分析了,因为自动流程里面已经包含了这部分实现。

下面是整体情况的一个源码总结流程图:

在这里插入图片描述

上图描述的场景是清单文件的 meta-data 只显式注册了 A\B\C,C 依赖 D\E,但是经过 App Startup 后 A\B\C\D\E 都被初始化了,且 D\E 优先于 C 被初始化。

总结反思

================================================================

由此可以呼应我们一开始说的了,App Startup 没那么完美,可以说他只解决了特定场景的问题,这种场景其实在所有模块都是自己开发可控源码的情况下还行(尤其是组件化开发中,很好的做到了面向接口编程),但是对于他想真正约束的三方 SDK 来说,按照国内行情,可能短期还是达不到其设计预想的目的。

个人认为目前 App Startup 最好的落地其实是解决组件化编程中 module 全局初始化的一些场景,多 ContentProvider 可以说目前来说还是很难推进三方的。

虽然 App Startup 提供了 dependencies() 依赖能力,但是通过源码我们能发现,其 dependencies() 能力是串行的,没有做到异步并行,也没有提供延迟依赖初始化能力,所以本质上来说他压根没有解决启动性能问题。

另一方面,App Startup 依赖的 Initializer 是运行时被反射发现调用的,所以每一个实现都需要被 keep 住,从这个角度来看他也是不完美的。

有什么推荐

=================================================================

既然 App Startup 不完美,那你肯定会说,有什么完美的推荐吗?我想说,没有,因为初始化都是自己应用业务强相关的,所以适合自己业务的才是最好的。但是业界还是有一些真正在解决启动初始化的框架可以借鉴的,他们真真实实的在解决启动初始化,而不像 App Startup 浪得虚名。

框架推荐

【Alpha启动框架】https://github.com/alibaba/alpha

官方简介:Alpha 是一个基于 PERT 图构建的 Android 异步启动框架,它简单,高效,功能完善。在应用启动的时候,我们通常会有很多工作需要做,为了提高启动速度,我们会尽可能让这些工作并发进行。但这些工作之间可能存在前后依赖的关系,所以我们又需要想办法保证他们执行顺序的正确性。Alpha 就是为此而设计的,使用者只需定义好自己的 task,并描述它依赖的 task,将它添加到 Project 中。框架会自动并发有序地执行这些 task,并将执行的结果抛出来。由于 Android 应用支持多进程,所以 Alpha 支持为不同进程配置不同的启动模式。

【AppStartFaster】https://github.com/NoEndToLF/AppStartFaster

官方简介:AppStartFaster 包含两部分,一部分是冷启动任务分发,一部分是 Multdex 冷启动优化。启动器本质所有任务就是一个有向无环图,通过 Systrace 确定 wallTime 和 cpuTime,然后选择合适的线程池,这里的线程池有两种(cpu 定容线程池,io 缓存线程池),cpuTime 长的就证明他消耗 cpu 时间片,多线程并发的本质就是抢夺时间片,所以 cpuTime 长的要选择定容线程池,防止他并发时候影响 cpu 效率,反之选择缓存线程池,再构造任务之间的图关系,因为有些任务有先后顺序(正确使用启动速度优化 30% 很容易)。5.0 以下开新进程 Activity 去加载 dex,其实就是为了第一时间显示第一个 Activity,属于伪优化,其实在加载 dex 过程中,Multdex 先将 dex 压缩成了 zip,然后又解压 zip,而他是可以直接去加载 dex 的,这里多了一个压缩又解压的过程,所以其实真正的优化应该是避免先解压再压缩。

【Anchors】https://github.com/YummyLau/Anchors

官方简介:Anchors 是一个基于图结构,支持同异步依赖任务初始化 Android 启动框架。其锚点提供 “勾住” 依赖的功能,能灵活解决初始化过程中复杂的同步问题。参考 alpha 并改进其部分细节, 更贴合 Android 启动的场景, 同时支持优化依赖初始化流程, 选择较优的路径进行初始化。

启动优化优质文章推荐

《关于 Android 异步启动框架 alpha 的思考》

《一个更贴近 android 场景的启动框架 | Anchors》

《Rocket-Android启动任务调度框架》

总结

最后为了帮助大家深刻理解Android相关知识点的原理以及面试相关知识,这里放上相关的我搜集整理的14套腾讯、字节跳动、阿里、百度等2021面试真题解析,我把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包知识脉络 + 诸多细节。

2020面试真题解析
腾讯面试真题解析

阿里巴巴面试真题解析

字节跳动面试真题解析
网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。

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

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

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

了视频和PDF(实际上比预期多花了不少精力),包知识脉络 + 诸多细节。

[外链图片转存中…(img-vWzzSIu7-1715449948275)]
[外链图片转存中…(img-5uCzVo7s-1715449948276)]

[外链图片转存中…(img-3pm6wycX-1715449948276)]

[外链图片转存中…(img-hVFmKDvr-1715449948276)]
网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。

[外链图片转存中…(img-hVekfiyx-1715449948277)]

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

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

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

  • 11
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值