Jetpack 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.

翻譯如下:

應用程序啟動庫提供了一種在應用程序啟動時初始化組件的簡單、高效的方法。庫開發人員和應用程序開發人員都可以使用StartUp來簡化啟動序列并顯式設置初始化順序。
StartUp允許您定義共享單個內容提供程序的組件初始化程序,而不是為每個需要初始化的組件定義單獨的content provider。這可以顯著縮短應用程序啟動時間。

簡單的說就是通過一個公共的content provider來集中管理需要初始化的組件,從而提高應用的啟動速度。

StartUp的使用方法

  • 添加所需依賴

dependencies {
implementation "androidx.startup:startup-runtime:1.0.0"
}

  • 為需要的每一個組件定義一個component initializer,假設存在A,B,C,D四個需要初始化的組件,這時候就需要定義四個initializer.
class ASdk {
    //假設這里是我們需要初始化的組件A
    companion object {
        fun getInstance(): ASdk {
            return Instance.instance
        }
    }
    private object Instance {
        val instance = ASdk()
    }
}

每一個需要初始化的組件我們需要創建一個class去實現Initializer<T>接口,它所對應的Initializer如下:

class ASdkInitializer : Initializer<ASdk> {
    override fun create(context: Context): ASdk {
        Log.i("gj","ASdkInitializer create()方法執行" )
        return ASdk.getInstance()
    }
    override fun dependencies(): MutableList<Class<out Initializer<*>>> {
        Log.i("gj","ASdkInitializer dependencies()方法執行" )
        return mutableListOf()
    }

}

可以看到只有兩個方法需要我們去實現,create ()方法和dependencies()方法。

  • create ()方法包含初始化組件所需的所有操作,并返回T的實例。
  • dependencies()方法返回的是一個Initializer<T>list,這個集合當中包含了當前的Initializer<T>所依賴的其他的Initializer<T>,由此可見該方法的作用是讓我們可以控制在程序啟動時的組件的初始化順序。

比如我們的組件A,B,C的初始化之間存在著C依賴B,B依賴A的這么一種關系,這時B和C組件的Initializer<T>就應該寫成如下:

/**
* B組件的初始化Initializer,依賴A
*/
class BSdkInitializer : Initializer<BSdk> {
  override fun create(context: Context): BSdk {
      Log.i("gj","BSdkInitializer create()方法執行" )
      return BSdk.getInstance()
  }

  override fun dependencies(): MutableList<Class<out Initializer<*>>> {
      Log.i("gj","BSdkInitializer dependencies()方法執行" )
      return mutableListOf(ASdkInitializer::class.java)
  }
}

因為B組件依賴以A組件的初始化完成,所以在dependencies()方法中,我們需要返回的是ASdkInitializer,同理C的Initializer如下:

/**
 * C組件的初始化Initializer,依賴B
 */
class CSdkInitializer : Initializer<CSdk> {
    override fun create(context: Context): CSdk {
        Log.i("gj", "CSdkInitializer create()方法執行")
        return CSdk.getInstance()
    }

    override fun dependencies(): MutableList<Class<out Initializer<*>>> {
        Log.i("gj", "CSdkInitializer dependencies()方法執行")
        return mutableListOf(BSdkInitializer::class.java)
    }
}

至此,我們的對組件的定義基本完成,接下來StartUp是怎么啟動的?


StartUp為我們提供了兩種方式來啟動,一種是自動啟動,一種是手動調用啟動。

  • 自動啟動的方式如下:
    我們只需要在AndroidManifest中對InitializationProvider添加對應聲明,如下:
        <provider
                android:name="androidx.startup.InitializationProvider"
                android:authorities="${applicationId}.androidx-startup"
                android:exported="false"
                tools:node="merge">
            <meta-data
                    android:name="com.yy.myapplication.CSdkInitializer"
                    android:value="androidx.startup"/>
            <meta-data
                    android:name="com.yy.myapplication.DSdkInitializer"
                    android:value="androidx.startup"
                    tools:node="remove" />
        </provider>

meta-data標簽下是我們Initializer的路徑,value需要注意的是必須為androidx.startup,可以看到在上面,A,B,C組件中我只對C的Initializer做了聲明,這是因為B和C都可以通過dependencies()的鏈式調用進行初始化,當然,如果A,B,C之間不存在依賴關系的話,則需要對每一個對應的Initializer進行聲明。而A,B,C的執行順序則與我們的聲明順序保持一致。
至此,程序啟動的時候就會按照我們既定的順序進行初始化操作。
運行后日志如下:

1611313866000.jpg

從日志不難看出:creat()的執行順序為A-->B-->C,dependencies()的執行順序為C-->B-->A,符合我們的預期效果。

  • 手動控制方式如下:
    比如我們還定義了一個DSdkInitializer,這時候需要對D組件手動進行初始化,我們就可以直接對其進行初始化調用:
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        //手動啟動sdk的初始化
        AppInitializer.getInstance(this).initializeComponent(DSdkInitializer::class.java)
    }
}

從日志也可以看到D組件是最后被初始化的。

