Jetpack App Startup——SDK自动初始化,告别Init

2023-10-27

一个项目在开发过程中,常常都伴随着很多sdk的依赖。大部分的sdk在使用时都需要在应用启动时进行初始化才能正常工作,所以在集成sdk时通常需要做如下操作:

在Application的onCreate内调用对应sdk的初始化方法,目的是:

  1. 保证在sdk使用之前,sdk需要的一些准备工作已经完成;

  2. 为sdk内部提供application context;

所以我们常常会看到类似的调用:

XxxSDK.init(application)

最近发现一种sdk自动初始化的方案,可以告别sdk集成手动初始化。

ContentProvide想必都不陌生吧

定义一个ContentProvider,然后在mainfest注册声明后,它就能在应用启动时被创建

通过分析应用的启动流程可以发现如下的执行顺序

Application attachBaseContext
           |
           |
           V
ContentProvide attachInfo
           |
           |
           V
ContentProvide onCreate
           |
           |
           V
Application onCreate

通常初始化sdk都是在Application的onCreate完成的,那么我们在ContentProvide的attachInfo和onCreate中进行sdk的初始化是不是也能保证sdk的初始化时机?通过ContentProvide是不是也能获取到了Application Context,那么开始说到的sdk初始化的两个目的是不是也同样能达到?

现在实现思路便有了:

可以在sdk内注册应以一个ContentProvide,然后在它的onCreate内自行完成sdk的初始化任务,那么在app集成该sdk时候就不用在app层显式的编写sdk初始化逻辑便可以直接调用sdk的相关功能。

这里做一个思考?

一个APP的完成往往需要依赖很多的sdk?那么如果每个sdk都去创建一个ContentProvide,是不是会大大增加APP的启动时长,那岂不是有点得不偿失了。

如何避免每个sdk都创建一个ContentProvide,能不能都共用一个ContentProvide呢,答案时可以的,并且google官方已经提供了对应的开源库。

App Startup | Android Developers (google.cn)

实现很简单,源码总共就五个文件,主要代码就几百行
在这里插入图片描述

常规流程,先看看怎么使用

1、添加版本依赖

dependencies {
    implementation("androidx.startup:startup-runtime:1.1.1")
}

2、定义一个用于初始化的类, implements Initializer<T>,这里就直接引用官方的例子吧

// Initializes WorkManager.
class WorkManagerInitializer : Initializer<WorkManager> {
    override fun create(context: Context): WorkManager {
        val configuration = Configuration.Builder().build()
        WorkManager.initialize(context, configuration)
        return WorkManager.getInstance(context)
    }
    override fun dependencies(): List<Class<out Initializer<*>>> {
        // No dependencies on other libraries.
        return emptyList()
    }
}

Initializer接口需要实现两个方法

--create():完成对应的初始化操作
--dependencies(): 需要返回一个List,Initializer实现类的Class,
这是代表初始化该任务需要依赖的其他初始化项,添加返回值后,会将该初始化任务依赖的初始化项
放到它的前面,保证它所依赖的初始化项先进行初始化。

如下例子,定义初始化类ExampleLoggerInitializer,它需要先执行上面WorkManagerInitialize的初始化任务,则实现如下:

// Initializes ExampleLogger.
class ExampleLoggerInitializer : Initializer<ExampleLogger> {
    override fun create(context: Context): ExampleLogger {
        // WorkManager.getInstance() is non-null only after
        // WorkManager is initialized.
        return ExampleLogger(WorkManager.getInstance(context))
    }

    override fun dependencies(): List<Class<out Initializer<*>>> {
        // Defines a dependency on WorkManagerInitializer so it can be
        // initialized after WorkManager is initialized.
        return listOf(WorkManagerInitializer::class.java)
    }
}

3、在mainfest注册对应的Contentprovide,并声明需要执行的初始化任务

<provider
    android:name="androidx.startup.InitializationProvider"
    android:authorities="${applicationId}.androidx-startup"
    android:exported="false"
    tools:node="merge">
    <!-- This entry makes ExampleLoggerInitializer discoverable. -->
    <meta-data  android:name="com.example.ExampleLoggerInitializer"
          android:value="androidx.startup" />
</provider>


//tools:node="merge"  Mainfest 合并冲突的节点标记:合并此标记中的所有属性以及所有嵌套元素
//多个sdk使用startup同时声明注册InitializationProvider进行合并


//meta-data:声明了需要执行初始化的Initializer实现类

好了,通过以上的几步就可以实现一个sdk的自动初始化,而无需再到application里面显式的手动调用初始化的方法。


