Jetpack App Startup如何使用及原理分析

2,243 阅读9分钟

1.App Startup是什么?

来自 Google官方App Startup文档: App Startup 库提供了一种在应用程序启动时初始化组件的简单、高效的方法。
Libary开发人员和App开发人员都可以使用App Startup来简化启动顺序并明确设置初始化顺序。

2.简单回顾一下ContentProvider

内容提供者ContentProvider 玩法有很多种:可以进程间通信(数据共享)、ContentResolver#registerContentObserver观察uri变化(可以解析uri来处理各种命令) 等等
ContentProvider通过Binder实现进程间通信,使用起来比AIDL要简单,因为系统已经帮我们进行了封装(这里就不扩散介绍Binder,东西太多)

(1).注册

我们需要在AndroidManifest.xml中注册我们的ContentProvider

//AndroidManifest.xml
<provider
           android:authorities="..."
           android:name="..."/>

上面有两个属性,我们来看一下是干什么的?

  • android:authorities: 一个或多个URI授权方的列表,多个授权方需要使用分号( ; )分隔开。
  • android:name: 完整的ContentProvider类名,如:(com.xxx.xxx.xxxProvider)

(2).实现一个CustomProvider

class CustomProvider: ContentProvider() {
    override fun onCreate(): Boolean {
        // 返回true表示初始化成功
        // 返回false表示初始化失败
    }
    override fun query(uri: Uri, projection: Array<out String>?,selection: String?,selectionArgs: Array<out String>?,sortOrder: String?): Cursor? {
        //查询数据返回Cursor,如果是别的玩法,返回null也可以
    }
    override fun getType(uri: Uri): String? {
        //没有类型的话可以直接返回null
    }
    override fun insert(uri: Uri, values: ContentValues?): Uri? {
        //插入数据,返回uri,如果是别的玩法,返回null也可以
    }
    override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int {
        //删除数据,返回行数,如果是别的玩法,返回-1也可以
    }
    override fun update(uri: Uri,values: ContentValues?,selection: String?,selectionArgs: Array<out String>?): Int {
        //更新数据,返回行数,如果是别的玩法,返回-1也可以
    }
}

(3).ContentProvider在哪初始化的?

我们从ActivityThread的main函数开始跟踪:

//frameworks/base/core/java/android/app/ActivityThread.java
public static void main(String[] args) {
     ....
     ActivityThread thread = new ActivityThread();
     thread.attach(false, startSeq);
     ....
}

我们来看一下ActivityThread的attach方法

private void attach(boolean system, long startSeq) {
    ....
    final IActivityManager mgr = ActivityManager.getService();
    try {
      //通过IBinder进程间通讯调用AMS
      mgr.attachApplication(mAppThread, startSeq);
    } catch (RemoteException ex) {
        .....
    }
    .....
}

打开ActivityManagerService查看attachApplication方法里面会执行什么

//com.android.server.am.ActivityManagerService
public final void attachApplication(IApplicationThread thread, long startSeq) {
        ....
        synchronized (this) {
            ....
            attachApplicationLocked(thread, callingPid, callingUid, startSeq);
            ....
        }
}

private boolean attachApplicationLocked(@NonNull IApplicationThread thread,
        int pid, int callingUid, long startSeq) {
    ......
    try {
        ......
        if (instr2 != null) {
            thread.bindApplication(processName, appInfo, providerList,
                    instr2.mClass,
                    profilerInfo, instr2.mArguments,
                    instr2.mWatcher,....);
        } else {
            thread.bindApplication(processName, appInfo, providerList, null, profilerInfo,
                    null, null, null, testMode,....);
        }
        ......
    } catch (Exception e) {
        ......
    }
    ......
}

看到这里我们发现,调用了ActivityThread里面的bindApplication:

//android.app.ActivityThread.ApplicationThread#bindApplication

public final void bindApplication(String processName, ApplicationInfo appInfo,........){
    .....
    AppBindData data = new AppBindData();
    data.processName = processName;
    data.appInfo = appInfo;
    //ContentProvider列表
    data.providers = providerList.getList();
    .....
    sendMessage(H.BIND_APPLICATION, data);
}

看到这里,仿佛此刻看到了光,BIND_APPLICATION这条消息内部会执行到ActivityThread#handleBindApplication

