Android使用ContentProvider初始化SDK库方案小结

做Android SDK开发的时候,一般我们会将初始化的方法封装为,然后让调用SDK的开发者在Application的onCreate方法中进行初始化。但是目前一些主流的SDK框架,并没有提供相关的方法进行初始化,但是我们在使用的时候也能正常使用,通过挖掘其源码,可以看出来他们一般使用的ContentProvider来进行SDK的初始化的,目前使用ContentProvider的知名SDK有:ButterKnife、Leakcanary、BlockCanary...等等。

这里补充一个概念,SDK初始化的本质是什么?

SDK初始化的本质是将App的上下文(Context)注入到SDK中,使其能通过这个上下文访问到App的资源与服务。也包括在初始化时调用SDK方法进行相关选项的自定义配置。

一、ContentProvider初始化SDK库的实现

要实现在ContentProvider初始化SDK库,首先要在库中创建一个 ContentProvider,然后在 ContentProvider 的 onCreate() 方法中借助 getContext() 返回的 Context 来完成你的库初始化,当然,这个 Context 的实际类型就是应用的 Application。

下面是通过ContentProvider实现SDK库初始化的示例代码:

class ToolContentProvider : ContentProvider() {

    override fun onCreate(): Boolean {
        Log.e(GlobalConfig.LOG_TAG, "ToolContentProvider onCreate")
        AppContextHelper.init(context!!.applicationContext)
        AppContextHelper.initRoomDB(context!!.applicationContext)
        return true
    }

    override fun query(uri: Uri, projection: Array<out String>?, selection: String?, selectionArgs: Array<out String>?, sortOrder: String?): Cursor? {
        return null
    }

    override fun getType(uri: Uri): String? {
        return null
    }

    override fun insert(uri: Uri, values: ContentValues?): Uri? {
        return null
    }

    override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int {
        return 0
    }

    override fun update(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<out String>?): Int {
        return 0
    }
}
<provider
      android:name=".ToolContentProvider"
      android:authorities="${applicationId}.library-tool"
      android:exported="false" />
class MaoApplication : Application() {

    private lateinit var currentActivityRef: WeakReference<Activity>;

    override fun attachBaseContext(base: Context?) {
        super.attachBaseContext(base)
        Log.e(GlobalConfig.LOG_TAG, "MaoApplication attachBaseContext")
    }

    override fun onCreate() {
        super.onCreate()
        Log.e(GlobalConfig.LOG_TAG, "MaoApplication onCreate")
        initMMKV()
        initCodeView()
	}

    /**
     * 初始化MMKV工具
     */
    private fun initMMKV() {
        Log.e(GlobalConfig.LOG_TAG, "init MMKV")
        MMKV.initialize(this);
    }

    private fun initCodeView() {
        CodeProcessor.init(this)
    }

}

通过ContentProvider实现SDK库初始化的功能实现了,那么 ContentProvider 的 onCreate() 方法是什么时候被调用的呢?

下面是日志输出,来帮助助我们理解初始化时机:

com.renhui.maomaomedia E/MaoMaoMedia: MaoApplication attachBaseContext
com.renhui.maomaomedia E/MaoMaoMedia: ToolContentProvider onCreate
com.renhui.maomaomedia E/MaoMaoMedia: MaoApplication onCreate

可以看到,它是介于 Application 的 attachBaseContext(Context) 和 onCreate() 之间所调用的,Application 的 attachBaseContext(Context) 方法被调用这就意味着 Application 的 Context 被初始化了。这也再次说明我们确实可以通过ContentProvider来进行SDK库的初始化,并且执行时间在Application的onCreate之前。

二、ContentProvider初始化SDK库的优缺点

优点:

  1. 不需要使用SDK库的开发者调用初始化库的流程,降低了接入成本
  2. 代码侵入更低,使得SDK库的代码隔离性做的更好,而且方便升级和维护。

缺点:

  • 不一定适用SDK库的使用场景,因为在 ContentProvider 的 onCreate() 执行在 Application 的 onCreate() 方法之前,倘若你的库需要有其它业务的依赖,那么就不适合这种方式了。
  • 需要注意应用安全漏洞问题,避免组件暴露,需要在声明provider的时候,配置exported为false。
  • 必须注意Provider的authorities千万别写死,否则两个引入同样SDK的App就无法共存了

三、ContentProvider初始化SDK库实现的源码分析

