Android 组件初始化三种方式

方式一:使用 Application#onCreate 进行初始化

使用方式

  1. 自定义 CustomApplication
Kotlin 复制代码
class CustomApplication : Application() {
    // ..
    override fun onCreate() {
        super.onCreate()
        // 进行组件初始化
    }
    // ..
}
  1. 主module 清单文件中声明使用 CustomApplication
xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    <!-- ... -->
    <application android:name="CustomApplication全路径或者相对于资源的路径">
        <!-- ... -->
    </application>
</manifest>

优缺点

  • 优点:简单易用,只需在 主moduleApplication#onCreate 进行组件初始化,并且可以指定组件间初始化顺序;
  • 缺点:在其他 主module 使用的时候,需要在自身 Application#onCreate 进行一遍初始化(对应组件依赖方来说,较为繁琐);同时如果组件存在依赖关系,使用方要清楚组件之间的关系,从而确定组件初始化顺序,增加组件使用成本;

方式二:使用 Content Provider 进行初始化

使用方式

  1. 自定义 ContentProvider
Kotlin 复制代码
class CustomProvider : ContentProvider() {
    
    override fun onCreate(): Boolean {
        // 初始化组件
        return true
    }

    override fun query(
        uri: Uri,
        projection: Array<out String>?,
        selection: String?,
        selectionArgs: Array<out String>?,
        sortOrder: String?
    ): Cursor? = null

    override fun getType(uri: Uri): String? = null

    override fun insert(uri: Uri, values: ContentValues?): Uri? = null

    override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int = 0

    override fun update(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<out String>?
    ): Int = 0
}
  1. 自定义 ContentProvider 在当前组件 AndroidManifest.xml 进行声明
xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    <!-- ... -->
    <application>
        <!-- 
             1. name 为 CustomProvider 的全路径,或者相对于资源文件的路径 
             2. authorities 加上 ${applicationId} 可以有效避免多个应用依赖该组件重复,导致不能安装问题
             3. exported = false,让外部不能使用,纯粹只是为了当前组件的初始化
        -->
        <provider
            android:name="CustomProvider的路径"
            android:authorities="${applicationId}.custom-startup"
            android:exported="false" />
        <!-- ... -->
    </application>
</manifest>

优缺点

  • 优点:在应用启动的时候,系统会调用 ContentProvider#onCreate 进行初始化,则避免组件使用方主moduleApplication#onCreate 初始化,对于组件使用方 来说是无感知的
  • 缺点:如果组件间初始化是有依赖性,则多个 Provider 在清单文件中的顺序是有要求的,但这个是非常困难进行调整的,并且很容易错;同时 Provider实例化成本高昂 ,在不必要的情况下可能会拖慢启动序列

ContentProvider#onCreate 初始化路径(基于android33)

  • ActivityThread#main(zygote 新建进程,执行 ActivityThreadmain 方法,执行主 Looper 循环)
  • ActivityThread#attach(ActivityThread 初始化)
  • ActivityManagerService#attachApplication(Binder 调用,从应用 => ActivityManagerService)
  • ApplicationThread#bindApplication(Binder 调用,从 ActivityManagerService => 应用)
  • H#handleMessage(BIND_APPLICATION)(使用 H 进行分发)
  • ActivityThread#handleBindApplication(当前应用绑定 application)
  • ActivityThread#installContentProviders(安装多个 Provider)
  • ActivityThread#installProvider(安装单个 Provider)
  • ContentProvider#attachInfo
  • ContentProvider#onCreate

方式三:使用 Jetpack startup 组件库进行初始化

使用方式

  1. module 模块的 build.gradle 引用 Jetpack startup
groovy 复制代码
dependencies {
    implementation "androidx.startup:startup-runtime:1.1.1"
}
  1. 继承 Initializer<*>,重写 onCreatedependencies 方法,
Kotlin 复制代码
class CustomInitializer : Initializer<Custom> {
    
    override fun create(context: Context): Custom {
        // Custom 初始化
        return Custom.getInstance(context)
    }
    
    override fun dependencies(): List<Class<out Initializer<*>>> {
        // 声明当前依赖的类库
        return emptyList()
    }
}
  1. 在模块 AndroidManifest.xml 中声明
xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    <!-- ... -->
    <application>
        <!-- 
             1. name 为固定 androidx.startup.InitializationProvider,则知道是用这个来进行初始化的
             2. authorities = "${applicationId}.androidx-startup" 用于避免多个应用导致的重复
             3. exported 仅供自身使用,不对外暴露
             4. tools:node = "merge" 表示合并相同的 InitializationProvider,在这里代表合并其他组件的 meta-data
-->
        <provider
            android:name="androidx.startup.InitializationProvider"
            android:authorities="${applicationId}.androidx-startup"
            android:exported="false"
            tools:node="merge">
            <!-- 
                 1.  name 为 CustomInitializer 的全路径,或者相对于资源文件的路径
                 2.  value 为固定的 androidx.startup,用于提取 CustomInitializer 的路径
-->
            <meta-data  android:name="CustomInitializer的路径"
                  android:value="androidx.startup" />
        </provider>
        <!-- ... -->
    </application>
    
</manifest>

优缺点

  • 优点:解决方法二中的2个问题;在整体应用中,只存在一个 Provider,并且 Initializer#dependencies 中声明该组件所依赖组件 ,在初始该组件 的时候会先初始化该组件所依赖组件
  • 缺点:从 Initializer#dependencies 方法参数可知,需要所依赖组件 实现 Initializer 才可以,需要改动原来的组件;但如果后续的组件都按照此来声明,对于使用方来说,会更加简单

源码解析

  1. android:name 可知 InitializationProvider 入口
Java 复制代码
// androidx.startup.InitializationProvider
public class InitializationProvider extends ContentProvider {

    @Override
    public final boolean onCreate() {
        Context context = getContext();
        if (context != null) {
            Context applicationContext = context.getApplicationContext();
            if (applicationContext != null) {
                // 调用 AppInitializer 的 discoverAndInitialize
                AppInitializer.getInstance(context).discoverAndInitialize();
            } else {
                StartupLogger.w("Deferring initialization because `applicationContext` is null.");
            }
        } else {
            throw new StartupException("Context cannot be null");
        }
        return true;
    }

    // ...
}
  1. AppInitializer#discoverAndInitialize
Java 复制代码
public final class AppInitializer {

    private static volatile AppInitializer sInstance;
    private static final Object sLock = new Object();
    final Map<Class<?>, Object> mInitialized;  
    final Set<Class<? extends Initializer<?>>> mDiscovered;
    final Context mContext; 

    @NonNull
    public static AppInitializer getInstance(@NonNull Context context) {
        // 双重校验获取 AppInitializer
        if (sInstance == null) {
            synchronized (sLock) {
                if (sInstance == null) {
                    sInstance = new AppInitializer(context);
                }
            }
        }
        return sInstance;
    }

    AppInitializer(@NonNull Context context) {
        mContext = context.getApplicationContext(); // application context
        mDiscovered = new HashSet<>();  // 已发现的Initializer
        mInitialized = new HashMap<>(); // 已初始化的内容
    }
    
    void discoverAndInitialize() {
        try {
            // step1: 创建包含 InitializationProvider 类信息的 ComponentName
            ComponentName provider = new ComponentName(mContext.getPackageName(),
                    InitializationProvider.class.getName());
            // step2: 获取 InitializationProvider 对应的 META_DATA 信息
            ProviderInfo providerInfo = mContext.getPackageManager()
                    .getProviderInfo(provider, GET_META_DATA);
            Bundle metadata = providerInfo.metaData;
            // step3: 解析metadata
            discoverAndInitialize(metadata);
        } catch (PackageManager.NameNotFoundException exception) {
            throw new StartupException(exception);
        }
    }

    void discoverAndInitialize(@Nullable Bundle metadata) {
        // step4: 获取 startup 字符串
        String startup = mContext.getString(R.string.androidx_startup);
        try {
            if (metadata != null) {
                // step5: initializing 记录正在初始化的类,主要用于防止循环引用
                Set<Class<?>> initializing = new HashSet<>();
                Set<String> keys = metadata.keySet();
                for (String key : keys) {
                    String value = metadata.getString(key, null);
                    // step6: 这里限定了 android:value 只能为 startup 的值
                    if (startup.equals(value)) {
                        // step7: 反射获取此类,并检查是否是 Initializer 的子类
                        Class<?> clazz = Class.forName(key);
                        if (Initializer.class.isAssignableFrom(clazz)) {
                            Class<? extends Initializer<?>> component =
                                    (Class<? extends Initializer<?>>) clazz;
                            // step8: 记录当前类进入发现集合中
                            mDiscovered.add(component);
                        }
                    }
                }
                // step9: 遍历当前发现的类,开始初始化
                for (Class<? extends Initializer<?>> component : mDiscovered) {
                    // Tips: initializing 记录正在初始化的类,这里为空集合
                    doInitialize(component, initializing);
                }
            }
        } catch (ClassNotFoundException exception) {
            throw new StartupException(exception);
        }
    }
    
