背景
最近在项目中突然需要跨组件去访问彼此的服务。如果直接依赖对应的组件,整个项目的结构就复杂很多,而且依赖关系就显得很重。
可以使用Java SPI,Java SPI能够实现这种跨组件的服务访问。但是Java SPI有一个明显的弊端:在新增接口实现类需要将实现类的全类名加入到对应的配置文件中 META-INF/services/[接口全类名]
网上搜了一圈“注解实现SPI“,大部分的实现思路:依旧借助Java SPI的能力,通过注解处理器自动往对应的 META-INF/services/[接口全类名] 配置文件中添加实现类的具体信息。(参考 AutoService) (记忆中前司使用的也是 注解 的方式,具体实现方式不记得了)
ServiceLoader 加载介绍
private static final String PREFIX = "META-INF/services/";
//private boolean hasNextService()
try {
String fullName = PREFIX + service.getName();
if (loader == null)
// 加载配置文件下的所有类名
configs = ClassLoader.getSystemResources(fullName);
else
configs = loader.getResources(fullName);
} catch (IOException x) {
fail(service, "Error locating configuration files", x);
}
//private S nextService()
c = Class.forName(cn, false, loader)
S p = service.cast(c.newInstance()); // 实例化
providers.put(cn, p); // 缓存
因为上述的方案都涉及到反射,所以觉得这个也不是一个比较好的路子,然后了解到Jetpack Hilt,也能实现这种跨组件访问的功能。
前言
因为 Hilt 是基于注解处理器去生成系列相关的代码,所以我们需要写一个Demo去分析生成的代码到底做了哪些工作。Hilt 整个源码可以包含两个部分:注解处理器和AGP的源码逻辑 和 生成的代码的逻辑
本文分析的是 生成的代码的逻辑。 注解处理器和AGP的源码比较复杂,有空再看看,希望本文能给你带来帮助。下面就是 Hilt 工作原理的解析,仅供参考。
(Hilt使用的方式可以参考这个文章Android Jetpack组件之Hilt使用_android hilt-CSDN博客)
字节码阶段对 Application 的父类进行修改
我们在使用Hilt第一个需要的就是在 Application 上添加 @HiltAndroidApp,作为整个注入环节的入口,后续的一系列逻辑都和这个Application有着密切的关联。
Hilt 会把加上了 @HiltAndroidApp 注解的Application类的父类修改成由Hilt注解处理器生成的中间Application类 Hilt_XXXApplication。
然后在OnCreate()的阶段进行注入,如下图所示。
在生成 Hilt_XXXApplication 的类文件里我们能够看到以下注释,即在字节码阶段对父类进行替换。这就是Hilt的关键实现思路:在编译字节码阶段,将生成的类替换成源代码的父类,然后在原类的初始化阶段执行生成类的注入逻辑
/**
* A generated base class to be extended by the @dagger.hilt.android.HiltAndroidApp annotated class. If using the Gradle plugin, this is swapped as the base class via bytecode transformation.
*/
@Generated("dagger.hilt.android.processor.internal.androidentrypoint.ApplicationGenerator")
public abstract class Hilt_XXXXXApplication extends Application implements GeneratedComponentManagerHolder {
....
}
剩下两个类其实主要就是管理各个Component的注入逻辑,DaggerOCApplication_HiltComponents_SingletonC, OCApplication_HiltComponents
这里重点写一下 ActivityC,下面在分析注入过程会涉及到。
@Generated("dagger.hilt.processor.internal.root.RootProcessor")
public final class OCApplication_HiltComponents {
//...
// Activity 相关的注入容器
@ActivityScoped
public abstract static class ActivityC implements MainActivity_GeneratedInjector,
ActivityComponent,
DefaultViewModelFactories.ActivityEntryPoint,
HiltWrapper_HiltViewModelFactory_ActivityCreatorEntryPoint,
FragmentComponentManager.FragmentComponentBuilderEntryPoint,
ViewComponentManager.ViewComponentBuilderEntryPoint,
GeneratedComponent {
@Subcomponent.Builder
abstract interface Builder extends ActivityComponentBuilder {
}
}
//....
}
public final class DaggerOCApplication_HiltComponents_SingletonC {
private static final class ActivityRetainedCBuilder implements OCApplication_HiltComponents.ActivityRetainedC.Builder {
private final SingletonCImpl singletonCImpl;
private SavedStateHandleHolder savedStateHandleHolder;
@Override
public OCApplication_HiltComponents.ActivityRetainedC build() {
Preconditions.checkBuilderRequirement(savedStateHandleHolder, SavedStateHandleHolder.class);
return new ActivityRetainedCImpl(singletonCImpl, savedStateHandleHolder);
}
}
private static final class ActivityRetainedCImpl extends OCApplication_HiltComponents.ActivityRetainedC {
private final SingletonCImpl singletonCImpl;
private final ActivityRetainedCImpl activityRetainedCImpl = this;
private dagger.internal.Provider<ActivityRetainedLifecycle> provideActivityRetainedLifecycleProvider;
@Override
public ActivityComponentBuilder activityComponentBuilder() {
return new ActivityCBuilder(singletonCImpl, activityRetainedCImpl);
}
}
private static final class SingletonCImpl extends OCApplication_HiltComponents.SingletonC {
private final SingletonCImpl singletonCImpl = this;
private SingletonCImpl() {}
@Override
public void injectOCApplication(OCApplication arg0) {}
@Override
public Set<Boolean> getDisableFragmentGetContextFix() {
return Collections.<Boolean>emptySet();
}
// Activity
@Override
public ActivityRetainedComponentBuilder retainedComponentBuilder() {
return new ActivityRetainedCBuilder (singletonCImpl) ;
}
@Override
public ServiceComponentBuilder serviceComponentBuilder() {
return new ServiceCBuilder(singletonCImpl);
}
}
//...
private static final class ActivityCImpl extends OCApplication_HiltComponents.ActivityC {
private final SingletonCImpl singletonCImpl;
private final ActivityRetainedCImpl activityRetainedCImpl;
private final ActivityCImpl activityCImpl = this;
private ActivityCImpl(SingletonCImpl singletonCImpl,
ActivityRetainedCImpl activityRetainedCImpl, Activity activityParam) {
this.singletonCImpl = singletonCImpl;
this.activityRetainedCImpl = activityRetainedCImpl;
}
@Override
public void injectMainActivity(MainActivity arg0) {
injectMainActivity2(arg0);
}
//...
// 这个方法是实际调用Inject赋值MainActivity中变量的方法
// 有多个@Inject时,这个方法中就会依次调用 MainActivity_MembersInjector.injectXXX 进行赋值
@CanIgnoreReturnValue
private MainActivity injectMainActivity2(MainActivity instance) {
// 这里用的是 singletonCImpl中的TestModule, testModule是个唯一实例
MainActivity_MembersInjector.injectService(instance, TestModule_ProvideTestServiceFactory.provideTestService(singletonCImpl.testModule));
// MainActivity_MembersInjector.injectService2(instance, TestModule_ProvideTestServiceFactory.provideTestService(singletonCImpl.testModule));
return instance;
}
}
//...
}
调用链:SingletonCImpl -> ActivityRetainedCBuilder -> ActivityRetainedCImpl -> ActivityCBuilder -> ActivityCImpl -> inject()
(后面会有比较详细的过程)
注入实例的过程(Activity为例)
原始的代码
当我们要在Activity中注入依赖时,我们需要在代码中添加 @AndroidEntryPoint @Inject注解,如下面的原始代码:
// 依赖项的接口,会有具体的实现
interface ITestService {
fun hello(): String
}
// 依赖实例注入的入口点,可以简单理解为这个类可以并且需要注入,并不是所有的类都能用这个注解
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
// 表示将依赖项注入。
// 注意: 需要是public的,因为hilt生成的代码会直接通过xxx.service = [value] 进行赋值, 没有使用反射
@Inject
lateinit var service: ITestService
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
XxxxTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
Greeting(
name = "Android",
modifier = Modifier.padding(innerPadding)
)
}
}
}
}
}
Hilt生成的代码
在Hilt进行编译后,会生成下面的代码,从注释可以知道,也是通过字节码阶段替换掉原来的父类。
关键类:ActivityComponentManager、``
/**
* A generated base class to be extended by the @dagger.hilt.android.AndroidEntryPoint annotated class. If using the Gradle plugin, this is swapped as the base class via bytecode transformation.
*/
@Generated("dagger.hilt.android.processor.internal.androidentrypoint.ActivityGenerator")
public abstract class Hilt_MainActivity extends ComponentActivity implements GeneratedComponentManagerHolder {
private SavedStateHandleHolder savedStateHandleHolder;
private volatile ActivityComponentManager componentManager;
private final Object componentManagerLock = new Object();
private boolean injected = false;
// 抛个小问题:为什么只生成了这两个构造方法?
Hilt_MainActivity() {
super();
_initHiltInternal();
}
Hilt_MainActivity(int contentLayoutId) {
super(contentLayoutId);
_initHiltInternal();
}
private void _initHiltInternal() {
addOnContextAvailableListener(new OnContextAvailableListener() {
@Override
public void onContextAvailable(Context context) {
// 1. 初始化过程中,当Context可用,就会执行中注入逻辑
inject();
}
});
}
@Override
public final Object generatedComponent() {
// 2. 查找对应的组件, 收集找到的是前面生成的 ActivityCImpl
return this.componentManager().generatedComponent();
}
protected ActivityComponentManager createComponentManager() {
return new ActivityComponentManager(this);
}
@Override
public final ActivityComponentManager componentManager() {
if (componentManager == null) {
synchronized (componentManagerLock) {
if (componentManager == null) {
componentManager = createComponentManager();
}
}
}
return componentManager;
}
protected void inject() {
if (!injected) {
injected = true;
// 3. 传入当前Activity,给变量进行赋值
((MainActivity_GeneratedInjector) this.generatedComponent()).injectMainActivity(UnsafeCasts.<MainActivity>unsafeCast(this));
}
}
}
作用域解释
树形结构的继承关系,子节点能够使用父节点中安装的Module,从而使用其中提供(@Provides)的实例,实现注入。会自底向上去选择最近的可以提供实例的节点。
但是同一节点,同时不能提供多个相同的@Provides,包括从父节点那边“继承“下来的。其实声明没有问题,就是不能在当前节点注入@Inject,不然会发生冲突。
-
仅声明编译通过
因为没有生成具体的注入代码,没有冲突检查。
interface ITestService { fun hello(): String } object TestServiceImpl : ITestService { override fun hello(): String { return "Hello" } } @Module @InstallIn(ActivityComponent::class) class ActivityTestModule { @Provides fun provideActivityTestService(): ITestService { return TestServiceImpl } } @Module @InstallIn(SingletonComponent::class) class SingletonTestModule { @Provides fun provideSingletonTestService(): ITestService { return TestServiceImpl } } @AndroidEntryPoint class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() setContent { XxxxTheme { Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> Greeting( name = "Android", modifier = Modifier.padding(innerPadding) ) } } } } } -
添加
@Inject注入代码见 原始的代码 小节, 编译后出现以下错误:
> Task :app:hiltJavaCompileDebug FAILED /home/xxxxxxxxx/app/build/generated/hilt/component_sources/debug/com/jwslh/xxxxx/OCApplication_HiltComponents.java:138: error: [Dagger/DuplicateBindings] com.jwslh.xxx.core.activity.ITestService is bound multiple times: public abstract static class SingletonC implements OCApplication_GeneratedInjector, ^ @Provides com.jwslh.xxxx.core.activity.ITestService com.jwslh.xxxx.core.activity.ActivityTestModule.provideActivityTestService() @Provides com.jwslh.xxxx.core.activity.ITestService com.jwslh.xxxx.core.activity.SingletonTestModule.provideSingletonTestService() com.jwslh.xxxx.core.activity.ITestService is injected at [com.jwslh.xxxx.OCApplication_HiltComponents.ActivityC] com.jwslh.xxxx.core.activity.MainActivity.service com.jwslh.xxxx.core.activity.MainActivity is injected at [com.jwslh.xxxx.OCApplication_HiltComponents.ActivityC] com.jwslh.xxxx.core.activity.MainActivity_GeneratedInjector.injectMainActivity(com.jwslh.xxxx.core.activity.MainActivity) [com.jwslh.xxxx.OCApplication_HiltComponents.SingletonC → com.jwslh.xxxx.OCApplication_HiltComponents.ActivityRetainedC → com.jwslh.xxxx.OCApplication_HiltComponents.ActivityC] 1 error原因就是在注入的时候有多个可以选择的Module(ActivityComponent和SingletonComponent)来提供这个
ITestService,这是不允许的。(那如果希望存在多个?怎么优化?是不是可以在@Inject中加入希望选择的Scope或是优先级列表,在生成代码注入时再进行判断和选择?)
能够使用父节点中的Module是怎么实现的?
其实很简单,就把父节点传入子节点中,子节点中持有这个父节点的对象。如下代码。
// 原代码
@AndroidEntryPoint
class TestTextView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : AppCompatTextView(context, attrs, defStyleAttr) {
@Inject
lateinit var service: ITestService
}
// Hilt生成的代码
private static final class ViewCImpl extends OCApplication_HiltComponents.ViewC {
// 三个父节点
private final SingletonCImpl singletonCImpl;
private final ActivityRetainedCImpl activityRetainedCImpl;
private final ActivityCImpl activityCImpl;
private final ViewCImpl viewCImpl = this;
private ViewCImpl(SingletonCImpl singletonCImpl, ActivityRetainedCImpl activityRetainedCImpl,
ActivityCImpl activityCImpl, View viewParam) {
this.singletonCImpl = singletonCImpl;
this.activityRetainedCImpl = activityRetainedCImpl;
this.activityCImpl = activityCImpl;
}
@Override
public void injectTestTextView(TestTextView arg0) {
injectTestTextView2(arg0);
}
@CanIgnoreReturnValue
private TestTextView injectTestTextView2(TestTextView instance) {
// 会自底向上去选择最近的可以提供实例的节点
TestTextView_MembersInjector.injectService(instance, ActivityTestModule_ProvideActivityTestServiceFactory.provideActivityTestService(activityCImpl.activityTestModule));
return instance;
}
}
注入流程
下面这段关键的代码是注入过程中逐层获取Component的代码:
class EntryPoints {
// ...
public static <T> T get(Object component, Class<T> entryPoint) {
if (component instanceof GeneratedComponent) {
if (component instanceof TestSingletonComponent) {
// @EarlyEntryPoint only has an effect in test environment, so we shouldn't fail in
// non-test cases. In addition, some of the validation requires the use of reflection, which
// we don't want to do in non-test cases anyway.
Preconditions.checkState(
!hasAnnotationReflection(entryPoint, EARLY_ENTRY_POINT),
"Interface, %s, annotated with @EarlyEntryPoint should be called with "
+ "EarlyEntryPoints.get() rather than EntryPoints.get()",
entryPoint.getCanonicalName());
}
// Unsafe cast. There is no way for this method to know that the correct component was used.
return entryPoint.cast(component);
} else if (component instanceof GeneratedComponentManager) {
return get(((GeneratedComponentManager<?>) component).generatedComponent(), entryPoint);
} else {
throw new IllegalStateException(
String.format(
"Given component holder %s does not implement %s or %s",
component.getClass(), GeneratedComponent.class, GeneratedComponentManager.class));
}
}
// ...
}
通过上面的调用链上,我们可以知道,不管是Activity还是View,都大致遵循以下流程:
-
EntryPoint(Activity、View等添加了@AndroidEntryPoint的类)会先获取 ComponentManager:
generatedComponent -
从ComponentManager中获取Component:
createComponent,EntryPoints.get(x,x)- 调用对应的
xxxComponentBuilder,如下EntryPoints.get(componentManager, ViewComponentBuilderEntryPoint.class) .viewComponentBuilder() .view(view) .build();
- 调用对应的
-
调用Component对应Hilt生成的inject代码进行注入,如下
-
protected void inject() { if (!injected) { injected = true; ((TestTextView_GeneratedInjector) this.generatedComponent()).injectTestTextView(UnsafeCasts.<TestTextView>unsafeCast(this)); } }
-
单例的实现逻辑
private static final class SingletonCImpl extends OCApplication_HiltComponents.SingletonC {
private final TestModule testModule;
private final SingletonCImpl singletonCImpl = this;
private dagger.internal.Provider<ISingletonTestService> provideSingletonTestServiceProvider;
private SingletonCImpl(TestModule testModuleParam) {
this.testModule = testModuleParam;
initialize(testModuleParam);
}
@SuppressWarnings("unchecked")
private void initialize(final TestModule testModuleParam) {
// =========================> a
this.provideSingletonTestServiceProvider = DoubleCheck.provider(new SwitchingProvider<ISingletonTestService>(singletonCImpl, 0));
}
//...
}
public final class DoubleCheck<T> implements Provider<T>, Lazy<T> {
private static final Object UNINITIALIZED = new Object();
private volatile Provider<T> provider;
private volatile Object instance = UNINITIALIZED;
private DoubleCheck(Provider<T> provider) {
assert provider != null;
this.provider = provider;
}
// ==================> 同步代码块判断对象是否已经创建
@SuppressWarnings("unchecked") // cast only happens when result comes from the provider
@Override
public T get() {
Object result = instance;
if (result == UNINITIALIZED) {
synchronized (this) {
result = instance;
if (result == UNINITIALIZED) {
result = provider.get();
instance = reentrantCheck(instance, result);
/* Null out the reference to the provider. We are never going to need it again, so we
* can make it eligible for GC. */
provider = null;
}
}
}
return (T) result;
}
}
a.当使用@Singleton注解修饰一个Provider,就会生成这段DoubleCheck的代码,用于存储单例对象
总结
注解处理器扫描注解生成代码(借用了androidx.Room.compiler.processing包的功能,有空可以学习一下),字节码替换父类。
-
使用注解处理器生成一系列注入相关逻辑的代码,主要有以下逻辑的代码:
- 原始类的中间父类: Hilt_XXXX
- 树形依赖关系的Component: XXXCImpl
- Module中创建各个实例的Factory
-
在字节码阶段替换掉原代码中的父类,替换成 Hilt_XXX