dagger.android(Dagger2中的AndroidInjector)使用解析

文章首发于个人博客

参考资料:
Dagger2 与 AndroidInjector
告别 Dagger2 模板代码:Dagger Android 使用详解
Dagger2 Android

前面都是一些基础概念, 现在开始正式开始使用dagger.android了, 也就是说在正式项目中使用Dagger2. 正如聊聊 GeekNews 架构:MVP + RRD说的那样, 在学习一个框架之前要认清这个框架可以为我们带来什么好处, 能够帮助你找出最适合的使用场景从而灵活运用.Dagger2的好处就是降低耦合, 解除依赖关系, 就不用在Activity或者Fragment中写那么多new一个对象的代码了.所以我们的首要目标是, 学习如何给Activity以及Fragment注入对象.

在Android项目中使用Dagger的问题

我就直接搬用官方文档中的例子了.

在使用 Dagger2 进行 Android 开发时,不可避免的问题是很多类是由系统来实例化的, 比如 Activity 或者 Fragment。最理想的情况是 Dagger 能够创建所有需要依赖注入的对象,但事实上,我们不得不在容器的声明周期中声明这样的代码:

public class FrombulationActivity extends Activity {
  @Inject Frombulator frombulator;

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // DO THIS FIRST. Otherwise frombulator might be null!
    ((SomeApplicationBaseType) getContext().getApplicationContext())
        .getApplicationComponent()
        .newActivityComponentBuilder()
        .activity(this)
        .build()
        .inject(this);
    // ... now you can write the exciting code
  }
}

可以这段代码需要在生命周期方法中调用, 之后才能正常使用被注入的对象. 可以看到FrombulationActivity需要知道自身是被AppComponent所注入的, 而实际上, 被注入的类不应该了解注入者.
dagger.android就是为了在这一点上进行改进.

为Activity注入对象

首先还是要在build.gradle中添加依赖引入dagger.android

    // dagger2
    compile 'com.google.dagger:dagger:2.16'
    annotationProcessor 'com.google.dagger:dagger-compiler:2.16'
    // dagger.android
    compile 'com.google.dagger:dagger-android:2.16'
    compile 'com.google.dagger:dagger-android-support:2.16' // if you use the support libraries
    annotationProcessor 'com.google.dagger:dagger-android-processor:2.16'

开始注入:
1.在application component中安装AndroidInjectionModule

@Singleton
@Component(modules = {
    AndroidInjectionModule.class,
    ...
})
public interface AppComponent {
    void inject(DemoApplication application);
}

2.创建一个subcomponent

@Subcomponent(modules = UserModule.class)
public interface YourActivitySubcomponent extends AndroidInjector<YourActivity> {
    @Subcomponent.Builder
    abstract class Builder extends AndroidInjector.Builder<YourActivity> {}
}

需要继承AndroidInjector并且提供一个Builder, 并且该builder需要继承AndroidInjector.Builder(它继承自AndroidInjector.Factory)
另外, UserModule中是用来提供给Activity注入的对象, 代码如下:

@Module
public class UserModule {
    
    @Provides
    User provideUser() {
        User user = new User("mundane", 0);
        return user;
    }
}

3.新建一个Module

@Module(subcomponents = YourActivitySubcomponent.class)
public abstract class YourActivityModule {
    @Binds
    @IntoMap
    @ActivityKey(YourActivity.class)
    abstract AndroidInjector.Factory<? extends Activity>
    bindYourActivityInjectorFactory(YourActivitySubcomponent.Builder builder);
}

使用@Binds来将YourActivitySubcomponent.Builder绑定到接口AndroidInjector.Factory中, 因为在上面说了, YourActivitySubcomponent.Builder是继承了AndroidInjector.Factory的

4.将这个module添加到Application的Component中

@Singleton
@Component(modules = {
    AndroidInjectionModule.class,
    YourActivityModule.class,
    ...
})
public interface AppComponent {
    void inject(DemoApplication application);
}

5.修改我们的Applicatoin

