Jetpack Startup源码分析

7 篇文章 0 订阅
5 篇文章 0 订阅

Jetpack Startup源码分析

Jetpack给我们提供一种组件,可以让应用启动时的初始化操作放在一起,在我们启动应用的时候进行初始化。

Startup的用法很简单,就需要我们实现Initializer接口,并在AndroidManifest清单文件进行<provider>的注册并给该provider添加相关的类的meta-data即可使用。

简单的用法

清单文件对androidx.startup.InitializationProvider进行注册

<provider
          android:name="androidx.startup.InitializationProvider"
          android:authorities="${applicationId}.androidx-startup"
          android:exported="false"
          tools:node="merge">

  <meta-data
             android:name="com.android.jetpackstartup.ExampleInitializer"
             android:value="androidx.startup" />
</provider>

创建我们的Initializer并实现Initializer接口

class ExampleInitializer : Initializer<Unit> {
    override fun create(context: Context) {
        ToastUtils.init(context)
        return
    }
  //依赖项
    override fun dependencies(): MutableList<Class<out Initializer<*>>> {
        return ArrayList()
    }
}

运行:

image-20210608004145440

源码分析

通过下图可以看到Startup所涉及的类不多,我们通过源码看看实现方式。

image-20210607233231654

InitializationProvider

我们首先看InitializationProvider:

@RestrictTo(RestrictTo.Scope.LIBRARY)
public final class InitializationProvider extends ContentProvider {
    @Override
    public boolean onCreate() {
        Context context = getContext();//获取上下文
        if (context != null) {
            AppInitializer.getInstance(context).discoverAndInitialize();//查找Initializer并进行初始化
        } else {
            throw new StartupException("Context cannot be null");
        }
        return true;
    }

    @Nullable
    @Override
    public Cursor query(
            @NonNull Uri uri,
            @Nullable String[] projection,
            @Nullable String selection,
            @Nullable String[] selectionArgs,
            @Nullable String sortOrder) {
        throw new IllegalStateException("Not allowed.");
    }

    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
        throw new IllegalStateException("Not allowed.");
    }

    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
        throw new IllegalStateException("Not allowed.");
    }

    @Override
    public int delete(
            @NonNull Uri uri,
            @Nullable String selection,
            @Nullable String[] selectionArgs) {
        throw new IllegalStateException("Not allowed.");
    }

    @Override
    public int update(
            @NonNull Uri uri,
            @Nullable ContentValues values,
            @Nullable String selection,
            @Nullable String[] selectionArgs) {
        throw new IllegalStateException("Not allowed.");
    }
}

InitializationProvider是一个ContentProvider,如果有用过LeakCanary2并且看过该库的源码或对LeakCanary2的自动初始化有过了解的话就会知道LeakCanary2其实也是用ContentProvider来实现初始化,即不用开发人员手动进行初始化。我在制作一个开源库(还没完全完成)的时候也是使用了这个方式在debug时进行无侵入式初始化。

ContentProvider初始化时机

ContentProvider的onCreate方法是在Application的attachBaseContext之后在onCreate之前。

我们可以通过自定义ApplciationContentProvider来验证这一点

class MApplication : Application() {
    companion object {
        private const val TAG = "MApplication"
    }

    override fun attachBaseContext(base: Context?) {
        super.attachBaseContext(base)
        Log.d(TAG, "attachBaseContext: ")
    }

    override fun onCreate() {
        super.onCreate()
        Log.d(TAG, "onCreate: ")
    }
}
class MContentProvider : ContentProvider() {
    companion object {
        private const val TAG = "MContentProvider"
    }

    override fun onCreate(): Boolean {
        Log.d(TAG, "onCreate: ")
        return true
    }
  ....
}
<application
       ...
       ...
             >
  <provider
            android:name=".MContentProvider"
            android:authorities="${applicationId}.mProvider"
            android:exported="false" />
</application>
image-20210607234509950

前面补了一下ContentProvider的初始化时机与一些开源库除了用于内容提供还用来做别的用途的案例举例。

回头看InitializationProvider,该ContentProvider的最主要的也是唯一一个有用的方法是onCreate方法。

@Override
public boolean onCreate() {
  Context context = getContext();//获取上下文
  if (context != null) {
    AppInitializer.getInstance(context).discoverAndInitialize();//查找Initializer并进行初始化
  } else {
    throw new StartupException("Context cannot be null");
  }
  return true;
}

在该方法中获取了AppInitializer单例对象并调用了discoverAndInitialize方法。

AppInitializer

首先看一下AppInitializer的单例是如何实现的。

@NonNull
final Map<Class<?>, Object> mInitialized;//已初始化缓存

@NonNull
final Set<Class<? extends Initializer<?>>> mDiscovered;//已发现缓存

AppInitializer(@NonNull Context context) {
  mContext = context.getApplicationContext();
  mDiscovered = new HashSet<>();
  mInitialized = new HashMap<>();
}

private static volatile AppInitializer sInstance;

public static AppInitializer getInstance(@NonNull Context context) {
  if (sInstance == null) {
    synchronized (sLock) {
      if (sInstance == null) {
        sInstance = new AppInitializer(context);
      }
    }
  }
  return sInstance;
}

通过上述代码可以知道这是一个很标准的双检锁(DCL)单例模式。

AppInitializer#discoverAndInitialize

