Android使用ContentProvider初始化SDK庫方案小結(jié)
做Android SDK開發(fā)的時(shí)候,一般我們會將初始化的方法封裝為,然后讓調(diào)用SDK的開發(fā)者在Application的onCreate方法中進(jìn)行初始化。但是目前一些主流的SDK框架,并沒有提供相關(guān)的方法進(jìn)行初始化,但是我們在使用的時(shí)候也能正常使用,通過挖掘其源碼,可以看出來他們一般使用的ContentProvider來進(jìn)行SDK的初始化的,目前使用ContentProvider的知名SDK有:ButterKnife、Leakcanary、BlockCanary...等等。
這里補(bǔ)充一個(gè)概念,SDK初始化的本質(zhì)是什么?
SDK初始化的本質(zhì)是將App的上下文(Context)注入到SDK中,使其能通過這個(gè)上下文訪問到App的資源與服務(wù)。也包括在初始化時(shí)調(diào)用SDK方法進(jìn)行相關(guān)選項(xiàng)的自定義配置。
一、ContentProvider初始化SDK庫的實(shí)現(xiàn)
要實(shí)現(xiàn)在ContentProvider初始化SDK庫,首先要在庫中創(chuàng)建一個(gè) ContentProvider,然后在 ContentProvider 的 onCreate() 方法中借助 getContext() 返回的 Context 來完成你的庫初始化,當(dāng)然,這個(gè) Context 的實(shí)際類型就是應(yīng)用的 Application。
下面是通過ContentProvider實(shí)現(xiàn)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實(shí)現(xiàn)SDK庫初始化的功能實(shí)現(xiàn)了,那么 ContentProvider 的 onCreate() 方法是什么時(shí)候被調(diào)用的呢?
下面是日志輸出,來幫助助我們理解初始化時(shí)機(jī):
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() 之間所調(diào)用的,Application 的 attachBaseContext(Context) 方法被調(diào)用這就意味著 Application 的 Context 被初始化了。這也再次說明我們確實(shí)可以通過ContentProvider來進(jìn)行SDK庫的初始化,并且執(zhí)行時(shí)間在Application的onCreate之前。
二、ContentProvider初始化SDK庫的優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
- 不需要使用SDK庫的開發(fā)者調(diào)用初始化庫的流程,降低了接入成本
- 代碼侵入更低,使得SDK庫的代碼隔離性做的更好,而且方便升級和維護(hù)。
缺點(diǎn):
- 不一定適用SDK庫的使用場景,因?yàn)樵?ContentProvider 的 onCreate() 執(zhí)行在 Application 的 onCreate() 方法之前,倘若你的庫需要有其它業(yè)務(wù)的依賴,那么就不適合這種方式了。
- 需要注意應(yīng)用安全漏洞問題,避免組件暴露,需要在聲明provider的時(shí)候,配置exported為false。
- 必須注意Provider的authorities千萬別寫死,否則兩個(gè)引入同樣SDK的App就無法共存了
三、ContentProvider初始化SDK庫實(shí)現(xiàn)的源碼分析
那么為什么在ContentProvider做初始化,能獲取到application context的呢?看一下下面幾段源碼就能知道了。
private void handleBindApplication(AppBindData data) { .... final InstrumentationInfo ii; .... if (ii != null) { //1.創(chuàng)建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.創(chuàng)建Instrumentation final ComponentName component = new ComponentName(ii.packageName, ii.name); mInstrumentation.init(this, instrContext, appContext, component, data.instrumentationWatcher, data.instrumentationUiAutomationConnection); .... //3.創(chuàng)建Application對象 Application app; app = data.info.makeApplication(data.restrictedBackupMode, null); // Propagate autofill compat state app.setAutofillCompatibilityEnabled(data.autofillCompatibilityEnabled); mInitialApplication = app; ... //4.啟動當(dāng)前進(jìn)程中的ContentProvider和調(diào)用其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.調(diào)用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,并且傳了一個(gè)Application實(shí)例進(jìn)去,最終在ContentProvider中調(diào)用了onCreate()方法。因此,在自定義的ContentProvider中,通過getContext()方法就可以獲取到Application的實(shí)例了。
其實(shí)從這段源碼中,我們也可以看到,ContentProvider中的onCreate()方法是先于Application中的onCreate()方法執(zhí)行的(注意:此時(shí)Application對象已經(jīng)創(chuàng)建)。
四、谷歌的新組件 - App Startup
谷歌推出的App Startup提供了一種在應(yīng)用程序啟動時(shí)高效、直接初始化組件的方法。SDK開發(fā)人員和APP開發(fā)人員都可以使用App Startup簡化啟動順序并顯式設(shè)置初始化順序。App Startup還允許通過定義共享的ContentProvider統(tǒng)一組件的初始化,大大縮短應(yīng)用啟動時(shí)間。
如果項(xiàng)目中的初始化都是同步初始化的話,并且使用到了多個(gè)ContentProvider,App Startup 還是不錯(cuò)的,畢竟統(tǒng)一到了一個(gè)ContentProvider中,同時(shí)支持了簡單的順序依賴。
但是如果在追求App性能與啟動速度的場景中,多個(gè)SDK同時(shí)利用各自定義的ContentProvider實(shí)現(xiàn)“自啟動”, 在各種有先后順序與依賴的SDK初始化下做優(yōu)化,那么 App Startup 就不是很好用了。也正式這個(gè)原因,目前不建議將 App Startup 用于生產(chǎn)環(huán)境中。
目前的推薦方案還是之前我們都使用過的:同步+異步初始化,并通過有向無環(huán)圖拓?fù)渑判虻姆绞絹肀WC內(nèi)部依賴組件的初始化順序。
到此這篇關(guān)于Android使用ContentProvider初始化SDK庫方案總結(jié)的文章就介紹到這了,更多相關(guān)Android初始化SDK庫內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Android利用ContentProvider初始化組件的踩坑記錄
- Android中多個(gè)ContentProvider的初始化順序詳解
- Android ContentProvider基礎(chǔ)應(yīng)用詳解
- Android利用ContentProvider獲取聯(lián)系人信息
- Android利用ContentProvider讀取短信內(nèi)容
- Android使用ContentProvider實(shí)現(xiàn)查看系統(tǒng)短信功能
- 詳解Android ContentProvider的基本原理和使用
- Android ContentProvider實(shí)現(xiàn)手機(jī)聯(lián)系人讀取和插入
- Kotlin?ContentProvider使用方法介紹
相關(guān)文章
Android使用ImageView實(shí)現(xiàn)支持手勢縮放效果
這篇文章主要介紹了Android使用ImageView實(shí)現(xiàn)支持手勢縮放效果,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-09-09Android環(huán)形進(jìn)度條(安卓默認(rèn)形式)實(shí)例代碼
這篇文章主要介紹了Android環(huán)形進(jìn)度條(安卓默認(rèn)形式)實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下2016-03-03Android開發(fā)中一個(gè)簡單實(shí)用的調(diào)試應(yīng)用技巧分享
這篇文章主要跟大家分享了一個(gè)簡單實(shí)用的Android調(diào)試應(yīng)用技巧,文中介紹的非常詳細(xì),相信對大家具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友下面來一起看看吧。2017-05-05Android使用DrawerLayout實(shí)現(xiàn)雙向側(cè)滑菜單
這篇文章主要為大家詳細(xì)介紹了Android使用DrawerLayout實(shí)現(xiàn)雙向側(cè)滑菜單,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-11-11Android動畫之補(bǔ)間動畫(Tween Animation)實(shí)例詳解
這篇文章主要介紹了Android動畫之補(bǔ)間動畫(Tween Animation)用法,結(jié)合實(shí)例形式較為詳細(xì)的分析了Android補(bǔ)間動畫的定義,原理,注意事項(xiàng)與相關(guān)使用技巧,需要的朋友可以參考下2016-01-01Android SwipeRefreshLayout下拉刷新源碼解析
這篇文章主要為大家詳細(xì)解析了Android SwipeRefreshLayout下拉刷新源碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-11-11android實(shí)現(xiàn)漢字轉(zhuǎn)拼音功能 帶多音字識別
這篇文章主要介紹了android實(shí)現(xiàn)漢字轉(zhuǎn)拼音功能,帶多音字識別,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-02-02