赞
踩
和人类需要群居一样,程序界的进程、线程也需要通信往来。它们的交流则依赖模块之间、文件之间产生的关系。如何快速地搞清和构建这种关系,同时还能减轻彼此的依赖,需要开发者们认真思考。
我们将这种需求称之为依赖注入(DI,Dependency Injection),这个编程技术由来已久,在讲述之前想来简单回顾下依赖和关联的基本概念。
像下图示意的那样,模块或类之间的关系大体可以分为依赖(Dependency)和关联(Association)两种。依赖一般表现为局部参数,关联则表现为属性的持有。
按照被关联对象的生命周期的不同,又可以将关联分为聚合(Aggregation)和组合(Composition)。耦合度依次增强。
依赖注入编程技术最终要构建的并非特指上面的依赖关系,也包含了关联关系。只是构建的入口多表现为参数注入。
不依赖框架的情况下我们也可以手动注入这些关系。
但面对依赖纵深复杂的大型项目,手动注入这些依赖非常繁琐和易错,互相的依赖关系难以避免得混乱而丑陋。久而久之,耦合度越来越强,难以扩展。这时候自动注入这些依赖关系显得尤为必要。
自动注入可以选择通过反射在运行阶段构建依赖关系的框架,比如Guice
。也可以选择在编译阶段即可构建依赖的更优方案。
比如今天的主角Dagger2
,像它的名字一样,是实现DI技术的一把利器。
Dagger由开源了okHttp的Square公司开发,广为人知。但其部分功能仍然依靠反射来实现,美中不足。
于是Google接过了接力棒,在其基础之上进行了改善,推出了Dagger2。它通过APT在编译阶段解析注解生成代码实现依赖注入。
我们通过Dagger2、ViewModel和Retrofit查询电影接口,简单演示下如何使用Dagger2。
DEMO: github.com/ellisonchan…
apply plugin: 'kotlin-kapt'
dependencies {
implementation 'com.google.dagger:dagger:2.x'
kapt 'com.google.dagger:dagger-compiler:2.x'
}
@Singleton
@Component(modules = [NetworkModule::class])
interface ApplicationGraph {
fun inject(activity: DemoActivity)
}
class MyApplication: Application() {
val appComponent = DaggerApplicationComponent.create()
}
class DemoActivity: Activity() {
@Inject
lateinit var movieViewModel: MovieViewModel
override fun onCreate(savedInstanceState: Bundle?) {
(applicationContext as MyApplication).appGraph.inject(this)
super.onCreate(savedInstanceState)
val binding = ActivityDaggerBinding.inflate(layoutInflater)
...
}
}
注意:Activity由系统实例化只能通过字段注入。
class MovieViewModel @Inject constructor(
private val movieRepository: MovieRepository
)
@Singleton
class MovieRepository @Inject constructor(
private val localData: MovieLocalData,
private val remoteData: MovieRemoteData
)
class MovieLocalData @Inject constructor()
@Singleton
class MovieRepository @Inject constructor(
private val localData: MovieLocalData,
private val remoteData: MovieRemoteData
)
@Module
class NetworkModule {
...
@Singleton
@Provides
fun provideLoginService(okHttpClient: OkHttpClient, gsonConverterFactory: GsonConverterFactory): MovieService {
return Retrofit.Builder()
.baseUrl("http://omdbapi.com/")
.addConverterFactory(gsonConverterFactory)
.client(okHttpClient)
.build()
.create(MovieService::class.java)
}
}
Dagger2的功能十分强大,上述实战仍有不少未提及的高阶用法,感兴趣者可进一步尝试。
…
Android Studio针对Dagger2的导航进行了支持,方便开发者快速回溯依赖关系。
对于小型项目而言,引入DI框架显得大材小用、大动干戈。而且对于后期接手人员,如果对于DI框架不熟悉的话,维护将变得尤为困难。似乎只有大型项目才能让它自由地施展拳脚。
前些年我在调查某个导航栏Bug的时候查阅过SystemUI的代码,当时意外地发现大量的模块包括StatusBar、Recents、Keyguard等都是DI方式引入的。虽然对Dagger略有耳闻,但仍看得云里雾里,不得其解。
SystemUI作为Android系统里最核心最复杂的App,称之为大型项目毫不过分。现在就来看看Dagger2如何助力这个大型App管理大量的系统组件。
※ 源码版本:Android 11
SystemUI中主要的依赖实例都管理在Denpency类中。
public class Dependency { ... @Inject @Background Lazy<Executor> mBackgroundExecutor; @Inject Lazy<ClockManager> mClockManager; @Inject Lazy<ActivityManagerWrapper> mActivityManagerWrapper; @Inject Lazy<DevicePolicyManagerWrapper> mDevicePolicyManagerWrapper; @Inject Lazy<PackageManagerWrapper> mPackageManagerWrapper; @Inject Lazy<SensorPrivacyController> mSensorPrivacyController; @Inject Lazy<DockManager> mDockManager; @Inject Lazy<INotificationManager> mINotificationManager; @Inject Lazy<SysUiState> mSysUiStateFlagsContainer; @Inject Lazy<AlarmManager> mAlarmManager; @Inject Lazy<KeyguardSecurityModel> mKeyguardSecurityModel; @Inject Lazy<DozeParameters> mDozeParameters; @Inject Lazy<IWallpaperManager> mWallpaperManager; @Inject Lazy<CommandQueue> mCommandQueue; @Inject Lazy<Recents> mRecents; @Inject Lazy<StatusBar> mStatusBar; @Inject Lazy<DisplayController> mDisplayController; @Inject Lazy<SystemWindows> mSystemWindows; }
后面以StatusBar实例的注入为例阐述下SystemUI里Dagger2的注入流程。
随着SystemServer发出启动SystemUIService的请求,SystemUI的Application将首先被实例化。在实例化之前,指定的AppComponentFactory实现类将会收到回调。
// AndroidManifest.xml
<application
android:name=".SystemUIApplication"
...
tools:replace="android:appComponentFactory"
android:appComponentFactory=".SystemUIAppComponentFactory">
</Application>
调用super得到Application实例之后向其注册Context准备完毕的回调,该回调会执行SystemUIFactory和DI组件的初始化。
public class SystemUIAppComponentFactory extends AppComponentFactory { @Inject public ContextComponentHelper mComponentHelper; ... @Override public Application instantiateApplicationCompat( @NonNull ClassLoader cl, @NonNull String className) throws InstantiationException, IllegalAccessException, ClassNotFoundException { Application app = super.instantiateApplicationCompat(cl, className); if (app instanceof ContextInitializer) { // 注册Context成功取得的回调 ((ContextInitializer) app).setContextAvailableCallback( context -> { SystemUIFactory.createFromConfig(context); SystemUIFactory.getInstance().getRootComponent().inject( SystemUIAppComponentFactory.this); } ); } return app; } ... }
Application的onCreate()回调的时候意味着Context已准备完毕,接着执行上述回调。
public class SystemUIApplication extends Application implements SystemUIAppComponentFactory.ContextInitializer { ... @Override public void setContextAvailableCallback( SystemUIAppComponentFactory.ContextAvailableCallback callback) { mContextAvailableCallback = callback; } @Override public void onCreate() { ... log.traceBegin("DependencyInjection"); mContextAvailableCallback.onContextAvailable(this);★ mRootComponent = SystemUIFactory.getInstance().getRootComponent(); mComponentHelper = mRootComponent.getContextComponentHelper(); ... } }
回调将先创建SystemUIFactory实例,并初始化SystemUI App的Dagger组件。之后初始化DI子组件并向Dependency实例注入依赖。
public class SystemUIFactory { public static void createFromConfig(Context context) { ... try { Class<?> cls = null; cls = context.getClassLoader().loadClass(clsName); // 1\. 创建SystemUIFactory实例 mFactory = (SystemUIFactory) cls.newInstance(); mFactory.init(context); } } private void init(Context context) { // 2\. 取得SystemUI的Dagger组件实例 mRootComponent = buildSystemUIRootComponent(context); // 3\. 创建Dependency实例并绑定到DependencyInjector子组件中 Dependency dependency = new Dependency(); mRootComponent.createDependency().createSystemUI(dependency); // 4\. 初始化Dependency dependency.start(); } // 初始化Dagger组件 protected SystemUIRootComponent buildSystemUIRootComponent(Context context) { return DaggerSystemUIRootComponent.builder() .dependencyProvider(new DependencyProvider()) .contextHolder(new ContextHolder(context)) .build(); } ... }
Dependency类里掌管着各式各样的依赖,被依赖的各实例通过Map管理。但并不是在初始化的时候就缓存它们。而先将各实例对应的懒加载回调缓存进去。其后在各实例确实需要使用的时候通过注入的懒加载获取和缓存。
public class Dependency { // 使用class作为key将对应实例缓存的Map private final ArrayMap<Object, Object> mDependencies = new ArrayMap<>(); // 缓存实例的懒加载回调的Map private final ArrayMap<Object, LazyDependencyCreator> mProviders = new ArrayMap<>(); protected void start() { mProviders.put(ActivityStarter.class, mActivityStarter::get); mProviders.put(Recents.class, mRecents::get); mProviders.put(StatusBar.class, mStatusBar::get); mProviders.put(NavigationBarController.class, mNavigationBarController::get); ... } // 根据class查询缓存,尚未缓存的话通过懒加载回调获取注入的实例并缓存 private synchronized <T> T getDependencyInner(Object key) { T obj = (T) mDependencies.get(key); if (obj == null) { obj = createDependency(key); mDependencies.put(key, obj); if (autoRegisterModulesForDump() && obj instanceof Dumpable) { mDumpManager.registerDumpable(obj.getClass().getName(), (Dumpable) obj); } } return obj; } protected <T> T createDependency(Object cls) { Preconditions.checkArgument(cls instanceof DependencyKey<?> || cls instanceof Class<?>); LazyDependencyCreator<T> provider = mProviders.get(cls); return provider.createDependency(); } private interface LazyDependencyCreator<T> { T createDependency(); } }
Application创建好之后SystemUI的主Service将启动起来,并逐个启动其他Service。
public class SystemUIService extends Service {
...
@Override
public void onCreate() {
super.onCreate();
// Start all of SystemUI
((SystemUIApplication) getApplication()).startServicesIfNeeded();
...
}
}
通过ContextComponentHelper解析预设的service类名得到实例并启动。
public class SystemUIApplication { public void startServicesIfNeeded() { String[] names = SystemUIFactory.getInstance().getSystemUIServiceComponents(getResources()); startServicesIfNeeded(/* metricsPrefix= */ "StartServices", names); } private void startServicesIfNeeded(String metricsPrefix, String[] services) { ... final int N = services.length; for (int i = 0; i < N; i++) { String clsName = services[i]; try { // 从ContextComponentHelper里获取对应的实例 SystemUI obj = mComponentHelper.resolveSystemUI(clsName); if (obj == null) { Constructor constructor = Class.forName(clsName).getConstructor(Context.class); obj = (SystemUI) constructor.newInstance(this); } mServices[i] = obj; } mServices[i].start(); ... } mRootComponent.getInitController().executePostInitTasks(); } }
配置的Service列表。
// config.xml
<string-array name="config_systemUIServiceComponents" translatable="false">
...
<item>com.android.systemui.recents.Recents</item>
<item>com.android.systemui.volume.VolumeUI</item>
<item>com.android.systemui.stackdivider.Divider</item>
<item>com.android.systemui.statusbar.phone.StatusBar</item> ★
...
</string-array>
ContextComponentHelper单例已声明由Dagger组件提供。
@Singleton
@Component(modules = {...})
public interface SystemUIRootComponent {
...
/**
* Creates a ContextComponentHelper.
*/
@Singleton
ContextComponentHelper getContextComponentHelper();
}
模块SystemUIModule负责注入ContextComponentHelper实例,实际注入的是ContextComponentResolver实例。
@Module(...)
public abstract class SystemUIModule {
...
/** */
@Binds
public abstract ContextComponentHelper bindComponentHelper(
ContextComponentResolver componentHelper);
}
ContextComponentResolver用于解析Activity和Service等实例,通过class实例从Map查询得到的Provider里取得对应的Service实例。 它的构造函数注释了@Inject。它依赖几个Map参数,比如StatusBar的Provider是注入到其中的SystemUI Map里。
@Singleton public class ContextComponentResolver implements ContextComponentHelper { @Inject ContextComponentResolver(Map<Class<?>, Provider<Activity>> activityCreators, Map<Class<?>, Provider<Service>> serviceCreators, Map<Class<?>, Provider<SystemUI>> systemUICreators, Map<Class<?>, Provider<RecentsImplementation>> recentsCreators, Map<Class<?>, Provider<BroadcastReceiver>> broadcastReceiverCreators) { mSystemUICreators = systemUICreators; ... } ... @Override public SystemUI resolveSystemUI(String className) { return resolve(className, mSystemUICreators); } // 依据名称得到的class实例去查询Provider实例,进而取得对应SystemUI的实例 private <T> T resolve(String className, Map<Class<?>, Provider<T>> creators) { try { Class<?> clazz = Class.forName(className); Provider<T> provider = creators.get(clazz); return provider == null ? null : provider.get(); } catch (ClassNotFoundException e) { return null; } } }
在SystemUIBinder的Module里声明了以ClassKey为StatusBar.class,value由StatusBarPhoneModule模块注入到Map里。而Provider#get()的实例将拿到provideStatusBar注入的实例。(StatusBar构造器的参数竟有76个之多,简直恐怖。。。)
@Module(includes = {RecentsModule.class, StatusBarModule.class...}) public abstract class SystemUIBinder { /** Inject into StatusBar. */ @Binds @IntoMap @ClassKey(StatusBar.class) public abstract SystemUI bindsStatusBar(StatusBar sysui); ... } @Module(includes = {StatusBarPhoneModule.class...}) public interface StatusBarModule { } @Module(includes = {StatusBarPhoneDependenciesModule.class}) public interface StatusBarPhoneModule { @Provides @Singleton static StatusBar provideStatusBar( Context context, NotificationsController notificationsController, LightBarController lightBarController, AutoHideController autoHideController, KeyguardUpdateMonitor keyguardUpdateMonitor, StatusBarIconController statusBarIconController, ...) { return new StatusBar(...); } }
回顾下依赖注入技术的必要性。
是否一定非要选择Dagger2这种自动方案呢?我觉得依据对项目的了解程度决定。
因为无论是采用手动还是自动的依赖注入方案,都需要我们理清各模块各类之前的关系,正确地定位每个类的角色,把握每个实例的作用域。
况且必须要认识到Dagger2这种框架的局限性。
技术人的脚步永远不会停滞不前,优化和改善是他们永恒的追求。
Google在Dagger2
的基础上再次进行了改良,一来简化了DI的使用,二来强化了Android上的使用。这个框架也收录在Jetpack系列中,命名为Hilt
。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。