public class DemoApplication extends Application implements HasActivityInjector {
    
    @Inject
    DispatchingAndroidInjector<Activity> dispatchingActivityInjector;
    
    @Override
    public void onCreate() {
        super.onCreate();
        DaggerAppComponent.create().inject(this);
    }
    
    @Override
    public AndroidInjector<Activity> activityInjector() {
        return dispatchingActivityInjector;
    }
    
}

首先这个application要实现HasActivityInjector, 然后会实现其中的方法activityInjector, 这时候我们需要注入一个DispatchingAndroidInjector对象(注意DispathingAndroidInjector是实现了AndroidInjector的), 该对象的注入我们直接用@Inject就行了, dagger会自动帮我们注入(注意, 我们并没有提供@Provides这样的方法来提供这个对象).
然后在activityInjector方法中返回DispatchingAndroidInjector对象

6.最后一步, 在Activity.onCreate()方法中, 调用AndroidInjection.inject(this), 要在super.onCreate()之前. 代码如下:

public class YourActivity extends AppCompatActivity {
    
    private static final String TAG = "YourActivity";
    
    @Inject
    User mUser;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        AndroidInjection.inject(this);
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_your);
        Log.d(TAG, "mUser = " + mUser);
        
    }
}

检验一下是否对Activity成功注入, 将程序跑起来, log打印如下:

D/YourActivity: mUser = me.mundane.daggerfragmentinjectdemo.bean.User@b1b6f33

可见我们已经成功对Activity注入了User对象.

分析一下原理, 直接看AndroidInjection.inject(this)这个方法

  public static void inject(Activity activity) {
    checkNotNull(activity, "activity");
    Application application = activity.getApplication();
    if (!(application instanceof HasActivityInjector)) {
      throw new RuntimeException(
          String.format(
              "%s does not implement %s",
              application.getClass().getCanonicalName(),
              HasActivityInjector.class.getCanonicalName()));
    }

    AndroidInjector<Activity> activityInjector =
        ((HasActivityInjector) application).activityInjector();
    checkNotNull(activityInjector, "%s.activityInjector() returned null", application.getClass());

    activityInjector.inject(activity);
  }

该方法首先取出Application对象, 如果发现这个application没有实现HasActivityInjector就会报错.
然后调用application的activityInjector()获取一个AndroidInjector<Activity>对象, 更确切的来说, 是DispatchingAndroidInjector<Activity>对象, 最后调用该对象的inject方法完成注入.
现在问题集中到了dispatchingActivityInjector对象, 该对象是我们在 Application 中通过 dagger 自动注入的.
但是这篇博客就暂时在这里浅尝辄止了, 具体的原理我会另开一篇博客详细介绍.

简化上述步骤

上面的一些步骤其实有些繁琐, 我们可以简化步骤2和步骤3.现在我们把YourActivitySubcomponent这个类了, 把它删了. 删了之后YourActivityModule这个类就报错了

所以现在修改这个类, 代码如下

@Module
public abstract class YourActivityModule {
    @ActivityScope
    @ContributesAndroidInjector(modules = { UserModule.class})
    abstract YourActivity contributeYourActivityInjector();
}

@ActivityScope是为了限制UserModule.class中提供的对象的作用域.
现在我们再次启动YourActivity, log如下:

D/YourActivity: mUser = me.mundane.daggerfragmentinjectdemo.bean.User@b1b6f33

可见这种方式一样能为Activity注入对象

上述简化步骤的原理

我们打开YourActivityModule_ContributeYourActivityInjector这个类, 它是由上述的简化步骤自动生成的, 代码如下:

@Module(
  subcomponents = YourActivityModule_ContributeYourActivityInjector.YourActivitySubcomponent.class
)
public abstract class YourActivityModule_ContributeYourActivityInjector {
  private YourActivityModule_ContributeYourActivityInjector() {}

  @Binds
  @IntoMap
  @ActivityKey(YourActivity.class)
  abstract AndroidInjector.Factory<? extends Activity> bindAndroidInjectorFactory(
      YourActivitySubcomponent.Builder builder);