//android.app.ActivityThread.ApplicationThread#handleBindApplication

private void handleBindApplication(AppBindData data) {
    .....
    Application app;
    .....
    try {
        //触发LoadedApk调用makeApplication
        app = data.info.makeApplication(data.restrictedBackupMode, null);
        .....
        mInitialApplication = app;
        if (!data.restrictedBackupMode) {
            if (!ArrayUtils.isEmpty(data.providers)) {
                //安装ContentProvider入口
                installContentProviders(app, data.providers);
            }
        }
        ....
        try {
            //调用Application#onCreate方法
            mInstrumentation.callApplicationOnCreate(app);
        } catch (Exception e) {
            ....
        }
    } finally {
        .....
    }
    .....
}

handleBindApplication步骤如下:
(1). 执行LoadedApk调用makeApplication
(2). 执行installContentProviders遍历providers列表,实例化ContentProvider
(3). mInstrumentation.callApplicationOnCreate触发Application#onCreate回调

//android.app.ActivityThread#installContentProviders
private void installContentProviders(
        Context context, List<ProviderInfo> providers) {
    final ArrayList<ContentProviderHolder> results = new ArrayList<>();
    for (ProviderInfo cpi : providers) {
        ....
        //真正初始化ContentProvider的地方
        ContentProviderHolder cph = installProvider(context, null, cpi,
                false /*noisy*/, true /*noReleaseNeeded*/, true /*stable*/);
        if (cph != null) {
            cph.noReleaseNeeded = true;
            results.add(cph);
        }
    }
    try {
        //AMS内部会执行mHandler.removeMessages(CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG, r)
        ActivityManager.getService().publishContentProviders(
            getApplicationThread(), results);
    } catch (RemoteException ex) {
        ....
    }
}

installContentProviders步骤如下:
(1). 遍历providers列表,执行installProvider来实例化ContentProvider
(2). AMS调用publishContentProviders清除掉超时的消息,否则会导致应用被kill

//android.app.ActivityThread#installProvider
private ContentProviderHolder installProvider(Context context,
        ContentProviderHolder holder, ProviderInfo info,
        boolean noisy, boolean noReleaseNeeded, boolean stable) {
    ContentProvider localProvider = null;
    IContentProvider provider;
    if (holder == null || holder.provider == null) {
        ....
        Context c = null;
        ApplicationInfo ai = info.applicationInfo;
        //初始化Context
        if (context.getPackageName().equals(ai.packageName)) {
            c = context;
        } else if (mInitialApplication != null &&
                mInitialApplication.getPackageName().equals(ai.packageName)) {
            c = mInitialApplication;
        } else {
            try {
                c = context.createPackageContext(ai.packageName,
                        Context.CONTEXT_INCLUDE_CODE);
            } catch (PackageManager.NameNotFoundException e) {
                // Ignore
            }
        }
        ....
        try {
            final java.lang.ClassLoader cl = c.getClassLoader();
            //ContentProvider和Application的加载器是同一个
            LoadedApk packageInfo = peekPackageInfo(ai.packageName, true);
            if (packageInfo == null) {
                // System startup case.
                packageInfo = getSystemContext().mPackageInfo;
            }
            //通过ClassLoader初始化ContentProvider
            localProvider = packageInfo.getAppFactory()
                    .instantiateProvider(cl, info.name);
            provider = localProvider.getIContentProvider();
            if (provider == null) {
                return null;
            }
            //ContentProvider实例化成功之后,回调ContentProvider#onCreate
            localProvider.attachInfo(c, info);
        } catch (java.lang.Exception e) {
            return null;
        }
    } else {
        provider = holder.provider;
    }
    ....
    return retHolder;
}

installProvider步骤如下:
(1). 获取Context
(2). 通过peekPackageInfo获取LoadedApk,然后通过ClassLoader来实例化ContentProvider
(3). ContentProvider实例化成功之后,attachInfo会触发ContentProvider#onCreate回调

从上面的代码分析之后得出结论:ContentProvider的onCreate会比Application的onCreate先执行

(4).ContentResolver

如果我们需要访问内容提供者里面数据的话,可以使用App的Context中的ContentResolver对象与ContentProvider进行通信。
ContentProvider对象从客户端接收数据请求、执行请求的操作并返回结果。ContentResolver方法可以提供基本的“CRUD”功能。

A.在哪初始化