那么为什么在ContentProvider做初始化,能获取到application context的呢?看一下下面几段源码就能知道了。

 private void handleBindApplication(AppBindData data) {
     ....
     final InstrumentationInfo ii;
     ....
     if (ii != null) {
       //1.创建ContentImpl
       final ContextImpl instrContext = ContextImpl.createAppContext(this, pi);
            try {
                final ClassLoader cl = instrContext.getClassLoader();
                mInstrumentation = (Instrumentation)
                    cl.loadClass(data.instrumentationName.getClassName()).newInstance();
            } catch (Exception e) {
                throw new RuntimeException(
                    "Unable to instantiate instrumentation "
                    + data.instrumentationName + ": " + e.toString(), e);
            }

        //2.创建Instrumentation
      final ComponentName component = new ComponentName(ii.packageName, ii.name);
            mInstrumentation.init(this, instrContext, appContext, component,
                    data.instrumentationWatcher, data.instrumentationUiAutomationConnection);
        ....
        //3.创建Application对象
         Application app;
         app = data.info.makeApplication(data.restrictedBackupMode, null);

         // Propagate autofill compat state
            app.setAutofillCompatibilityEnabled(data.autofillCompatibilityEnabled);

            mInitialApplication = app;

        ...
        //4.启动当前进程中的ContentProvider和调用其onCreate方法

        if (!data.restrictedBackupMode) {
                if (!ArrayUtils.isEmpty(data.providers)) {
                    installContentProviders(app, data.providers);
                    // For process that contains content providers, we want to
                    // ensure that the JIT is enabled "at some point".
                    mH.sendEmptyMessageDelayed(H.ENABLE_JIT, 10*1000);
                }
            }

        //5.调用Application的onCreate方法
        try {
                mInstrumentation.callApplicationOnCreate(app);
            } catch (Exception e) {
                if (!mInstrumentation.onException(app, e)) {
                    throw new RuntimeException(
                      "Unable to create application " + app.getClass().getName()
                      + ": " + e.toString(), e);
                }
            }
    }
 }
private void attachInfo(Context context, ProviderInfo info, boolean testing) {
    mNoPerms = testing;

    /*
     * Only allow it to be set once, so after the content service gives
     * this to us clients can't change it.
     */
    if (mContext == null) {
        mContext = context;
        if (context != null) {
            mTransport.mAppOpsManager = (AppOpsManager) context.getSystemService (Context.APP_OPS_SERVICE);
        }
        mMyUid = Process.myUid();
        if (info != null) {
            setReadPermission(info.readPermission);
            setWritePermission(info.writePermission);
            setPathPermissions(info.pathPermissions);
            mExported = info.exported;
            mSingleUser = (info.flags & ProviderInfo.FLAG_SINGLE_USER) != 0;
            setAuthorities(info.authority);
        }
        ContentProvider.this.onCreate();
    }
}

可以看到App的启动过程中加载了provider,并且传了一个Application实例进去,最终在ContentProvider中调用了onCreate()方法。因此,在自定义的ContentProvider中,通过getContext()方法就可以获取到Application的实例了。

其实从这段源码中,我们也可以看到,ContentProvider中的onCreate()方法是先于Application中的onCreate()方法执行的(注意:此时Application对象已经创建)。

四、谷歌的新组件 - App Startup

谷歌推出的App Startup提供了一种在应用程序启动时高效、直接初始化组件的方法。SDK开发人员和APP开发人员都可以使用App Startup简化启动顺序并显式设置初始化顺序。App Startup还允许通过定义共享的ContentProvider统一组件的初始化,大大缩短应用启动时间。

如果项目中的初始化都是同步初始化的话,并且使用到了多个ContentProvider,App Startup 还是不错的,毕竟统一到了一个ContentProvider中,同时支持了简单的顺序依赖。

但是如果在追求App性能与启动速度的场景中,多个SDK同时利用各自定义的ContentProvider实现“自启动”, 在各种有先后顺序与依赖的SDK初始化下做优化,那么 App Startup 就不是很好用了。也正式这个原因,目前不建议将 App Startup 用于生产环境中。

目前的推荐方案还是之前我们都使用过的:同步+异步初始化,并通过有向无环图拓扑排序的方式来保证内部依赖组件的初始化顺序。