说完了怎么使用的,再来简单的看看Startup内部时怎么将开始说的实现思路转变成代码的吧,总共也就几百行代码。

开头已经介绍了它的实现思路,利用Contentprovide来完成sdk内部的自动初始化,而上面的第三步在Mainfest内注册声明的InitializationProvider是不是就是对应的用来完成初始化的?那我们便从InitializationProvider来切入:

    /** 主要实现就一个onCreate方法,其他的增删改查方法不允许被调用直接抛出异常
      * 按照思路onCreate内进行初始化任务,可以发现这里只调用了AppInitializer的discoverAndInitialize方法
      * discoverAndInitialize字面理解(发现并且初始化)不难看出AppInitializer内主要的逻辑就是解析需要初始化的
      * Initializer都有哪些,然后逐一调用初始化方法
      **/
    @Override
    public final boolean onCreate() {
        Context context = getContext();
        if (context != null) {
            Context applicationContext = context.getApplicationContext();
            if (applicationContext != null) {
                AppInitializer.getInstance(context).discoverAndInitialize();
            } else {
                StartupLogger.w("Deferring initialization because `applicationContext` is null.");
            }
        } else {
            throw new StartupException("Context cannot be null");
        }
        return true;
    }return true;
    }

接着看AppInitializerdiscoverAndInitialize

void discoverAndInitialize() {
        try {
            ComponentName provider = new ComponentName(mContext.getPackageName(),
                    InitializationProvider.class.getName());
            ProviderInfo providerInfo = mContext.getPackageManager()
                    .getProviderInfo(provider, GET_META_DATA);
            Bundle metadata = providerInfo.metaData;
            discoverAndInitialize(metadata);
        } catch (PackageManager.NameNotFoundException exception) {
            throw new StartupException(exception);
        } finally {
        }
    }
/**
  * 该方法很简单,就是获取在Mainfest注册的InitializationProvider下声明的metadata数据
  * 拿到metadata数据干什么用呢?很显然metadata标签声明的是Initializer的实现类,即每个需要初始化的任务的
  **/

接着看拿到metadata后调用的方法

void discoverAndInitialize(@Nullable Bundle metadata) {
        String startup = mContext.getString(R.string.androidx_startup);
        try {
            if (metadata != null) {
                Set<Class<?>> initializing = new HashSet<>();
                Set<String> keys = metadata.keySet();
                //1、遍历所有的metadata标签声明的数据
                /** 还记得metadata标签都声明了哪些值吗?
                  * name: Initializer实现类
                  * value:androidx.startup
                 */
                for (String key : keys) {
                    String value = metadata.getString(key, null);
                    //2、判断每个metadata是不是都带androidx.startup
                    if (startup.equals(value)) {
                        //3、将Initializer Class添加到mDiscovered中,为解析出来的需要初始化的任务
                        Class<?> clazz = Class.forName(key);
                        if (Initializer.class.isAssignableFrom(clazz)) {
                            Class<? extends Initializer<?>> component =
                                    (Class<? extends Initializer<?>>) clazz;
                            mDiscovered.add(component);
                        }
                    }
                }
                // 4、遍历mDiscovered,将Initializer逐个执行初始化
                for (Class<? extends Initializer<?>> component : mDiscovered) {
                    doInitialize(component, initializing);
                }
            }
        } catch (ClassNotFoundException exception) {
            throw new StartupException(exception);
        }
    }

接着看doInitialize

private <T> T doInitialize(
            @NonNull Class<? extends Initializer<?>> component,
            @NonNull Set<Class<?>> initializing) {
        try {
            //1、initializing这是在上个方法中创建的map,当开始执行每个Initializer初始化之前会将其加入到
            // initializing map中,然后在执行完成后再remove掉
            // 所以此处是防止正在被出初始化的任务再次调用
            if (initializing.contains(component)) {
                String message = String.format(
                        "Cannot initialize %s. Cycle detected.", component.getName()
                );
                throw new IllegalStateException(message);
            }
            Object result;
            // 2、mInitialized记录了被初始化Initializer, 防止同一个初始化任务多次初始化
            if (!mInitialized.containsKey(component)) {
                initializing.add(component);
                try {
                    //3、这里反射创建出Initializer对象,获取到Initializer初始化任务的依赖项
                    // Initializer接口实现的dependencies返回的依赖列表
                    Object instance = component.getDeclaredConstructor().newInstance();
                    Initializer<?> initializer = (Initializer<?>) instance;
                    List<Class<? extends Initializer<?>>> dependencies =
                            initializer.dependencies();
                    //4、这里将依赖项遍历,先将依赖项进行初始化,递归回到了doInitialize方法
                    if (!dependencies.isEmpty()) {
                        for (Class<? extends Initializer<?>> clazz : dependencies) {
                            if (!mInitialized.containsKey(clazz)) {
                                doInitialize(clazz, initializing);
                            }
                        }
                    }
                  //5、最后再调用当前initializer的create方法,所以Initializer实现类的初始化任务在create中调用
                    result = initializer.create(mContext);
                    //6、初始化完成,将initializer从正在初始化的记录中移除
                    initializing.remove(component);
                    //7、initializer放入已经完成初始化的记录中mInitialized
                    mInitialized.put(component, result);
                } catch (Throwable throwable) {
                    throw new StartupException(throwable);
                }
            } else {
                result = mInitialized.get(component);
            }
            return (T) result;
        } finally {
        }
    }

