SystemUI 与 Dagger2 框架

Dagger2 注解基础

结构

Dagger2 要实现一个完整的依赖注入,必不可少的元素有三种,Module,Component,Container。

Container 就是可以被注入的容器,Container 拥有需要被初始化的元素。需要被初始化的元素必须标上 @Inject,只有被标上 @Inject 的元素才会被自动初始化。被注解的构造方法会自动编译生成一个Factory工厂类提供该类对象。

Module 可以说就是依赖的原材料的制造工厂,所有需要被注入的元素的实现都是从 Module 生产并且封装起来,然后交给注入器 Component。

Component 相当于中间的运输员,将 Module 中产生的依赖对象自动注入到 Container 中。

一些常用的注解

@Inject
  1. 用 @Inject 注解标注目标类中依赖类的实例对象
  2. 用 @Inject 注解标注依赖类的构造函数
  3. 若其他类还依赖于另外的类,则重复进行上面2个步骤
@Component
  1. 所有的 Component 都必须以接口形式定义,使用 @Component 注解的接口一般直接命名为 XXXComponent
  2. Dagger2 框架将自动生成该 Component 的实现类,对应的类名是 DaggerXXXComponent
  3. 添加注入方法,一般使用 inject 做为方法名,方法参数为对应的 Container,调用该方法进行注入,有参数的方法返回值类型是void
  4. 注入方法也可以没有输入参数,但是必须有返回值,返回的实例会先从事先定义的 Module 中查找,如果找不到,则会使用该类中带 @Inject 的构造方法来生成,同时也会递归注入构造方法的参数以及带 @Inject 的成员变量
@Module
  1. Module 是一个简单的工厂类,添加注解 @Module 注明本类属于 Module
  2. Module 里面的方法都是创建相应类实例的方法
  3. 在 Module 中添加注解 @Provides 注明该方法是用来提供依赖对象的特殊方法

注:一个 Component 可以包含多个 Module ,这样 Component 获取依赖时候会自动从多个 Module 中查找获取,Module 间不能有重复方法。添加多个 Module 有两种方法,一种是在 Component 的注解 @Component(modules={×××.class,×××.class}) 添加多个 modules,另外一种是在可以被使用的 Module 标签中的添加其他 modules: @Module(includes={×××.class,×××.class})

@Provides
  1. 通过 @Provides 标注该 Module 可以向外界提供的类的实例对象的方法
  2. Module 中 @Provides 方法可以带输入参数,其参数由 Module 集合中的其他 @Provides 方法的返回值提供
  3. 如果2中找不到 @Provides 方法提供对应参数的对象,则其会自动查找参数类中带 @Inject 的无参构造生成对象
@Binds
  1. 使用 @Inject 初始化对象时如果需要初始化的是接口的实例,这时候就需要用到 @Binds
  2. 方法中必须有且只有一个参数,必须是接口的实现类
  3. 实现类必须提供 @Inject 的构造或 Module 中以 @Provides 形式提供
  4. @Binds 相当于是在接口或者抽象类中代替 @Provides
@Named
  1. @Named 是基于 String 的限定符
  2. 有两个相同的依赖(都继承某一个父类或者都实现某一个接口)可以提供,这个时候就需要用 @Named 来区分,使用方法为 @Named("AAA"),@Named("BBB")
  3. 拥有相同 @Named 的 @Inject 成员变量与 @Provides 方法才能被对应起来使用
@Qualifier
  1. @Qualifier 用来注解接口作为自定义区分标志
  2. 使用此注解的接口 xxx.java 可以当做注解标签进行使用,@xxx
@Singleton与@Scope
  1. 顾名思义使用 @Singleton 是用来标注单例对象的
  2. 在 Component 里的方法添加 @Singleton,同时在 Module 对应的 @Provides 方法上也添加 @Singleton,这样一来每次注入的都会是同一个对象
  3. @Scope 是注解的注解, Scope机制可以保证在 Scope 标记的 Component 作用域内 ,类会保持单例,@Singleton 是 @Scope 的一个默认实现
@Subcomponent
  1. @Subcomponent 用于拓展原有 Component,它与 @Component(dependencies=XXComponent.classs) 类似
  2. @Subcomponent 注解不会在编译后生成Dagger前缀的容器类,而是体现在父 Component 的私有内部类
  3. Subcomponent 既拥有父 Component 拥有的 Scope,也拥有自己的 Scope
  4. 在父 Component 中显式添加添加返回 SubComponent 的方法后,继承关系才成立,SubComponent 能自动在父 Component 中查找缺失的依赖

