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阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容