Android Dagger-Hilt 工作原理浅析

260 阅读9分钟

背景

最近在项目中突然需要跨组件去访问彼此的服务。如果直接依赖对应的组件,整个项目的结构就复杂很多,而且依赖关系就显得很重。

可以使用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

image.png

然后在OnCreate()的阶段进行注入,如下图所示。

image.png

在生成 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));
    }
  }
}

作用域解释

image.png 树形结构的继承关系,子节点能够使用父节点中安装的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));
      }
    }
    
    // ...
}

image.png

image.png

通过上面的调用链上,我们可以知道,不管是Activity还是View,都大致遵循以下流程:

  1. EntryPoint(Activity、View等添加了@AndroidEntryPoint的类)会先获取 ComponentManager: generatedComponent

  2. 从ComponentManager中获取Component: createComponentEntryPoints.get(x,x)

    1. 调用对应的 xxxComponentBuilder,如下
      EntryPoints.get(componentManager, ViewComponentBuilderEntryPoint.class)
          .viewComponentBuilder()
          .view(view)
          .build();
      
  3. 调用Component对应Hilt生成的inject代码进行注入,如下

    1. 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包的功能,有空可以学习一下),字节码替换父类

  1. 使用注解处理器生成一系列注入相关逻辑的代码,主要有以下逻辑的代码:

    1. 原始类的中间父类: Hilt_XXXX
    2. 树形依赖关系的Component: XXXCImpl
    3. Module中创建各个实例的Factory
  2. 在字节码阶段替换掉原代码中的父类,替换成 Hilt_XXX