//frameworks/base/core/java/android/app/LoadedApk.java

public Application makeApplication(boolean forceDefaultAppClass,
Instrumentation instrumentation) {
    ....
    Application app = null;
    ....
    try {
        ....
        //初始化ContextImpl,并初始化一个ApplicationContentResolver「即ContentResolver」
        ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
        ....
        //执行进程中application对象的实例化,并执行Application#attach方法同时触发attachBaseContext回调
        //更多细节,感兴趣的,可以自己看源码查看更多内容
        app = mActivityThread.mInstrumentation.newApplication(
            cl, appClass, appContext);
        //设置applicationContext
        appContext.setOuterContext(app);
    } catch (Exception e) {
        ...
    }
    ....
    mApplication = app;
    if (instrumentation != null) {
        try {
            //触发application的onCreate方法执行
            instrumentation.callApplicationOnCreate(app);
        } catch (Exception e) {
            ....
        }
    }
    ....
    return app;
}

makeApplication简单逻辑如下:
(1).初始化ContextImpl同时初始化ContentResolver
(2).使用ActivityThread中的Instrumentation执行里面的newApplication:
初始化Application并调用attach方法并触发attachBaseContext回调
(3).调用Application的onCreate方法

B.监听数据变化

我们可以使用ContentResolver#registerContentObserver来观察数据是否发生变化。
我们来看一下简单的demo:

//注册内容观察者
contentResolver.registerContentObserver(Uri.parse("....."),true,xxxxCallback)

//不是和数据库进行操作交互的话,自己写的一些功能玩法的uri可以像下面这样
//uri => content://xxxx.service.status
//我们registerContentObserver观察这个原始的uri
//别的进程或者app通过:contentResolver.notifyChange(uri+"/lastPathSegment")
//可以通过下面的方式去解析uri来做一些操作,可玩性也比较好
private val xxxxCallback: ContentObserver = object : ContentObserver(Handler(Looper.getMainLooper())) {
   override fun onChange(selfChange: Boolean, uri: Uri?) {
       super.onChange(selfChange, uri)
       val lastPathSegment = uri?.lastPathSegment
       //有些玩法可以自己定义,比如我们可以拿这个lastPathSegment来实现一些命令操作
       .....
   }
}

C.如何CURD

//查询数据
ContentResolver#query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String)

//插入一条数据
ContentResolver#insert(android.net.Uri, android.content.ContentValues)

//删除数据
ContentResolver#delete(android.net.Uri, java.lang.String, java.lang.String[])

//更新数据
ContentResolver#update(android.net.Uri, android.content.ContentValues, java.lang.String, java.lang.String[])

3.App Startup使用

(1).依赖

dependencies {
    implementation("androidx.startup:startup-runtime:<新版本>")
}

(2).AndroidManifest.xml配置

App Startup是用InitializationProvider来发现和调用你的组件,所以这里我们需要定义<provider/>

<provider
    android:name="androidx.startup.InitializationProvider"
    android:authorities="${applicationId}.androidx-startup"
    android:exported="false"
    tools:node="merge">
    <meta-data  android:name="xxx.xxx.xxxInitializer"
          android:value="androidx.startup" />
</provider>

使用tools:node="merge"合并属性,解决任何冲突的条目,合并到一起。
使用<meta-data/>,这使得我们自定义的Initializer可以被发现。
如果<provider/>里面增加tools:node="remove"禁用所有组件的自动初始化。
如果需要禁用单个组件自动初始化,可以在<meta-data/>里面增加:tools:node="remove"

(3).自定义Initializer

class MyInitializer :Initializer<Boolean>{
    override fun create(context: Context): Boolean {
        //可以在此处进行SDK初始化
	....
        return true
    }
    override fun dependencies(): List<Class<out Initializer<*>>> {
        //没有依赖直接返回emptyList()即可
        //如果有依赖,是针对实现了Initializer的类
        return emptyList()
    }
}

举个例子:
一、Leakcanary目前还没有集成App Startup,那么我们怎么使用呢?
首先我们去AndroidManifest.xml找到Leakcanary的<provider/>

WX20210902-105232@2x.png

然后我们在AndroidManifest.xml找到这两个<provider/>,添加tools:node="remove"

