文章首发于个人博客
参考资料:
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会怎么样呢?试一下吧
结果是, 妥妥的报错了, 所以这里的结论是这样的:
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