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