我们继续看discoverAndInitialize方法:

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);//通过PackageManager获取ProviderInfo信息        Bundle metadata = providerInfo.metaData;//获取metaData        String startup = mContext.getString(R.string.androidx_startup);//        if (metadata != null) {//如果metadata不为空            Set<Class<?>> initializing = new HashSet<>();//初始化内容缓存HashMap            Set<String> keys = metadata.keySet();//获取键集合,这里的键就是我们定义的name即我们的类名            for (String key : keys) {//遍历                String value = metadata.getString(key, null);//获取value                if (startup.equals(value)) {//如果value为androidx_startup                    Class<?> clazz = Class.forName(key);//获取Class                    if (Initializer.class.isAssignableFrom(clazz)) {//该类实现了Initializer接口                        Class<? extends Initializer<?>> component =                                (Class<? extends Initializer<?>>) clazz;//Class类型转换                        mDiscovered.add(component);//                        if (StartupLogger.DEBUG) {//如果是Debug打印日志                            StartupLogger.i(String.format("Discovered %s", key));                        }                        doInitialize(component, initializing);//调用初始化方法                    }                }            }        }    } catch (PackageManager.NameNotFoundException | ClassNotFoundException exception) {        throw new StartupException(exception);    } finally {        Trace.endSection();//性能检测工具结束    }}

discoverAndInitialize方法中通过组件名与PackageManager获取ProviderInfo,并从ProviderInfo获取metaData并获取该metaData获取键集合,该集合为我们定义的name即我们的类名。在该meta-data的value为androidx_startup时候调用反射获取该类并判断是否实现了Initializer接口,如果实现了将该类放进已发现HashMap中进行缓存。最终调用doInitialize(component, initializing);方法进行初始化。

AppInitialize#doInitialize
		@NonNull		final Map<Class<?>, Object> mInitialized;		@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)) {//如果initializing中已经有相同的类                    String message = String.format(                            "Cannot initialize %s. Cycle detected.", component.getName()                    );                    throw new IllegalStateException(message);                }                Object result;                if (!mInitialized.containsKey(component)) {//已初始化缓存中没有此类                    initializing.add(component);//将其加入到初始化中HashMap中                    try {                        Object instance = component.getDeclaredConstructor().newInstance();//通过反射获取构造函数并进行实例化                        Initializer<?> initializer = (Initializer<?>) instance;//转换类型                        List<Class<? extends Initializer<?>>> dependencies =                                initializer.dependencies();//获取该Initializer的依赖,主要用于初始化排序,比如A、B、C,其中A依赖与B所以B需要在A之前进行初始化。                        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);//调用initializer的create方法,在此时调用了我们的create方法我们在此方法中的初始化逻辑进行调用                        if (StartupLogger.DEBUG) {                            StartupLogger.i(String.format("Initialized %s", component.getName()));                        }                        initializing.remove(component);//从初始化中HashMap移除该内容                        mInitialized.put(component, result);//加入到已初始化缓存中                    } catch (Throwable throwable) {                        throw new StartupException(throwable);                    }                } else {//如果已经初始化设置返回对象                    result = mInitialized.get(component);                }                return (T) result;//返回对象            } finally {                Trace.endSection();            }        }    }

通过doInitialize方法我们可以知道此方法是一个递归方法,在我们编写的Initializer没有被初始化即不在mInitialized这个HashMap中的时候,进行初始化相关的逻辑,在Initializer的依赖项不为空的时候通过递归优先进行依赖项的初始化,之后进行该Initiallizer的create调用,在初始化完毕后将初始化过的对象加入到已初始化缓存中。

总结

StartUp的源码内容很少(嗯,非常非常少),实现方式也很简单,但是它却很精妙,Startup通过自己实现ContentProvider通过ContentProvider在Application的attachBaseContext之后调用onCreate的特性,这么做可以实现对初始化方法的自动调用,也同时可以优化应用启动时间,通过阅读Startup等源码可以知道设计模式的巧妙用法、面向对象的三大特性的合理使用等技巧,通过阅读与分析这些优秀的项目、组件源码可以给自己提供很不错的思路与技巧,阅读源码对自己的提升帮助还是很大的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
作为AI助手,我无法直接安装nvidia-jetpack,但是我可以为您提供一些步骤,帮助您完成源码安装nvidia-jetpack的过程。 1.下载nvidia-jetpack源码 您可以从官方网站下载nvidia-jetpack源码包,下载后解压到本地目录。 2.安装依赖包 在安装nvidia-jetpack前,您需要安装一些必要的依赖包,可以使用以下命令安装: ``` sudo apt-get update sudo apt-get install build-essential cmake git unzip pkg-config sudo apt-get install libjpeg-dev libpng-dev libtiff-dev sudo apt-get install libavcodec-dev libavformat-dev libswscale-dev libv4l-dev sudo apt-get install libxvidcore-dev libx264-dev sudo apt-get install libgtk-3-dev sudo apt-get install libatlas-base-dev gfortran ``` 3.安装CUDA 由于nvidia-jetpack需要CUDA支持,因此您需要先安装CUDA。您可以从nvidia官网下载相应的CUDA版本并按照官方指南进行安装。 4.安装cuDNN 同样,您需要安装cuDNN以支持nvidia-jetpack,您可以从nvidia官网下载相应的cuDNN版本并按照官方指南进行安装。 5.编译nvidia-jetpack 打开终端,进入nvidia-jetpack源码目录,执行以下命令: ``` mkdir build cd build cmake .. make -j4 ``` 其中-j4表示使用4个线程进行编译,可以根据您的CPU核心数进行调整。 6.安装nvidia-jetpack 编译成功后,执行以下命令进行安装: ``` sudo make install ``` 安装完成后,您可以执行以下命令进行验证: ``` jetson_release -v ``` 如果输出了正确的版本信息,则表示安装成功。 希望这些步骤能够帮助到您。请注意,由于nvidia-jetpack的安装过程较为复杂,如果您不熟悉Linux操作,请谨慎操作,以免造成不必要的损失。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值