這里我們需要注意的是,我們雖然在AndroidManifest中聲明了DSdkInitializer但是這個聲明并不是了為了D組件的初始化所做的,可以看到我們加了一個屬性tools:node="remove"這個標簽的作用是為了防止在其他引用的三方庫中有對相同組件的一個初始化,保證該組件的自動初始化真正的被關閉。


  • 關閉startup的所有組件的自動初始化,我們除了可以上訴一個一個關閉的方法,還可以調用如下的方法:
        <provider android:name="androidx.startup.InitializationProvider"
                  android:authorities="${applicationId}.androidx-startup" 
                  tools:node="remove"/>

這樣就可以做到真正的關閉 Startup 的所有自動初始化邏輯。


StartUp 源碼詳解

1611555500174.jpg

由上圖可以看到StartUp包含的類只有五個AppInitializer,InitializationProvider,Initializer,StartupException,StartupLogger下面我們依次對這五個類進行詳細的介紹。


AppInitializer

這個類是StartUp類庫的核心類。

public final class AppInitializer {
              ...
private static AppInitializer sInstance;

    /**
     * Guards app initialization.
     */
    private static final Object sLock = new Object();

    @NonNull
    final Map<Class<?>, Object> mInitialized;

    @NonNull
    final Context mContext;

    /**
     * Creates an instance of {@link AppInitializer}
     *
     * @param context The application context
     */
    AppInitializer(@NonNull Context context) {
        mContext = context.getApplicationContext();
        mInitialized = new HashMap<>();
    }

    /**
     * @param context The Application {@link Context}
     * @return The instance of {@link AppInitializer} after initialization.
     */
    @NonNull
    @SuppressWarnings("UnusedReturnValue")
    public static AppInitializer getInstance(@NonNull Context context) {
        synchronized (sLock) {
            if (sInstance == null) {
                sInstance = new AppInitializer(context);
            }
            return sInstance;
        }
    }
                ...
}

