以前咱們說了啓動優化的一些經常使用方法,可是有的小夥伴就很不屑了:java
「這些方法好久以前就知道了,不知道說點新東西?好比App Startup?能對啓動優化有幫助嗎?」node
ok,既然你誠心誠意的發問了,那我就大發慈悲的告訴你:俺也不知道😢
。android
走吧,一塊兒瞅瞅這個App Startup
吧,是否是真的能給咱們的啓動帶來優化呢?git
(想看結果的能夠直接跳到最後的實踐
和總結
階段)github
想必你們都瞭解,不少三方庫都須要在Application
中進行初始化,並順便獲取到Application
的上下文。面試
可是也有的庫不須要咱們本身去初始化,它偷偷摸摸就給初始化了,用到的方法就是使用ContentProvider
進行初始化,定義一個ContentProvider
,而後在onCreate拿到上下文,就能夠進行三方庫本身的初始化工做了。而在APP的啓動流程中,有一步就是要執行到程序中全部註冊過的ContentProvider
的onCreate方法,因此這個庫的初始化就默默完成了。shell
這種作法確實給集成庫的開發者們帶來了很大的便利,如今不少庫都用到了這種方法,好比Facebook,Firebase
,這裏拿Facebook
舉例看看他的ContentProvider:app
<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進行初始化。異步
雖然更方便了,可是這種作法有給啓動優化帶來什麼好處嗎?咱們一塊兒再回顧下以前的啓動流程研究下,截取一部分:ide
這其中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
。
這樣就至少不會增長啓動耗時了。
怎麼操做呢?假如咱們是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="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="androidx.startup" /> <meta-data android:name="com.example.studynote.appstartup.LibraryBInitializer" android:value="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="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="androidx.startup" tools:node="remove"/> </provider>
這樣FacebookSDK
就不會自動進行初始化了,須要咱們手動調用初始化方法。
1)App Startup
的設計是爲了解決一個問題:
2)App Startup
具體能減小多少耗時時間:
3)App Startup
的使用場景應該是:
4)若是想解決多個庫初始化任務太多致使的啓動耗時
問題:
有一塊兒學習的小夥伴能夠關注下❤️ 個人公衆號——碼上積木,天天剖析一個知識點,咱們一塊兒積累知識。公衆號回覆111可得到面試題《思考與解答》以往期刊。