  @Subcomponent(modules = UserModule.class)
  @ActivityScope
  public interface YourActivitySubcomponent extends AndroidInjector<YourActivity> {
    @Subcomponent.Builder
    abstract class Builder extends AndroidInjector.Builder<YourActivity> {}
  }
}

没错, 看起来就跟我们第一次中的步骤2和步骤3中的代码一样.

注入以Activity为构造参数的对象

有时候我们需要注入的对象需要以Context甚至Activity作为构造函数中的参数, 比如dialog.那这时代码该怎么写呢?我们改一下UserModule的代码

@Module
public class UserModule {
    
    @Provides
    User provideUser() {
        User user = new User("mundane", 0);
        return user;
    }
    
    @ActivityScope
    @Provides
    TextView provideTextView(YourActivity activity) {
        TextView textView = new TextView(activity);
        return textView;
    }
}

然后是YourActivity

public class YourActivity extends AppCompatActivity {
    
    private static final String TAG = "YourActivity";
    
    @Inject
    User mUser;
    @Inject
    TextView mTv;
    private FrameLayout mFl;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        AndroidInjection.inject(this);
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_your);
        Log.d(TAG, "mUser = " + mUser);
        mFl = findViewById(R.id.fl);
    
        mTv.setText(mUser.name);
        mFl.addView(mTv);
    }
}

我就直接上应用截图了


可见这个textView对象已经被成功注入了, 那同理, 要注入一个dialog对象也是不成问题的.
那现在我们把provideTextView方法中的入参类型改成Activity或者Context会怎么样呢?试一下吧

20180609201855.png

结果是, 妥妥的报错了, 所以这里的结论是这样的:
Component的inject方法接收父类型参数,而调用时传入的是子类型对象则无法注入,也就是说无法使用多态方式进行注入。
举个例子, 在不使用dagger.android的时候, 我们是这样写的
LoginActivity.java

public class LoginActivity extends AppCompatActivity {

    @Inject
    UserManager mManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        DaggerUserComponet.create().inject(this);
        mManager.login();
    }
}

UserComponet.java

@Singleton
@Component(modules = {UserModule.class})
public interface UserComponet {
    void inject(MainActivity mainActivity);
}

这时候编译就报错了, 因为 void inject(MainActivity mainActivity); 它里面接收的是MainActivity类型,显然注入LoginActivity类型是不允许的。
我们可能天真的认为,使用多态接收就可以了:

@Singleton
@Component(modules = {UserModule.class})
public interface UserComponet {
    void inject(Activity mainActivity);
}

还是编译错误, 这里应征了之前的结论:
Component的inject方法接收父类型参数,而调用时传入的是子类型对象则无法注入,也就是说无法使用多态方式进行注入。
那我们再接着看看能不能直接获取到Application呢?改写代码:

    @ActivityScope
    @Provides
    TextView provideTextView(DemoApplication application) {
        TextView textView = new TextView(application);
        return textView;
    }

编译报错了, 看样子不经过一点"处理"是不能直接获取到应用的application的, 这点"处理"我们稍后再说.

为Fragment注入对象

我们按照官方文档一步一步来就行.
1.首先需要对frament的宿主Activity进行一定的处理

public class MainActivity extends AppCompatActivity implements HasSupportFragmentInjector {
    
    @Inject
    DispatchingAndroidInjector<Fragment> mFragmentInjector;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        AndroidInjection.inject(this);
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    
    }
    
    @Override
    public AndroidInjector<Fragment> supportFragmentInjector() {
        return mFragmentInjector;
    }
}

和上面修改Application类似的步骤, 让MainActivity实现HasSupportFragmentInjector, 实现supportFragmentInjector方法, 然后需要注入一个DispatchingAndroidJnjector<Fragment>对象, 然后在supportFragmentInjector方法中将这个对象返回.

2.创建一个MainFragmentSubcomponent
和Activity类似的步骤, MainFragmentSubcomponent的代码如下:

@Subcomponent(modules = MainFragmentModule.class)
public interface MainFragmentSubcomponent extends AndroidInjector<MainFragment> {
    @Subcomponent.Builder
    abstract class Builder extends AndroidInjector.Builder<MainFragment> {
    }
}

3.创建一个BindMainFragmentModule
和Activity类似的步骤, BindMainFragmentModule的代码如下:

@Module(subcomponents = MainFragmentSubcomponent.class)
public abstract class BindMainFragmentModule {
    @Binds
    @IntoMap
    @FragmentKey(MainFragment.class)
    abstract AndroidInjector.Factory<? extends Fragment> bindMainFragmentInjectorFactory(
            MainFragmentSubcomponent.Builder builder);
}

4.安装BindMainFragmentModule
按照官方文档的说法, 我们可以把这个你的这个BindMainFragmentModule安装在你想要的位置, 比如另一个fragment的component中, 或者Activity的component中, 或者Application的component中, 取决于你自己. 但是相应的你需要生成相对应的HasFragmentInjector.因为我们这里的HasFragmentInjector是MainActivity, 所以我把它安装在Activity的component上, 代码如下:

@Subcomponent(modules = {MainActivityModule.class, BindMainFragmentModule.class})
public interface MainActivitySubcomponent extends AndroidInjector<MainActivity> {
    @Subcomponent.Builder
    abstract class Builder extends AndroidInjector.Builder<MainActivity> {}
}

5.修改fragment
最终, fragment的代码如下:

public class MainFragment extends Fragment {
    
    private static final String TAG = "MainFragment";
    
    @Inject
    FragmentUser mFragmentUser;
    
    public MainFragment() {
        // Required empty public constructor
    }
    
    @Override
    public void onAttach(Context context) {
        Log.d(TAG, "context = " + context);
        AndroidSupportInjection.inject(this);
        super.onAttach(context);
    }
    
    
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_blank, container, false);
    }
    
    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        Log.d(TAG, "mFragmentUser = " + mFragmentUser);
    }
}

我们将程序跑起来, log打印如下:

D/MainFragment: context = me.mundane.daggerfragmentinjectdemo.activity.MainActivity@6db1066
D/MainFragment: mFragmentUser = me.mundane.daggerfragmentinjectdemo.bean.FragmentUser@b6c9a25

可见对Fragment的对象注入成功了.

简化对Fragment进行注入的步骤

也是和对Activity的简化的步骤类似.首先抛弃MainFragmentSubcomponent这个类, 然后改写BindMainFragmentModule这个类, 使用注解@ContributesAndroidInjector, 代码如下

@Module
public abstract class BindMainFragmentModule {
    
    @ContributesAndroidInjector(modules = MainFragmentModule.class)
    abstract MainFragment contributeMainFragmentInjector();
}

这样就行了, 跑起来的效果和上面是一样的.

如何使用一些全局性的对象

之前提到了, 想要获取到在activity的module中直接获取到应用的application需要一点处理, 这一小节就是讲这个.
有时候我们需要使用到一些全局性的对象, 比如Application, SharedPreferences, 和网络请求的一些全局对象等等.这时候需要怎么办呢?
首先需要用到@Component.Builder@BindsInstance这两个注解, 如果不理解, 参考我之前写的这篇Dagger2 初探 (三)
改写AppComponent

@Singleton
@Component(modules = {
        AppModule.class,
        BindYourActivityModule.class,
        BindMainActivityModule.class,
        AndroidSupportInjectionModule.class,
        AndroidInjectionModule.class
})
public interface AppComponent {
    void inject(DemoApplication application);
    
    @Component.Builder
    interface Builder {
        
        AppComponent build();
        
        @BindsInstance
        Builder application(Application application);
    }
}

然后再改写Application中的代码

public class DemoApplication extends Application implements HasActivityInjector {
    
    @Inject
    DispatchingAndroidInjector<Activity> dispatchingActivityInjector;
    