<provider
            android:name="leakcanary.internal.PlumberInstaller"
            android:authorities="写你自己的包名.plumber-installer"
            android:enabled="@bool/leak_canary_plumber_auto_install"
            android:exported="false"
            tools:node="remove"/>
<provider
            android:name="leakcanary.internal.AppWatcherInstaller$MainProcess"
            android:authorities="写你自己的包名.leakcanary-installer"
            android:enabled="@bool/leak_canary_watcher_auto_install"
            android:exported="false"
            tools:node="remove"/>

这个时候我们就可以在我们的App Startup中来控制初始化,愉快的玩耍了

class MyInitializer :Initializer<Boolean>{
    override fun create(context: Context): Boolean {
        val application = context.applicationContext as Application
        AndroidLeakFixes.applyFixes(application)
        AppWatcher.manualInstall(application)
        ....
        return true
    }
    override fun dependencies(): List<Class<out Initializer<*>>> {
        return emptyList()
    }
}

二、WorkManager目前也没有集成App Startup,那么我们自己来扩展一下它实现Initializer,然后再看一下dependencies()的用处

class MyWorkManagerInitializer:Initializer<WorkManager> {
    override fun create(context: Context): WorkManager {
        //集成了WorkManager之后,可以在AndroidManifest.xml中找到默认的provider,可以看到内部实现
        WorkManager.initialize(context, Configuration.Builder().build())
        return WorkManager.getInstance(context)
    }
    override fun dependencies(): List<Class<out Initializer<*>>> {
        return emptyList()
    }
}

我们还需要把WorkManager默认的provider删除掉

<provider
            android:name="androidx.work.impl.WorkManagerInitializer"
            android:authorities="你自己的包名.workmanager-init"
            android:directBootAware="false"
            android:exported="false"
            android:multiprocess="true"
            tools:targetApi="n" 
            tools:node="remove"/>

在我们的MyInitializer中依赖MyWorkManagerInitializer

class MyInitializer :Initializer<Boolean>{
    override fun create(context: Context): Boolean {
        //先触发依赖的执行,执行完,我们这个create才会被执行到,表示依赖已经完成,可以正常使用了
        return true
    }
    //先执行
    override fun dependencies(): List<Class<out Initializer<*>>> {
        return listOf(MyWorkManagerInitializer::class.java)
    }
}

玩法使用如下: (1). 我们可以自己定义扩展的XXXInitializer来替换第三方Libary的ContentProvider,然后在自定义的Initializer的dependencies()中依赖扩展的XXXInitializer
(2). 不自定义扩展,我们知道第三方Libary的ContentProvider内部初始化的代码,可以写在自定义的Initializer的onCreate()中也可以。

4.原理分析

我们在上面提到AndroidManifest.xml中使用了InitializationProvider,它就是一个ContentProvider

//androidx.startup.InitializationProvider

public class InitializationProvider extends ContentProvider {
    @Override
    public final boolean onCreate() {
        Context context = getContext();
        if (context != null) {
            //初始化解析
            AppInitializer.getInstance(context).discoverAndInitialize();
        } else {
            throw new StartupException("Context cannot be null");
        }
        return true;
    }
    ....
}

InitializationProvider#onCreate
获取内容提供者信息,解析meta-data数据,初始化Initializer并执行Initializer#create调用

//androidx.startup.AppInitializer

void discoverAndInitialize() {
    try {
        Trace.beginSection(SECTION_NAME);
        ComponentName provider = new ComponentName(mContext.getPackageName(),
                InitializationProvider.class.getName());
        //通过PackageManager.getProviderInfo获取内容提供者信息
        ProviderInfo providerInfo = mContext.getPackageManager()
                .getProviderInfo(provider, GET_META_DATA);
        //获取元数据
        Bundle metadata = providerInfo.metaData;
        //用于比较元数据里面的数据是不是androidx.startup
        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;
                        mDiscovered.add(component);
                        if (StartupLogger.DEBUG) {
                            StartupLogger.i(String.format("Discovered %s", key));
                        }
                        //初始化Initializer
                        doInitialize(component, initializing);
                    }
                }
            }
        }
    } catch (PackageManager.NameNotFoundException | ClassNotFoundException exception) {
        throw new StartupException(exception);
    } finally {
        Trace.endSection();
    }
}

