前言
之前我們說了啟動優化的一些常用方法,但是有的小伙伴就很不屑了:
“這些方法很久之前就知道了,不知道說點新東西?比如App Startup?能對啟動優化有幫助嗎?”
ok,既然你誠心誠意的發問了,那我就大發慈悲的告訴你:俺也不知道??,
走吧,一起瞅瞅這個App Startup吧,是不是真的能給我們的啟動帶來優化呢?
(想看結果的可以直接跳到最后的實踐和總結階段)
Contentprovider中初始化
想必大家都了解,很多三方庫都需要在Application中進行初始化,并順便獲取到Application的背景關系,
但是也有的庫不需要我們自己去初始化,它偷偷摸摸就給初始化了,用到的方法就是使用ContentProvider進行初始化,定義一個ContentProvider,然后在onCreate拿到背景關系,就可以進行三方庫自己的初始化作業了,而在APP的啟動流程中,有一步就是要執行到程式中所有注冊過的ContentProvider的onCreate方法,所以這個庫的初始化就默默完成了,
這種做法確實給集成庫的開發者們帶來了很大的便利,現在很多庫都用到了這種方法,比如Facebook,Firebase,這里拿Facebook舉例看看他的ContentProvider:
<provider
android:name="com.facebook.internal.FacebookInitProvider"
android:authorities="${applicationId}.FacebookInitProvider"
android:exported="false" />
public final class FacebookInitProvider extends ContentProvider {
private static final String TAG = FacebookInitProvider.class.getSimpleName();
@Override
@SuppressWarnings("deprecation")
public boolean onCreate() {
try {
FacebookSdk.sdkInitialize(getContext());
} catch (Exception ex) {
Log.i(TAG, "Failed to auto initialize the Facebook SDK", ex);
}
return false;
}
//...
}
可以看到,在Fackbook的sdk中,定義了一個FacebookInitProvider,并且在onCreate中進行了初始化,所以我們才無需單獨對Facebook的sdk進行初始化,
雖然更方便了,但是這種做法有給啟動優化帶來什么好處嗎?我們一起再回顧下之前的啟動流程研究下,截取一部分:
- ...
- attachBaseContext
- Application attach
- installContentProviders
- Application onCreate
- Looper.loop
- Activity onCreate,onResume
這其中installContentProviders方法就是用來啟動并執行各個ContentProvider的onCreate方法的,它會在Application的onCreate方法之前執行,
所以這些庫只是把Application的三方庫初始化作業提前放到ContentProvider中了,并不會減少啟動耗時,反而會增加啟動耗時,
怎么說呢?因為不同的庫就定義了不同的ContentProvider類,多了這么多ContentProvider,ContentProvider作為四大組件之一,啟動也是耗時的,自然也就增加App啟動消耗的時間了,
這時候就需要App Startup來對此情況進行優化了~
官網簡介
The App Startup library provides a straightforward, performant way to initialize components at application startup. Both library developers and app developers can use App Startup to streamline startup sequences and explicitly set the order of initialization.Instead of defining separate content providers for each component you need to initialize, App Startup allows you to define component initializers that share a single content provider. This can significantly improve app startup time.
主要說了兩點特性:
- 可以共享單個Contentprovider,
- 可以明確地設定初始化順序,
可以共享單個Contentprovider
這一點功能就能解決剛才的問題了,不同的庫不再需要去啟動多個Contentprovider了,而是共享同一個Contentprovider,
這樣就至少不會增加啟動耗時了,
怎么操作呢?假如我們是FacebookSDK設計者,我們就來改一下剛才的FacebookSDK,集成App Startup:
//匯入庫
implementation "androidx.startup:startup-runtime:1.0.0"
// Initializes facebooksdk.
class FacebookSDKInitializer : Initializer<Unit> {
private val TAG = "FacebookSDKInitializer"
override fun create(context: Context): Unit {
try {
FacebookSdk.sdkInitialize(context)
} catch (ex: Exception) {
Log.i(TAG, "Failed to auto initialize the Facebook SDK", ex)
}
}
override fun dependencies(): List<Class<out Initializer<*>>> {
return emptyList()
}
}
//AndroidManifest.xml中定義
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<meta-data android:name="com.example.FacebookSDKInitializer"
android:value="https://www.cnblogs.com/jimuzz/archive/2020/12/21/androidx.startup" />
</provider>
實作了Initializer介面,然后在onCreate方法中進行初始化即可,只要所有的庫都按照這個標準來初始化,而不是自己單獨自定義ContentProvider,那么確實可以減少啟動耗時,
其中,tools:node="merge"標簽就是用來合并所有申明了InitializationProvider的ContentProvider,
等等,Initializer介面還有一個方法dependencies,這又是干啥的呢?
可以明確地設定初始化順序
這也就是App Startup的第二個特性了,可以設定初始化順序,
可以想象,按照上述做法,所有庫都這樣設定了,那么都會在同一個ContentProvider也就是androidx.startup.InitializationProvider中初始化,但是如果我需要設定不同庫的初始化順序怎么辦呢?
比如上述的facebook初始化,我需要設定在另一個庫WorkManager之后運行,那么我們就可以重寫dependencies方法:
class FacebookSDKInitializer : Initializer<Unit> {
private val TAG = "FacebookSDKInitializer"
override fun create(context: Context): Unit {
try {
FacebookSdk.sdkInitialize(context)
} catch (ex: Exception) {
Log.i(TAG, "Failed to auto initialize the Facebook SDK", ex)
}
}
override fun dependencies(): List<Class<out Initializer<*>>> {
return listOf(WorkManagerInitializer::class.java)
}
}
不錯吧,這樣設定之后,三方庫的初始化順序就變成了:
WorkManager初始化 -> FacebookSDK初始化,
實踐出真理
說了這么多,從理論上來說,確實App Startup減少了耗時,畢竟將多個ContentProvider融合成了一個,那么我們秉著“實踐才是檢驗真理的唯一標準”,就來實踐看看耗時減少了多少,
該怎么統計這個啟動時間呢?一般有以下幾個方案:
-
如果是Application和Activity的時間可以通過
TraceView、systrace等 的方式進行時間統計,但是ContentProvider的初始化在Application之前,不適用我們這次實踐, -
Android官方提供了一個可以統計線上應用啟動時間的工具——
Android Vitals,它可以在GooglePlay管理中心顯示應用啟動過長情況的啟動時間,很顯然這個也不適用于我們,這個必須上線到Googleplay, -
視頻錄制,如果是線下的app,我們可以采用
視頻錄制的方法準確測量啟動時間,也就是通過判定視頻的每一幀截圖來知曉什么時候app啟動了,然后統計這個啟動時間,具體做法就是使用adb shell screenrecord命令進行螢屏錄制然后分析視頻,有興趣的小伙伴可以網上找找資料,這里就不細說了, -
最后,就是用系統自帶的統計時間
TotalTime,
這個時間是Android原始碼中幫我們計算的,可統計到Activity的啟動時間,如果我們在Home頁執行命令,也就能得到一個冷啟動的時間,雖然這個時間不是很準確,但是我只需要比較App StartUp使用的的前后時間大小,所以也夠用了,開干,
1)測驗2個ContentProvider
第一次,我們測驗2個ContentProvider的情況,
<provider
android:name=".appstartup.LibraryAContentProvider"
android:authorities="${applicationId}.LibraryAContentProvider"
android:exported="false" />
<provider
android:name=".appstartup.LibraryBContentProvider"
android:authorities="${applicationId}.LibraryBContentProvider"
android:exported="false" />
安裝到手機后,打開應用,Terminal中輸入命令:
adb shell am start -W -n packagename/packageName.MainActivity
由于每次啟動時間不一,所以我們運行五次,取平均值:
TotalTime: 927
TotalTime: 938
TotalTime: 948
TotalTime: 934
TotalTime: 937
平均值:936.8
然后注釋剛才的ContentProvider注冊代碼,添加App startup代碼,并注冊:
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<meta-data android:name="com.example.studynote.appstartup.LibraryAInitializer"
android:value="https://www.cnblogs.com/jimuzz/archive/2020/12/21/androidx.startup" />
<meta-data android:name="com.example.studynote.appstartup.LibraryBInitializer"
android:value="https://www.cnblogs.com/jimuzz/archive/2020/12/21/androidx.startup" />
</provider>
運行App,并執行命令,得出啟動時間:
TotalTime: 931
TotalTime: 947
TotalTime: 937
TotalTime: 940
TotalTime: 932
平均值:937.4
咦??我手機壞了嗎?怎么跟預想的不一樣啊,結果耗時還增加了?
按道理來說原來有兩個ContentProvider,用了App startup,集成為一個,耗時不應該減少么,
其實這就涉及到ContentProvider的實際耗時了,我在網上找到一張圖,關于ContentProvider耗時,是Google官方做的統計,圖片來源于郭神的博客:
可以看到這里統計的1個ContentProvider耗時2ms左右,10ContentProvider耗時6ms左右,
所以我們只減少了一個ContentProvider的耗時,幾乎可以忽略不計,再加上我們用到的App Startup庫中InitializationProvider的一些任務也會產生耗時,比如:
- 會去遍歷所有
metadata標簽的組件 - 會通過反射獲取每個組件的
Initializer介面,并獲取相應的依賴項,并進行排序,
這些操作也是耗時的,也就是集成App Startup庫之后增加的耗時時間,所以就有可能會發生上面的情況了,集成App Startup庫之后啟動耗時反而增多,
那難道這個庫就沒用了嗎?肯定不是的,當ContentProvider的數量變多,它的作用就體現出來了,再試下10個ContentProvider的情況,
2)10個ContentProvider
首先寫好10個ContentProvider,并在AndroidManifest.xml中注冊:
<provider
android:name=".appstartup.LibraryAContentProvider"
android:authorities="${applicationId}.LibraryAContentProvider"
android:exported="false" />
<!-- 省略剩下9個provider注冊代碼 -->
運行五次,取平均值:
TotalTime: 1758
TotalTime: 1759
TotalTime: 1733
TotalTime: 1737
TotalTime: 1747
平均值:1746.8
然后注釋剛才的ContentProvider注冊代碼,添加App startup代碼,并注冊:
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<meta-data android:name="com.example.studynote.appstartup.LibraryAInitializer"
android:value="https://www.cnblogs.com/jimuzz/archive/2020/12/21/androidx.startup" />
<!--省略剩下9個meta-data注冊代碼-->
</provider>
運行App,并執行命令,得出啟動時間:
TotalTime: 1741
TotalTime: 1755
TotalTime: 1722
TotalTime: 1739
TotalTime: 1730
平均值:1737.4
可以看到,這里App Startup的作用就體現了出來,在使用App Startup之前的啟動耗時是1746.8ms,使用之后啟動耗時是1737.4ms,減少了9.4ms,
所以得出結論,當集成的庫使用的ContentProvider達到一定個數之后,確實能減少耗時,但是減少的不多,比如這里我們是10個ContentProvider集成App Startup后能減少的耗時在10ms左右,再結合上圖官方的統計時間來看,一般一個專案集成了十幾個使用ContentProvider的庫,耗時減少應該能在20ms之內,
所以我們的App Startup解決的就是這個耗時時間,雖然不多,但是也確實有減少耗時的功能,
思考
雖然這個庫能解決一定的三方庫初始化耗時問題,但是我覺得還是有很大的局限性,比如這些問題:
本身依賴的庫就不多,如果我們的專案本身依賴就不多,那么有沒有必要去集成這個呢?極端情況下,只依賴了一個庫,那么還要專門提供一個InitializationProvider,是不是又變相的增加了耗時呢?延時初始化,上次我們說過,有些庫并不需要一開始就初始化,那么我們最好將其延遲初始化,進行懶加載,異步初始化,同樣,有些庫不需要在主執行緒進行初始化,那么我們可以對其進行異步初始化,從而減少啟動耗時,多個異步任務依賴關系,如果有些任務需要異步執行的同時還有互相的依賴關系,該怎么辦呢,
如果我們在使用App Startup的時候,有以上需求,那么有沒有解決辦法呢?
- 沒有,也可以說有,就是關閉
App Startup的初始化動作,然后自己進行初始化任務管理,
這可不是開玩笑,App Startup的目的只是解決一個問題,就是多個ContentProvider創建的問題,通過一個統一的ContentProvider來形成規范,減少耗時,所以它的用法應該是針對各個三方庫的設計者,當你設計一個庫的時候,如果想靜默初始化,就可以接入App Startup,當盡量多的庫遵循這個要求,都接入App Startup的時候,開發者的啟動耗時自然就降低了,
但是如果我們有其他的需求,比如上述說到的延遲初始化,異步初始化等問題,我們就要關閉部分庫或者所有庫的App Startup的功能,然后自己單獨對任務進行初始化作業,比如通過啟動器來處理各個初始化任務的關系,
如果一個庫已經集成了App Startup功能,我們該怎么關閉呢?這就用到tools:node="remove"標簽了,
<!-- 禁用所有InitializationProvider組件初始化 -->
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
tools:node="remove" />
<!-- 禁用單個InitializationProvider組件初始化 -->
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<meta-data android:name="com.example.FacebookSDKInitializer"
android:value="https://www.cnblogs.com/jimuzz/archive/2020/12/21/androidx.startup"
tools:node="remove"/>
</provider>
這樣FacebookSDK就不會自動進行初始化了,需要我們手動呼叫初始化方法,
總結
1)App Startup的設計是為了解決一個問題:
- 即不同的庫使用不同的ContentProvider進行初始化,導致ContentProvider太多,管理雜亂,影響耗時的問題,
2)App Startup具體能減少多少耗時時間:
- 上面也實踐過了,如果二三十個三方庫都集成了App Startup,減少的耗時大概在20ms以內,
3)App Startup的使用場景應該是:
- 針對三方庫的設計者或者組件化的場景,當你設計一個庫或者一個組件的時候,就可以接入App Startup,當盡量多的庫遵循這個標準,都接入App Startup的時候,就能形成一種規范,App的啟動耗時自然就降低了,
4)如果想解決多個庫初始化任務太多導致的啟動耗時問題:
- 請左轉前往各種啟動器,比如alibaba/alpha
參考
Google檔案
App Startup-郭霖
Android啟動時間—siyu8023
App Startup原始碼—葉志陳
拜拜
有一起學習的小伙伴可以關注下?? 我的公眾號——碼上積木,每天剖析一個知識點,我們一起積累知識,公眾號回復111可獲得面試題《思考與解答》以往期刊,
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/238475.html
標籤:其他
上一篇:探究 | App Startup真的能減少啟動耗時嗎
下一篇:Android期末整理(一)