    @NonNull
    @SuppressWarnings({"unchecked", "TypeParameterUnusedInFormals"})
    private <T> T doInitialize(
            @NonNull Class<? extends Initializer<?>> component,
            @NonNull Set<Class<?>> initializing) {
        try {
            // step10: 如果 initializing 包含 component,则证明出现循环依赖
            if (initializing.contains(component)) {
                String message = String.format(
                        "Cannot initialize %s. Cycle detected.", component.getName()
                );
                throw new IllegalStateException(message);
            }
            
            Object result;
            // step11: 检查此类是否已初始化,已初始化这直接返回,反之进行初始化
            if (!mInitialized.containsKey(component)) {
                initializing.add(component);
                
                try {
                    // step12: 使用反射调用构造参数;由这可知这里需要一个默认的空参的构造函数
                    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) {
                            // step13: 如果当前依赖组件未进行初始化,则进行初始化
                            if (!mInitialized.containsKey(clazz)) {
                                doInitialize(clazz, initializing);
                            }
                        }
                    }
                    
                    // step14: 调用 Initializer#create 创建组件
                    result = initializer.create(mContext);
                    // step15: 当前组件初始化完成,从正在初始化集合移除和加入已初始化集合中
                    initializing.remove(component);
                    mInitialized.put(component, result);
                } catch (Throwable throwable) {
                    throw new StartupException(throwable);
                }
            } else {
                result = mInitialized.get(component);
            }
            return (T) result;
        }
    }
    
    // ======================= 剩余方法 =======================

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


    public boolean isEagerlyInitialized(@NonNull Class<? extends Initializer<?>> component) {
        return mDiscovered.contains(component);
    }

    @NonNull
    @SuppressWarnings({"unchecked", "TypeParameterUnusedInFormals"})
    <T> T doInitialize(@NonNull Class<? extends Initializer<?>> component) {
        Object result;
        synchronized (sLock) {
            result = mInitialized.get(component);
            if (result == null) {
                result = doInitialize(component, new HashSet<Class<?>>());
            }
        }
        return (T) result;
    }    
}

Tips

从上述的源码解析可知,整个过程分为2部分

  1. 提取当前 Providermeta-data 数据,从 meta-data 上可以获取对应 Initializer 的类信息
  2. 根据提取到 Initializer 的类信息,进行反射调用构造函数和调用 onCreate 方法;

因此可以不使用第一步进行初始化,选择合适时机进行初始化,也就官网说的延迟初始化 , 此时调用上述 AppInitializer#initializeComponent 进行初始化

默认行为是从 Provider 清单文件声明 meta-data 提取类信息,因此当不需要某个初始化的时候,可以屏蔽对应 meta-data 的类信息;或者使用 aapt2 所带的 tools:node="remove" 进行移除

xml 复制代码
<provider
    android:name="androidx.startup.InitializationProvider"
    android:authorities="${applicationId}.androidx-startup"
    android:exported="false"
    tools:node="merge">
    <!-- tools:node="remove" 在构建的时候,不会打进去 -->
    <meta-data android:name="com.example.ExampleLoggerInitializer"
              tools:node="remove" />
</provider>

同时 tools:node="remove" 也适用于 provider 结点,使整个 provider 结点移除

xml 复制代码
<provider
    android:name="androidx.startup.InitializationProvider"
    android:authorities="${applicationId}.androidx-startup"
    tools:node="remove" />
相关推荐
阿巴斯甜19 小时前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker20 小时前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq952721 小时前
Andorid Google 登录接入文档
android
黄林晴1 天前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab1 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿2 天前
Android MediaPlayer 笔记
android
Jony_2 天前
Android 启动优化方案
android
阿巴斯甜2 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇2 天前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_2 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android