到此这篇关于Android使用ContentProvider初始化SDK库方案总结的文章就介绍到这了,更多相关Android初始化SDK库内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Android 利用反射+try catch实现sdk按需引入依赖库的方法

    Android开发sdk过程中,很有可能在sdk内部引入其他的三方sdk库.比如开发sdk过程中可能同时包含Google和Facebook等SDK.但是接入方如果只想要接入包含Google登录的SDK要怎么办呢,gradle想只依赖Google的库不依赖Facebook可以实现吗?本文简单利用反射+try catch即可实现按需接入,无需再新建module和考虑代码分离的问题. 在自己SDK代码中使用三方sdk的地方做如下处理: 原代码: Intent googleSignInIntent =

  • Android Studio下载、安装和配置+SDK+tools下载(无敌超级详细版本)

    下载: Anderson Studio是Google为Android提供的官方IDE工具,下载地址:http://www.android-studio.org/ 下载3.4.1.0版本地址:ctrl+f 查找3.4.1.0 直接下载3.4.1.0的下载地址:https://dl.google.com/dl/android/studio/install/3.4.1.0/android-studio-ide-183.5522156-windows.exe 安装环境要求 : 其中JDK的最低版本是1.

  • Android4.X中SIM卡信息初始化过程详解

    本文实例讲述了Android4.X中SIM卡信息初始化过程详解.分享给大家供大家参考,具体如下: Phone 对象初始化的过程中,会加载SIM卡的部分数据信息,这些信息会保存在IccRecords 和 AdnRecordCache 中.SIM卡的数据信息的初始化过程主要分为如下几个步骤 1.RIL 和 UiccController 建立监听关系 ,SIM卡状态发生变化时,UiccController 第一个去处理. Phone 应用初始化 Phone 对象时会建立一个 RIL 和UiccCont

  • Android Studio设置或修改Android SDK路径方法

    不少朋友自己下载了一个Android SDK,怎样在Android studio中默认的Android SDK路径呢? 打开Android studio,点击"File"菜单下的"Other Settings",接着点击"Default Project Structure"选项. 2.这时就会看到SDK Location,点击图示第二个红色区域的图标,就可以修改默认的AndroidSDK路径. 1.修改SDK路径方法/步骤2 点击"Fi

  • Android Studio报错unable to access android sdk add-on list解决方案

    一.问题 初次安装Android Studio,启动后,报错如下: unable to access android sdk add-on lis 如图: 二.原因 AS启动后,会在默认路径下检测是否有Android SDK,如果没有的话,就会报上述错误. 三.解决方案 3.1 主动设置SDK 如果本机有Android SDK的话,可以点击cancel跳过,在下一个界面手动选择本地SDK目录就可以了. 3.2 跳过检测 在Android Studio的安装目录下,找到\bin\idea.prop

  • 解决Android Studio sdk emulator directory is missing问题

    今天在安装Android Studio时,发现无法下载SDK,如下图所示: 网上看了一圈后,发现是代理的问题. 我之前的代理配置为 意识到是代理的问题后,我尝试把代理配置改成自动检测URL,如下图所示: 然后就可以愉快地下载SDK啦. 到此这篇关于解决Android Studio sdk emulator directory is missing问题的文章就介绍到这了,更多相关Android Studio sdk问题内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

  • Android中实现自动生成布局View的初始化代码方法

    在android开发过程中,界面布局是及其重要的,但同时也是复杂.有的时候我们急于实际运行查看布局效果.但是android的编译速度我实在不想吐槽啥,尤其在布局越来越复杂,项目越来越大,资源文件越来越多的情况下. 尤其是是android的view的初始化,findViewbyId 完全是体力活,我们完全可以根据布局文件自动生成View的初始化代码. 首先声明:   1.这是及其容易做到的,实用性性一般,但是在复杂布局和首次写初始化View代码的时候及其好用. 2.只能生成有id标签的view的初

  • Android使用ContentProvider初始化SDK库方案小结

    做Android SDK开发的时候,一般我们会将初始化的方法封装为,然后让调用SDK的开发者在Application的onCreate方法中进行初始化.但是目前一些主流的SDK框架,并没有提供相关的方法进行初始化,但是我们在使用的时候也能正常使用,通过挖掘其源码,可以看出来他们一般使用的ContentProvider来进行SDK的初始化的,目前使用ContentProvider的知名SDK有:ButterKnife.Leakcanary.BlockCanary...等等. 这里补充一个概念,SD

  • Android利用ContentProvider初始化组件的踩坑记录

    目录 项目描述 问题排查 总结 项目描述 先简单描述一下遇到的问题. 项目比较庞大是以组件化的形式进行构建的,记录崩溃日志是由专门的一个组件去做,这里且叫它crash吧.而crash的核心逻辑如下: //伪代码 public class MyCrash implements UncaughtExceptionHandler { private static UncaughtExceptionHandler defaultUncaughtExceptionHandler; public stati

  • Kotlin + Flow 实现Android 应用初始化任务启动库

    特性 Kotlin + Flow 实现的 Android 应用初始化任务启动库. 支持模块化,按模块加载任务 可指定工作进程名称,main 表示仅在主进程运行,all 表示在所有进程运行,默认值all 可指定任务仅在工作线程执行 可指定任务仅在调试模式执行 可指定任务在满足合规条件后执行 可指定任务优先级,决定同模块内无依赖同步任务的执行顺序 可指定依赖任务列表,能检测循环依赖 使用 Flow 调度任务 仅200多行代码,简单明了 有耗时统计 引入依赖 项目地址:github.com/czy11

  • 一文掌握Redis的三种集群方案(小结)

    在开发测试环境中,我们一般搭建Redis的单实例来应对开发测试需求,但是在生产环境,如果对可用性.可靠性要求较高,则需要引入Redis的集群方案.虽然现在各大云平台有提供缓存服务可以直接使用,但了解一下其背后的实现与原理总还是有些必要(比如面试), 本文就一起来学习一下Redis的几种集群方案. Redis支持三种集群方案 主从复制模式 Sentinel(哨兵)模式 Cluster模式 主从复制模式 1. 基本原理 主从复制模式中包含一个主数据库实例(master)与一个或多个从数据库实例(sl

  • 详解Android v1、v2、v3签名(小结)

    Android签名机制 什么是Android签名 了解 HTTPS 通信的同学都知道,在消息通信时,必须至少解决两个问题:一是确保消息来源的真实性,二是确保消息不会被第三方篡改. 同理,在安装 apk 时,同样也需要确保 apk 来源的真实性,以及 apk 没有被第三方篡改.为了解决这一问题,Android官方要求开发者对 apk 进行签名,而签名就是对apk进行加密的过程.要了解如何实现签名,需要了解两个基本概念:消息摘要.数字签名和数字证书. 消息摘要 消息摘要(Message Digest

  • android studio的使用sdk manager的方法

    author:要你命三千又三千 type :学习笔记整理 整理时间:2019-1-12 问题一:关于sdk manager的使用方式 Tools目录(必须的工具): Android SDK Tools(必须,只需下载一个版本,一般选最新版本):基础工具包,版本号带rc字样的是预览版. Android SDK Platform-tools(必须,只需下载一个版本,一般选最新版本):从android2.3开始划出此目录,存放公用开发工具,比如adb.sqlite3等,被划分到了这里. Android

  • webpack的移动端适配方案小结

    目录 rem vw 适配第三方UI框架 结语 在移动端开发的过程中,一个最常见的问题就是适配不同的屏幕宽度.目前比较常见的适配方案有rem和vw,它们都是css中的相对单位. rem W3C对rem的定义是 font-size of the root element,它是一个只相对于浏览器的根元素(HTML元素)的font-size的来确定的单位,也就是说对于不同宽度的机型,我们只需要计算出对应的根元素的字体大小,用同样的css代码可以实现等比适配.考虑最简单的情况: html代码片段 //移动

  • android 调用JNI SO动态库的方法

    总结一下: android 调用JNI 分为静态调用与动态调用(不论动态还是静态前提都是NDK环境已经配置好的前提下) 一.静态主要就是将c(.c)或者c++(cpp)的源文件直接加到项目中进行调用, 然后在CMakeLists.txt中进行配置. 二.动态调用 1.动态调用使用已经编译好的动态库.so文件 2.android调用ndk类 生成后的so文件 public class SerialPort { p */ public static native int GetSOVer(Strin

  • Android WebView软键盘遮挡输入框方案详解

    目录 背景 纪实 方案 实现 总结 背景 笔者在使用 WebView 加载含有输入框的 H5 页面时,点击输入框后,输入框会被软键盘遮挡住,无法看到输入的内容,这很影响用户体验. 笔者想着这种业务场景比较常见,遂上网搜索一番,果不其然,有不少同志遇到这个问题,想来这个问题很好解决了.笔者一一尝试了同志们提供的解决方案,结果要不是没有作用,要不是效果不太满意,只好自己另辟蹊径了. 注:在笔者的业务场景中,App是全屏的,即没有顶部的系统栏,也没有底部的导航栏,所以笔者的解决方案,可能不适用于其他场

  • Android中关于Binder常见面试问题小结

    目录 1.简单介绍下binder 2.Binder的定向制导,如何找到目标Binder,唤起进程或者线程? 3.Binder中的红黑树,为什么会有两棵binder_ref红黑树 4.Binder一次拷贝原理 5.Binder传输数据的大小限制? 6.系统服务与bindService等启动的服务的区别 7.Binder多线程 8.Android APP进程天生支持Binder通信的原理是什么? 9.同一个线程的请求必定是顺序执行,即使是异步请求(oneway) 1.简单介绍下binder bind

随机推荐