注入方式

Provider与Lazy

Provider和Lazy都是用于包装Container中需要被注入的类型,Lazy用于延迟加载,Provide用于强制重新加载。
Provider保证每次重新加载,但是并不意味着每次返回的对象都是不同的,只有Module的Provide方法每次都创建新实例时,Provider每次get()的对象才不相同。
Lazy != Singleton,如果有两个不同的Lazy对象,那么他们get()的对象也是不同的。

示例:

    @Module
    final class CounterModule {
        int next = 100;

        @Provides
        Integer provideInteger() {
            System.out.println("computing...");
            return next++;
        }
    }

    // Provider 注入
    final class ProviderCounter {
        @Inject Provider<Integer> provider;
        void print() {
            System.out.println("printing...");
            System.out.println(provider.get());
            System.out.println(provider.get());
            System.out.println(provider.get());
    }
    /**    
     *    打印结果:
     *     printing...
     *     computing...
     *     100
     *     computing...
     *     101
     *     computing...
     *     102
     */

    // Lazy 注入
    final class LazyCounter {
        @Inject Lazy<Integer> Lazy;
        void print() {
            System.out.println("printing...");
            System.out.println(Lazy.get());
            System.out.println(Lazy.get());
            System.out.println(Lazy.get());
    }
    /**    
     *    打印结果:
     *     printing...
     *     computing...
     *     100
     *     100
     *     100
     */

Dagger2 导航

Android Studio 针对 Dagger2 的导航进行了支持,方便开发者快速回溯依赖关系。


Dagger2 导航
  • 点击向上的箭头可以查看该实例注入的提供方
  • 点击向下的树形图会转到或展开该实例被用作依赖项的位置或列表

Dagger2 在 SystemUI 里的应用

SystemUI里面大量的模块都使用了此注入方式,比如StatusBar,QS,Keyguard等。
下面以 Android 11 源码的SystemUI来详细说明。

定义清单列表

SystemUI的application标签定义了一个appComponentFactory属性

<application
    android:name=".SystemUIApplication"
    ...
    tools:replace="android:appComponentFactory"
    android:appComponentFactory=".SystemUIAppComponentFactory">

    ...

    <service ...
    />

    <activity ...
    />

    <reveiver ...
    />

    ...

</application>

这个属性是用来控制manifest清单文件里的组件的初始化,在manifest清单文件里的组件构建对象时会调用下面这些方法。


AppComponentFactory

启动Application

随着SystemServer发出启动SystemUIService的请求,SystemUI的Application将首先被实例化。在实例化之前,指定的AppComponentFactory实现类将会收到回调。

  1. 调用super得到Application实例之后向其注册Context准备完毕的回调,该回调会执行SystemUIFactory和各组件的初始化:
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);
                        // 注入SystemUIAppComponentFactory对象
                        SystemUIFactory.getInstance().getRootComponent().inject(
                                SystemUIAppComponentFactory.this);
                    }
            );
        }

        return app;
    }
    ...
}
  1. 在SystemUIApplication onCreate时执行上述回调, 构建SystemUIFactory,并对它进行初始化:
public class SystemUIApplication extends Application implements
        SystemUIAppComponentFactory.ContextInitializer {
    ...
    @Override
    public void setContextAvailableCallback(
            SystemUIAppComponentFactory.ContextAvailableCallback callback) {
        mContextAvailableCallback = callback;
    }

    @Override
    public void onCreate() {
        ...
        // 执行回调
        mContextAvailableCallback.onContextAvailable(this);
        // 获取 SystemUIRootComponent,很多组件都是通过它注入的
        mRootComponent = SystemUIFactory.getInstance().getRootComponent();
        // 通过 SystemUIRootComponent 定义的注入方法获得 ContextComponentHelper 对象
        mComponentHelper = mRootComponent.getContextComponentHelper();
        ...
    }
}
  1. 创建SystemUIFactory实例,并初始化SystemUI 的Dagger组件,并向Dependency实例注入依赖:
public class SystemUIFactory {
    ...
    public static void createFromConfig(Context context) {
        ...
        try {
            Class<?> cls = null;
            cls = context.getClassLoader().loadClass(clsName);
            // 创建SystemUIFactory实例
            mFactory = (SystemUIFactory) cls.newInstance();
            // 初始化SystemUIFactory
            mFactory.init(context);
        }
    }