    @Override
    public void onCreate() {
        super.onCreate();
        DaggerAppComponent.builder().application(this).build().inject(this);   
    }
    
    @Override
    public AndroidInjector<Activity> activityInjector() {
        return dispatchingActivityInjector;
    }
    
    
}

然后我们在AppModule中提供SharedPreferences对象

@Module
public abstract class AppModule {
    private static final String TAG = "AppModule";
    
    private static final String DATA_STORE = "DATA_STORE";
    
    @Singleton
    @Provides
    static SharedPreferences providePreferences(Application application) {
        SharedPreferences preferences =
                application.getSharedPreferences(DATA_STORE, Context.MODE_PRIVATE);
        Log.d(TAG, "preferences = " + preferences);
        return preferences;
    }
}

现在mvp的架构模式很流行, 所以我们试着来建立一个YourActivityPresenter, 里面注入一个SharedPreferences对象

public class YourActivityPresenter {
    SharedPreferences mSharedPreferences;
    
    @Inject
    public YourActivityPresenter(@NonNull SharedPreferences sharedPreferences) {
        mSharedPreferences = sharedPreferences;
    }
    
    public SharedPreferences getSharedPreferences() {
        return mSharedPreferences;
    }
}

然后在YourActivity注入这个YourActivityPresenter

public class YourActivity extends AppCompatActivity {
    
    private static final String TAG = "YourActivity";
    
    @Inject
    User mUser;
    @Inject
    TextView mTv;
    @Inject
    YourActivityPresenter mPresenter;
    private FrameLayout mFl;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        AndroidInjection.inject(this);
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_your);
        Log.d(TAG, "mUser = " + mUser);
        mFl = findViewById(R.id.fl);
    
        mTv.setText(mUser.name);
        mFl.addView(mTv);

        Log.d(TAG, "mPresenter = " + mPresenter);
        Log.d(TAG, "mPresenter.getSharedPreferences() = " + mPresenter.getSharedPreferences());
    }
}

打印的log:

D/AppModule: preferences = android.app.SharedPreferencesImpl@1bfa7fd
D/YourActivity: mPresenter = me.mundane.daggerfragmentinjectdemo.Presenter.YourActivityPresenter@b6c9a25
D/YourActivity: mPresenter.getSharedPreferences() = android.app.SharedPreferencesImpl@1bfa7fd

可以看到perferences已经注入成功了, 并且和AppModule中的perferences是同一个对象.其实也可以在Activity中直接注入这个perferences对象, dagger会自动帮我们找到在AppModule中提供SharedPreferences的方法, 从而注入SharedPreferences对象.如果想注入一个应用的Application对象, 就这么写:

@Module
public abstract class AppModule {
        
    @Singleton
    @Provides
    static Context provideApplicationContext(Application application) {
        return application;
    }
    
    ...
    
}

利用这个特点, 我们可以新建一个叫HttpModule的module, 然后将全局性的对象比如retrofit的service方法在这个module中, 听起来是不是挺不错的呢?

demo地址

https://github.com/mundane799699/AndroidProjects/tree/master/DaggerFragmentInjectDemo

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

推荐阅读更多精彩内容

  • 部分内容参考自:[Android]使用Dagger 2依赖注入 - DI介绍(翻译)[Android]使用Dagg...
    AItsuki阅读 46,870评论 65 356
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,598评论 25 707
  • 1 前言 距离首次接触 Dagger2 已经有半年的时间了,从最初的一脸懵逼,到慢慢的熟练使用,这个过程真的感谢 ...
    xiaobailong24阅读 5,723评论 2 32
  • 夜深人静 一群拾梦者踩着月光舞步 聚集在海天的尽头找寻 童年的糖果屋漂浮着梦境 月光躺在甜蜜融化的屋顶,睡得安心 ...
    清白脸庞阅读 874评论 58 84
  • 两个月前,我有了自己的第一个孩子,这是个前所未有的第一次,这意味着我自己的人生差不多就从两个月前的那天逐渐...
    乐之仪阅读 162评论 0 1