到这里,整个startup的使用和实现原理都介绍完了,赶快使用体验下吧,当然我们也完全可以自己写一个Startup了。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Jetpack App Startup——SDK自动初始化,告别Init 的相关文章

  • Whatsapp 在 Android 中共享音频文件时正在剪切音频文件

    我在共享格式不是 mp3 的音频文件时遇到问题 文件被共享 但长度较短 例如 如果文件有 10 秒 则仅共享 5 秒 如果我使用 mp3 格式 它会完全共享 但其他格式会出现问题 注意 该文件在其他应用程序 如Messenger 中共享没有
  • Android 构建 gradle 在特定设备上失败

    我面临一个奇怪的问题 当我编译我的应用程序以在 Android 7 0 的设备上运行它时 它可以工作 但是当我尝试为 Android 4 2 1 的设备进行编译时 它会失败并出现以下错误 错误 任务 app transformClasses
  • ActionBarCompat 支持库 android:selectableItemBackground 不起作用

    我正在使用新的 ActionBarCompat 支持库 操作栏中的操作按钮在按下时应更改其背景 它适用于 Android 4 3 但不适用于 Gingerbread 在姜饼中 如果我按下按钮 它不会改变背景 我什至改变了选择器 它再次适用于
  • Android,让文本切换器成为中心?

    如何集中我的文本切换器 我尝试过设置重力 但似乎不起作用 ts setFactory new ViewFactory public View makeView TextView t new TextView this t setTypefa
  • 如何在 Android 中创建刮刮卡?

    我需要为我在学校的期末项目创建一个 刮刮卡 应用程序 但找不到如何实现刮刮事件的方法 如何创建背景图像并在其上放置灰色矩形 所以当我刮刮这些矩形时我会看到他们下面的图片 实现必须在 Android 中 因为我还不知道如何在 Objectiv
  • PhoneGap 是应用程序开发的好选择吗? [关闭]

    就目前情况而言 这个问题不太适合我们的问答形式 我们希望答案得到事实 参考资料或专业知识的支持 但这个问题可能会引发辩论 争论 民意调查或扩展讨论 如果您觉得这个问题可以改进并可能重新开放 访问帮助中心 help reopen questi
  • Android,语言文件不起作用

    我现在正在创建一个 Android 应用程序 并尝试为我的母语添加语言文件 但在某种程度上 这对我不起作用 我尝试在两部不同的手机中加载该应用程序 但结果相同 之前创建过语言文件 效果良好 但这次不行 手机设置为瑞典语 语言文件适用于我创建
  • 从Asynctask返回结果

    如果我的 Android 应用程序中有这个后台工作文件 并且它从我的数据库获取数据 我如何将字符串 结果 传递给另一个类 后台工作人员连接到我的服务器 然后使用 php 连接到数据库 public class BackgroundWorke
  • 注销时Firebase facebook按钮android身份验证

    我在我的 Android 应用程序中使用 firebase 并在 facebook SDK 中使用登录 我面临的唯一问题是 当我使用 facebook 登录然后注销时 facebook 登录按钮处于 注销 状态 当我单击它时 它会询问我是否
  • 在 Android 中关闭 Spinner 中的下拉菜单

    在 Android 中打开和关闭微调器时 我需要为箭头图标设置动画 打开微调器时我可以旋转箭头 我只是放了一个setOnTouchListener on the Spinner 当下拉菜单关闭或隐藏时 问题就来了 因为我不知道如何在该操作上
  • 使用 POST 将数据从 Android 发送到 AppEngine Datastore

    抱歉 如果这是一个简单的问题 但我只是不知道我应该做什么 而且我认为我有点超出了我的深度 我想将数据从 Android 应用程序发送到在 Google App Engine 上运行的应用程序 数据必须从那里写入数据存储区 我的数据主要采用对
  • NullPointerException org.chromium.android_webview.AwContents$AwViewMethodsImpl.onDragEvent

    大约 10 天前 我的应用程序开始记录此异常 在开发控制台上看到 java lang NullPointerException at org chromium android webview AwContents AwViewMethods
  • 如何在Firebase Android应用程序中分离两个不同的用户?

    我有一个应用程序 有两种不同类型的用户 一种是教师 第二种是普通用户 如果普通会员登录 他会去normal memberActivity如果他是教师会员 他会去Teacher memberActivity 我如何在登录活动中执行此操作 我的
  • 如何在不改变的情况下将字符串转换为字节?

    我需要一个解决方案将字符串转换为字节数组而不需要像这样进行更改 Input String s Test Output String s Test byte b Test 当我使用 s getBytes 那么回复是 B 428b76b8 但我
  • 将搜索结果更新为 Android 中的 Lazy Adapter

    我有项目列表 想为其实现搜索功能 因此 我有一个带有 addTextChangedListener 的文本框 搜索结果运行良好 但当我尝试将结果设置为 ListView 时 新结果将附加到旧结果中 我正在使用惰性适配器 如何清除适配器中的旧
  • BluetoothLeScanner 服务内部问题

    Update从Android 10以上我认为你需要ACCESS BACKGROUND LOCATION权限 因此 如果此代码在最新的 Android 版本上不起作用 就是针对此问题的 ACCESS BACKGROUND LOCATION 受
  • ImageButton 拉伸背景图像

    我正在尝试创建一个没有边框的 ImageButton 但遇到了图像按钮大小的问题 我使用 Eclipse ADT 将 ImageButton 拖到布局中并选择背景图像 图像按钮显示如下 正如您所看到的 背景图像和图像按钮周边之间有一个边框
  • 找不到资源矢量绘图的异常

    我将在某些设备上运行我的应用程序 其崩溃日志如下 01 04 16 54 02 206 7466 7466 com lawnmowers E AndroidRuntime FATAL EXCEPTION main Process com l
  • 如何在Linux中自动启动需要X的应用程序

    我试图在系统进入运行级别 5 时自动启动 X 应用程序 这样做的正确方法是什么 我写了一个脚本并将其放在 etc init d 中 我已运行适当的 chkconfig 命令来设置 etc rcX d 目录中的符号链接 一切工作正常 除了当我
  • 在 TextView onTextChanged 上设置文本

    我有一个定义为类属性的文本视图 以便我可以在整个类中访问它 在 onCreate 方法中我执行以下操作 chars TextView findViewById R id chars chars setText 300 之后 public v