首先通過該類的getInstance(@NonNull Context context)方法獲取我們所需的動態實例。
這里注意一下這個參數的作用`Map<Class<?>, Object> 用map去存儲已經被初始化過的組件。

@NonNull
    @SuppressWarnings("unused")
    public <T> T initializeComponent(@NonNull Class<? extends Initializer<T>> component) {
        return doInitialize(component, new HashSet<Class<?>>());
    }

    @NonNull
    @SuppressWarnings({"unchecked", "TypeParameterUnusedInFormals"})
    <T> T doInitialize(
            @NonNull Class<? extends Initializer<?>> component,
            @NonNull Set<Class<?>> initializing) {
        synchronized (sLock) {
            boolean isTracingEnabled = Trace.isEnabled();
            try {
                if (isTracingEnabled) {
                    // Use the simpleName here because section names would get too big otherwise.
                    Trace.beginSection(component.getSimpleName());
                }
                if (initializing.contains(component)) {
                    String message = String.format(
                            "Cannot initialize %s. Cycle detected.", component.getName()
                    );
                    throw new IllegalStateException(message);
                }
                Object result;
                if (!mInitialized.containsKey(component)) {
                    initializing.add(component);
                    try {
                        Object instance = component.getDeclaredConstructor().newInstance();
                        Initializer<?> initializer = (Initializer<?>) instance;
                        List<Class<? extends Initializer<?>>> dependencies =
                                initializer.dependencies();

                        if (!dependencies.isEmpty()) {
                            for (Class<? extends Initializer<?>> clazz : dependencies) {
                                if (!mInitialized.containsKey(clazz)) {
                                    doInitialize(clazz, initializing);
                                }
                            }
                        }
                        if (StartupLogger.DEBUG) {
                            StartupLogger.i(String.format("Initializing %s", component.getName()));
                        }
                        result = initializer.create(mContext);
                        if (StartupLogger.DEBUG) {
                            StartupLogger.i(String.format("Initialized %s", component.getName()));
                        }
                        initializing.remove(component);
                        mInitialized.put(component, result);
                    } catch (Throwable throwable) {
                        throw new StartupException(throwable);
                    }
                } else {
                    result = mInitialized.get(component);
                }
                return (T) result;
            } finally {
                Trace.endSection();
            }
        }
    }

從上面的代碼可以看到doInitialize(component, new HashSet<Class<?>>())方法最終調用的是result = initializer.create(mContext);不難看出這個方法最終的目的就是完成所有依賴項的初始化。
大致的流程如下:

  • 首先會判斷正在進行初始化的Initializer集合,如果集合中存在component,說明當前Initializer之間存在著循環依賴,會拋出一個循環依賴的異常,如果沒有依賴則繼續向下執行。
  • 如果已經初始化過的mInitialized中不包含component,說明當前的Initializer并為進行初始化,將其加到正在初始化的集合initializing中,之后通過反射調用component的構造方法進行初始化,同時獲取Initializer的所有的依賴項記作dependencies,如果dependencies當中存在依賴,則對每個依賴項通過遞歸調用doInitialize(clazz, initializing)的方式進行初始化。
  • 最終調用initializer.create(mContext)完成初始化,并將已經初始化的Initializer從集合initializing移除,同時將初始化完成的component放到mInitialized中保存起來。
  • 如果已經在mInitialized中包含了component,就只需要從result = mInitialized.get(component);中獲取緩存即可。

下面看一下discoverAndInitialize()方法做了什么?
該方法是由InitializationProvider進行調用,最終會調用的是我們上面提到過的方法doInitialize(component, initializing);

    @SuppressWarnings("unchecked")
    void discoverAndInitialize() {
        try {
            Trace.beginSection(SECTION_NAME);
            ComponentName provider = new ComponentName(mContext.getPackageName(),
                    InitializationProvider.class.getName());
            ProviderInfo providerInfo = mContext.getPackageManager()
                    .getProviderInfo(provider, GET_META_DATA);
            Bundle metadata = providerInfo.metaData;
            String startup = mContext.getString(R.string.androidx_startup);
            if (metadata != null) {
                Set<Class<?>> initializing = new HashSet<>();
                Set<String> keys = metadata.keySet();
                for (String key : keys) {
                    String value = metadata.getString(key, null);
                    if (startup.equals(value)) {
                        Class<?> clazz = Class.forName(key);
                        if (Initializer.class.isAssignableFrom(clazz)) {
                            Class<? extends Initializer<?>> component =
                                    (Class<? extends Initializer<?>>) clazz;
                            if (StartupLogger.DEBUG) {
                                StartupLogger.i(String.format("Discovered %s", key));
                            }
                            doInitialize(component, initializing);
                        }
                    }
                }
            }
        } catch (PackageManager.NameNotFoundException | ClassNotFoundException exception) {
            throw new StartupException(exception);
        } finally {
            Trace.endSection();
        }
    }

  • 首先,會獲取到InitializationProvider中所有的metadata
  • 接著遍歷所有的metadata,找到屬于startup的所有metadata,并通過包名路徑查看是否是Initializer的實現類,如果是的話就進行初始化的操作。

InitializationProvider

InitializationProvider繼承自ContentProvider,主要作用就是觸發對StartUp的整個初始化。

/**
 * The {@link ContentProvider} which discovers {@link Initializer}s in an application and
 * initializes them before {@link Application#onCreate()}.
 *
 * @hide
 */
@RestrictTo(RestrictTo.Scope.LIBRARY)
public final class InitializationProvider extends ContentProvider {
    @Override
    public boolean onCreate() {
        Context context = getContext();
        if (context != null) {
            AppInitializer.getInstance(context).discoverAndInitialize();
        } else {
            throw new StartupException("Context cannot be null");
        }
        return true;
    }
       .....
}

onCreate()方法由系統主動觸發。


Initializer

該類就是StartUp提供的需要我們去聲明初始化的組件以及初始化的依賴關系和順序的。

/**
 * {@link Initializer}s can be used to initialize libraries during app startup, without
 * the need to use additional {@link android.content.ContentProvider}s.
 *
 * @param <T> The instance type being initialized
 */
public interface Initializer<T> {

    /**
     * Initializes and a component given the application {@link Context}
     *
     * @param context The application context.
     */
    @NonNull
    T create(@NonNull Context context);

    /**
     * @return A list of dependencies that this {@link Initializer} depends on. This is
     * used to determine initialization order of {@link Initializer}s.
     * <br/>
     * For e.g. if a {@link Initializer} `B` defines another
     * {@link Initializer} `A` as its dependency, then `A` gets initialized before `B`.
     */
    @NonNull
    List<Class<? extends Initializer<?>>> dependencies();
}
  • T create(@NonNull Context context);方法中完成初始化并返回。
  • List<Class<? extends Initializer<?>>> dependencies();方法中指定當前Initializer的依賴關系。

StartupException

@RestrictTo(RestrictTo.Scope.LIBRARY)
@SuppressWarnings("WeakerAccess")
public final class StartupException extends RuntimeException {
    public StartupException(@NonNull String message) {
        super(message);
    }

    public StartupException(@NonNull Throwable throwable) {
        super(throwable);
    }

    public StartupException(@NonNull String message, @NonNull Throwable throwable) {
        super(message, throwable);
    }
}

RuntimeException的一個自定義的子類,用于StartUp初始化過程中遇到錯誤的拋出類。


StartupLogger

public final class StartupLogger {

    private StartupLogger() {
        // Does nothing.
    }

    /**
     * The log tag.
     */
    private static final String TAG = "StartupLogger";

    /**
     * To enable logging set this to true.
     */
    static final boolean DEBUG = false;

    /**
     * Info level logging.
     *
     * @param message The message being logged
     */
    public static void i(@NonNull String message) {
        Log.i(TAG, message);
    }

    /**
     * Error level logging
     *
     * @param message   The message being logged
     * @param throwable The optional {@link Throwable} exception
     */
    public static void e(@NonNull String message, @Nullable Throwable throwable) {
        Log.e(TAG, message, throwable);
    }
}

這個類就是一個普通的工具類,沒什么好說的。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,797評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,179評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 175,628評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,642評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,444評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,948評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,040評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,185評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,717評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,602評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,794評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,045評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,418評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,671評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,414評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,750評論 2 370

推薦閱讀更多精彩內容