    private void init(Context context) {
        // 取得SystemUI的Dagger组件实例
        mRootComponent = buildSystemUIRootComponent(context);
        // 创建Dependency实例
        Dependency dependency = new Dependency();
        // 将Dependency绑定到子组件DependencyInjector中,这里会通过Component将各类依赖注入Dependency
        mRootComponent.createDependency().createSystemUI(dependency);
        // 初始化Dependency
        dependency.start();
    }

    // 初始化Dagger组件
    protected SystemUIRootComponent buildSystemUIRootComponent(Context context) {
        return DaggerSystemUIRootComponent.builder() 
                .dependencyProvider(new DependencyProvider())
                .contextHolder(new ContextHolder(context))
                .build();
    }
    ...
}
  1. Dependency里掌管着各式各样的依赖,被依赖的各实例通过Map管理。每个依赖都通过LazyDependencyCreator将各实例对应的懒加载回调缓存进去,然后保存在Map mProviders里。其后在各实例真正使用时调用createDependency() 进行创建:
public class Dependency {
    // 使用class作为key将对应实例缓存
    private final ArrayMap<Object, Object> mDependencies = new ArrayMap<>();
    // 同样的class的key缓存实例的懒加载回调
    private final ArrayMap<Object, LazyDependencyCreator> mProviders = new ArrayMap<>();

    protected void start() {
        // 构建LazyDependencyCreator放入mProviders,回调取得对应实例
        mProviders.put(TIME_TICK_HANDLER, mTimeTickHandler::get);
        mProviders.put(BG_LOOPER, mBgLooper::get);
        mProviders.put(MAIN_LOOPER, mMainLooper::get);
        mProviders.put(MAIN_HANDLER, mMainHandler::get);
        mProviders.put(ActivityStarter.class, mActivityStarter::get);
        mProviders.put(BroadcastDispatcher.class, mBroadcastDispatcher::get);
        ...
    }

    // 根据class查询 mDependencies里的缓存
    private synchronized <T> T getDependencyInner(Object key) {
        T obj = (T) mDependencies.get(key);
        if (obj == null) {
            // 尚未缓存的话通过懒加载回调获取注入的实例并缓存进 mDependencies
            obj = createDependency(key);
            mDependencies.put(key, obj);
            if (autoRegisterModulesForDump() && obj instanceof Dumpable) {
                mDumpManager.registerDumpable(obj.getClass().getName(), (Dumpable) obj);
            }
        }
        return obj;
    }

    // 根据class查询 mProviders里的懒加载回调
    protected <T> T createDependency(Object cls) {
        Preconditions.checkArgument(cls instanceof DependencyKey<?> || cls instanceof Class<?>);
        LazyDependencyCreator<T> provider = mProviders.get(cls);
        if (provider == null) {
            throw new IllegalArgumentException("Unsupported dependency " + cls
                    + ". " + mProviders.size() + " providers known.");
        }
        // 里面的回调实现已经通过start()里的mProviders.put存入了,实际调用的就是各类Lazy.get()
        return provider.createDependency();
    }

    private interface LazyDependencyCreator<T> {
        // 封装好的回调
        T createDependency();
    }
}

启动SystemUIService

创建好Application后,会启动SystemUIService这个主Service,然后逐个启动其他组件。

public class SystemUIService extends Service {
    ...
    @Override
    public void onCreate() {
        super.onCreate();

        // Start all of SystemUI
        ((SystemUIApplication) getApplication()).startServicesIfNeeded();
        ...
    }
    ...
}

启动各类服务组件

回到 SystemUIApplication

public class SystemUIApplication {
    ...
    public void startServicesIfNeeded() {
        // 各services定义在 R.array.config_systemUIServiceComponents 里面
        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 {
                // 通过class name从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;
            } catch (ClassNotFoundException
                    | NoSuchMethodException
                    | IllegalAccessException
                    | InstantiationException
                    | InvocationTargetException ex) {
                throw new RuntimeException(ex);
            }

            if (DEBUG) Log.d(TAG, "running: " + mServices[i]);
            // 调用各组件实现的start()方法
            mServices[i].start();
            ...
        }
        ...
    }
}

其中 mComponentHelper 在上一节中有提到,下面讲解一下它的注入过程。
在 SystemUIRootComponent 中定义了 ContextComponentHelper 单例对象的获取方法,

@Singleton
@Component(modules = {
        ...
        SystemUIModule.class,
        SystemUIDefaultModule.class})
public interface SystemUIRootComponent {
    ...
    @Singleton
    ContextComponentHelper getContextComponentHelper();
    ...
}

上文dagger2注解基础里有提到,无参有返回值的方法,实例从定义的Module中查找,这里在 SystemUIModule 里可以找到,实际注入的是 ContextComponentResolver 对象,它是 ContextComponentHelper 的实现类:

@Module(includes = {
            ...
        },
        subcomponents = {StatusBarComponent.class,
                NotificationRowComponent.class,
                ExpandableNotificationRowComponent.class})
public abstract class SystemUIModule {
    ...
    @Binds
    public abstract ContextComponentHelper bindComponentHelper(
            ContextComponentResolver componentHelper);
    ...
}

由于该方法标了abstract并没有进行实现,所以实例对象会通过 ContextComponentResolver 里面带 @Inject 标签的构造方法来生成。

@Singleton
public class ContextComponentResolver implements ContextComponentHelper {
    private final Map<Class<?>, Provider<Activity>> mActivityCreators;
    private final Map<Class<?>, Provider<Service>> mServiceCreators;
    private final Map<Class<?>, Provider<SystemUI>> mSystemUICreators;
    private final Map<Class<?>, Provider<RecentsImplementation>> mRecentsCreators;
    private final Map<Class<?>, Provider<BroadcastReceiver>> mBroadcastReceiverCreators;

    @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) {
        mActivityCreators = activityCreators;
        mServiceCreators = serviceCreators;
        mSystemUICreators = systemUICreators;
        mRecentsCreators = recentsCreators;
        mBroadcastReceiverCreators = broadcastReceiverCreators;
    }
    ...

    /**
     * Looks up the SystemUI class name to see if Dagger has an instance of it.
     */
    @Override
    public SystemUI resolveSystemUI(String className) {
        return resolve(className, mSystemUICreators);
    }

    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;
        }
    }
}

这里的构造方法由于带了 @Inject,所以它的参数Map都会一起注入进去,那这些Map又是哪里来的呢,这里提一下dagger2的 MultiBindings。

首先在 Module 中多次定义一系列返回值类型相同的方法,这里以 SystemUIBinder 为例,如果需要的类型是Map,那么就使用 @IntoMap 和 对应key的 @MapKey 的实现 @ClassKey,@IntoMap 表示这些方法的返回值对象都要装入同一个Map集合中,@ClassKey 表示Map的key为class。
然后还必须加上 @Provides 标签,如果是接口或抽象类则用 @Binds。这样声明下来,dagger2 会自动为其注入 Map<Class<?>, Provider<SystemUI>>,即把上面的 systemUICreators 给注入进去了。

/**
 * SystemUI objects that are injectable should go here.
 */
@Module(includes = {RecentsModule.class, StatusBarModule.class, BubbleModule.class,
        KeyguardModule.class})
public abstract class SystemUIBinder {
    /** Inject into AuthController. */
    @Binds
    @IntoMap
    @ClassKey(AuthController.class)
    public abstract SystemUI bindAuthController(AuthController service);

    /** Inject into Divider. */
    @Binds
    @IntoMap
    @ClassKey(Divider.class)
    public abstract SystemUI bindDivider(Divider sysui);
    ...

    /** Inject into StatusBar. */
    @Binds
    @IntoMap
    @ClassKey(StatusBar.class)
    public abstract SystemUI bindsStatusBar(StatusBar sysui);
    ...
}

以 bindsStatusBar 为例,参数 StatusBar 的实例又是由 StatusBarPhoneModule 提供的

/**
 * Dagger Module providing {@link StatusBar}.
 */
@Module(includes = {StatusBarPhoneDependenciesModule.class})
public interface StatusBarPhoneModule {
    /**
     * Provides our instance of StatusBar which is considered optional.
     */
    @Provides
    @Singleton
    static StatusBar provideStatusBar(
            Context context,
            NotificationsController notificationsController,
            LightBarController lightBarController,
            AutoHideController autoHideController,
            KeyguardUpdateMonitor keyguardUpdateMonitor,
            StatusBarIconController statusBarIconController,
            ...) {
        return new StatusBar(
                context,
                notificationsController,
                lightBarController,
                autoHideController,
                keyguardUpdateMonitor,
                statusBarIconController,
                ...);
    }
}

new StatusBar 对象所用到的参数,有些是通过内部自身带 @Inject 标签的构造方法注入,有些则是又再通过其他 Module 提供,这里就不再往下继续跟踪了。

到此,config_systemUIServiceComponents 里面定义的组件的实例对象注入过程就结束了。

总结

由于 SystemUI 模块内大部分结构都已经用上了 Dagger2 框架,本文旨在通过对 Dagger2 框架进行一些基础概念的总结,结合 SystemUI 部分的源码来了解其工作原理,这样在后续解读代码以及添加需求功能的时候可以用上。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容