随机推荐

  • 拒绝从入门到入土:初识C语言

    目录 一 什么是C语言 1 C语言是与计算机交流的语言 2 计算机的组成 3 计算机语言的发展是从低级到高级的 C语言的发展也不例外 4 什么是编译 二 第一个C语言程序 1 步骤 2 main函数的使用 3 其他问题 三 常量与变量 1
  • 机器学习—有监督学习—KNN-K近邻法(k-NearestNeighbor)

    一 KNN简介 KNN K Nearest Neighbor 最邻近分类算法是数据挖掘分类 classification 技术中最简单的算法之一 其指导思想是 近朱者赤 近墨者黑 即由你的邻居来推断出你的类别 实现原理 为了判断未知样本的类
  • javascript - 实现拍照功能(详细示例代码)

    介绍 HTML5 的 getUserMedia API 为用户提供访问硬件设备媒体 摄像头 视频 音频 地理位置等 的接口 基于该接口 开发者可以在不依赖任何浏览器插件的条件下访问硬件媒体设备 另外 主流浏览器 Firefox Chrome
  • MatLab 中计算开根号

    原文地址为 MatLab 中计算开根号 1 方法 例如 如果对4要开根号 可以输入函数如下 gt gt sqrt 4 2方法 根号其实是1 2次方 gt gt 4 1 2 同样可以得到结果 转载请注明本文地址 MatLab 中计算开根号
  • ANOMALY简记-ANOMALY LOCALITY IN VIDEO SURVEILLANCE

    创新点 提出了一个带有标记的异常检测库 总结 http imagelab ing unimore it UCFCrime2Local
  • linux编译安装kvm、qemu

    kvm作为主流虚拟化产品 其实它的用户层使用的是qemu 所以要安装使用kvm 一般需要安装kvm kmod以及qemu两部分 安装kvm kmod 1 首先下载kvm kmod源码并解压 2 进入源码目录 3 configure kern
  • Spring Web MVC框架(五) 文件上传

    Spring同样支持文件上传功能 不过该功能默认未开启 因为可能有些开发者可能希望自己处理文件上传过程 Spring的文件上传功能在org springframework web multipart包下 有两个MultipartResolv
  • gitLab清理大文件_包括历史记录中的大文件

    文章目录 gitLab清理大文件 包括历史记录中的大文件 前言 查看仓库大小 解除保护分支 操作 gitLab清理大文件 包括历史记录中的大文件 项目中经常有不小心提交的大文件 这个就是清理方法 后面发现了更好的方法 使用bfg快速清理gi
  • 02.01 服务器开机报错

    Dell PowerEdge T320服务器 开机显示 Fatal Errort all channells have been disabled due to DIMMS falled the memory 原因 内存自检失败 可能是太久
  • yolo算法的优缺点分析_【精选推荐】基于深度学习的单阶段目标检测算法研究综述...

    DOI 10 12132 ISSN 1673 5048 2019 0100 引用格式 刘俊明 孟卫华 基于深度学习的单阶段目标检测算法研究综述 J 航空兵器 2020 27 3 44 53 Liu Junming Meng Weihua R
  • 【Python深度学习】RNN循环神经网络结构讲解及序列回归问题实战(图文解释 附源码)

    需要全部代码请点赞关注收藏后评论区留言私信 循环神经网络 循环神经网络 Recurrent Neural Network RNN 是用于对序列的非线性特征进行学习的深度神经网络 循环神经网络的输入是有前后关联关系的序列 循环神经网络可以用来
  • Spring Cache常用注解

    EnableCaching 开启缓存注解功能 Cacheable 在方法执行前spring先查看缓存中是否有数据 如果有数据 则直接返回缓存数据 若没有数据 调用方法并将方法返回值放到缓存中 CachePut 将方法的返回值放到缓存中 Ca
  • JS 实现 AES 加解密(十六进制)

    引入 aes min js js文件 aes min js Javascript文档类资源 CSDN下载js文件 aes min js更多下载资源 学习资料请访问CSDN下载频道 https download csdn net downlo
  • Python中用户管理(用户的登陆、用户的增删改查)

    一 用户登陆 题目要求 1 系统里面有多个用户 用户的信息目前保存在列表里面 users root westos passwd 123 456 2 用户登陆 判断用户登陆是否成功 1 判断用户是否存在 2 如果存在 1 判断用户密码是否正确
  • C: GNU regex library (regex.h)正则表达式调用示例

    GNU regex是GNU提供的跨平台的POSIX 正则表达式库 C语言 我也是最近才接触这个相对于C Java实现来说非常简陋 勉强够用的正则表达式库 不算GNU提供的扩展函数 POSIX标准的regex库总共就4个函数regcomp r
  • 第一章 数据结构绪论

    一 数据结构的基本概念 数据结构的三要素 逻辑结构 定义 存储结构 数据的运算 基本运算 增删改查 二 算法和算法评价 时间复杂度 时间复杂度的关键 就是想办法求出次数
  • 在虚拟机中安装Linux系统CentOS7详细教程

    虚拟机 VMware Workstation Linux CentOS 7 x86 64 DVD 1708 iso镜像文件 下载 虚拟机所在电脑系统 windows 安装步骤 安装VMware Workstation虚拟机 略 下载Linu
  • swiper 滚回第一个数据_数据库发展史(上)

    数据库技术是信息技术领域的核心技术之一 几乎所有的信息系统都需要使用数据库系统来组织 存储 操纵和管理业务数据 数据库领域也是现代计算机学科的重要分支和研究方向 目前 在数据库领域已经产生了四位图灵奖得主 他们在数据库理论和实践领域均有突出
  • DocArray x Weaviate

    Weaviate 作为 DocArray 中的 Document Store 可以使得 Document 在云端的处理和检索更加迅速 DocArray Weaviate 大起底 DocArray Data structure for uns
  • Jetpack App Startup——SDK自动初始化,告别Init

    一个项目在开发过程中 常常都伴随着很多sdk的依赖 大部分的sdk在使用时都需要在应用启动时进行初始化才能正常工作 所以在集成sdk时通常需要做如下操作 在Application的onCreate内调用对应sdk的初始化方法 目的是 保证在