discoverAndInitialize()逻辑如下:
(1). 通过PackageManager.getProviderInfo获取内容提供者信息 (2). 遍历元数据key列表,比较value是否等于androidx.startup (3). 条件匹配成功,执行doInitialize(component, initializing)

//androidx.startup.AppInitializer#doInitialize

<T> T doInitialize(
        @NonNull Class<? extends Initializer<?>> component,
        @NonNull Set<Class<?>> initializing) {
    synchronized (sLock) {
        try {
            ....
            if (initializing.contains(component)) {
                //没有初始化完成的抛异常
                throw new IllegalStateException(....);
            }
            Object result;
            //缓存里面是否已经有初始化过的数据
            if (!mInitialized.containsKey(component)) {
                //记录正在进行初始化
                initializing.add(component);
                try {
                    //实例化Initializer
                    Object instance = component.getDeclaredConstructor().newInstance();
                    Initializer<?> initializer = (Initializer<?>) instance;
                    //调用initializer.dependencies()
                    List<Class<? extends Initializer<?>>> dependencies =
                            initializer.dependencies();
                    
                    //如果dependencies不为空
                    if (!dependencies.isEmpty()) {
                        for (Class<? extends Initializer<?>> clazz : dependencies) {
                            if (!mInitialized.containsKey(clazz)) {
                                //递归调用
                                doInitialize(clazz, initializing);
                            }
                        }
                    }
                    //回调Initializer#create
                    result = initializer.create(mContext);
                    //Initializer初始化完,移除临时添加的component
                    initializing.remove(component);
                    //添加到缓存中
                    mInitialized.put(component, result);
                } catch (Throwable throwable) {
                    throw new StartupException(throwable);
                }
            } else {
                //从缓存中获取
                result = mInitialized.get(component);
            }
            return (T) result;
        } finally {
            ...
        }
    }
}

doInitialize步骤如下:
(1). 正在初始化的HashSet集合(initializing)中如果包含component,需要抛出异常。
(2). 缓存的Map集合mInitialized中没有component: 先把component记录到正在初始化的HashSet集合(initializing),实例化Initializer。
检查initializer.dependencies()集合是否有数据,如果有数据,需要递归调用doInitialize
如果没有数据直接执行Initializer#create方法的调用,然后从正在初始化的HashSet集合(initializing)移除component,再把component添加到缓存的Map集合mInitialized中。
(3). 缓存的Map集合mInitialized中有component,直接从Map集合中get返回,不用重复初始化。
(4). 从上面代码分析可以看出来:Initializer中dependencies()优先执行,最后再执行Initializer#create

5.总结 + 耗时对比

App Startup VS 多个ContentProvider 耗时对比,默认对比数值是参考什么都不集成的原始apk

我们用三款设备进行测试冷启动比较(大概的一个数据值,仅供参考):

测试设备1: Huawei Mate20-HarmonyOS 2.0.0 001.png


测试设备2: Nexus 6-Android 7.1 002.png


测试设备3: Nokia N1平板-Android 5.1.1 003.png


结论如下:
App Startup使用的好处有:统一管理,简化启动顺序并明确设置初始化顺序。
如果每个Libary库开发者都自己搞一个ContentProvider来初始化,不方便管理。
设计初衷应该是为了收拢ContentProvider,实际上对启动优化的帮助不是特别大。
抛开ContentProvider中初始化数据,如果大家在项目中大多数的时候会使用线程池异步加载初始化一些数据,这个时候App Startup就没有必要使用了。

我们从测试的数据对比可以看出来:
针对少量的SDK使用少量ContentProvider的时候,如果使用App Startup不一定能缩短应用启动时间。
但是在多个不同性能的设备上(尤其是低端机),App Startup启动时间会比使用多个ContentProvider有些许缩短时间。

看完上面的内容,我想大家应该知道App Startup什么时候用,怎么用,为什么要用。
这样我写这篇文章的目的就达到了。

往期文章推荐:
1.源码分析 | ThreadedRenderer空指针问题,顺便把Choreographer认识一下
2.源码分析 | 事件是怎么传递到Activity的?
3.聊聊CountDownLatch 源码
4.Jetpack Compose - Accompanist 组件库
5.Jetpack Compose - FloatingActionButton 展开/折叠 的多级悬浮菜单
6.Jetpack Compose - 实现Text跑马灯Marquee效果
7.如何禁用无障碍模拟点击,提高应用收益