听说你还不会用Dagger2?Dagger2 For Android最佳实践教程

@Inject
public A(){

}
}

public class B{
@Inject A a;

}

这种方法是最简单的,没什么难度。但是在实际的项目中我们会遇到各种各样的复杂情况,例如,A还需要依赖其它的类,并且这个类是第三方类库中提供的。又或者A实现了C接口,我们在编码的时候需要使用依赖导致原则来加强我们的代码的可维护性等等。这个时候,用上面这种方法是没办法实现这些需求的,我们使用Dagger2的主要难点也是因为上面这些原因导致的。

还是用上面的例子来解释,假设需要做一个餐饮系统,需要把点好的菜单发给厨师,让厨师负责做菜。现在我们来尝试下用Dagger2来实现这个需求。

首先,我们需要引入Dagger For Android的一些列依赖库:

implementation ‘com.google.dagger:dagger-android:2.17’
implementation ‘com.google.dagger:dagger-android-support:2.17’ // if you use the support libraries
implementation ‘com.google.dagger🗡2.17’
annotationProcessor ‘com.google.dagger:dagger-compiler:2.17’
annotationProcessor ‘com.google.dagger:dagger-android-processor:2.17’

然后我们实现Chef类和Menu类

Cooking接口

public interface Cooking{
String cook();
}

Chef

public class Chef implements Cooking{

Menu menu;

@Inject
public Chef(Menu menu){
this.menu = menu;
}

@Override
public String cook(){
//key菜名, value是否烹饪
Map<String,Boolean> menuList = menu.getMenus();
StringBuilder sb = new StringBuilder();
for (Map.Entry<String,Boolean> entry : menuList.entrySet()){
if (entry.getValue()){
sb.append(entry.getKey()).append(“,”);
}
}

return sb.toString();
}
}

Menu

public class Menu {

public Map<String,Boolean> menus;

@Inject
public Menu( Map<String,Boolean> menus){
this.menus = menus;
}

Map<String,Boolean> getMenus(){
return menus;
}

}

现在我们写一个Activity,作用是在onCreate方法中使用Chef对象实现cooking操作。我们先来看看不使用Dagger2和使用Dagger2的代码区别。

MainActivity

public class MainActivity extends Activity {

@Override
protected void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.activity_main);
Map<String, Boolean> menus = new LinkedHashMap<>();
menus.put(“酸菜鱼”, true);
menus.put(“土豆丝”, true);
menus.put(“铁板牛肉”, true);
Menu menu = new Menu(menus);
Chef chef = new Chef(menu);
System.out.println(chef.cook());
}
}

DaggerMainActivity

public class DaggerMainActivity extends DaggerActivity {
@Inject
Chef chef;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d(TAG,chef.cook());
}
}

可以看到,在使用Dagger2的时候,使用者的代码会变得非常简洁。但是,Dagger 2还需要一些列的辅助代码来实现依赖注入的。如果用过Dagger2就知道要实现依赖注入的话,需要写十分多模版代码。那么我们可不可以用更简单的方式使用Dagger2呢?今天笔者就来介绍一下在Android上使用Dagger2的更简洁的方案。

我们先来看看在DaggerMainActivity上实现依赖注入还需要哪些代码。

CookModules

@Module
public class CookModules {

@Singleton
@Provides
public Map<String, Boolean> providerMenus(){
Map<String, Boolean> menus = new LinkedHashMap<>();
menus.put(“酸菜鱼”, true);
menus.put(“土豆丝”, true);
menus.put(“铁板牛肉”, true);
return menus;
}
}

ActivityModules

@Module
abstract class ActivityModules {

@ContributesAndroidInjector
abstract MainActivity contributeMainActivity();
}

CookAppComponent

@Singleton
@Component(modules = {
AndroidSupportInjectionModule.class,
ActivityModules.class,
CookModules.class})
public interface CookAppComponent extends AndroidInjector {

@Component.Builder
abstract class Builder extends AndroidInjector.Builder{}

}

MyApplication

public class MyApplication extends DaggerApplication{

@Override
public void onCreate() {
super.onCreate();
}

@Override
protected AndroidInjector<? extends DaggerApplication> applicationInjector() {
return DaggerCookAppComponent.builder().create(this);
}
}

Dagger2 For Android 使用要点分析

  1. CookModules CookModule很简单,它的目的就是通过@Providers注解提供Menu对象需要的数据。因为Menu是需要依赖一个Map对象的,所以我们通过CookModules给它构造一个Map对象,并自动把它注入到Menu实例里面。
  2. ActivityModules ActivityModules的主要作用就是通过@ContributesAndroidInjector来标记哪个类需要使用依赖注入功能,这里标记的是ManActivity,所以MainActivity能通过@Inject注解来注入Chef对象。
  3. CookAppComponent CookAppComponent相当于一个注射器,我们前面定义的Modules就是被注射的类,使用@Inject注入对象的地方就是接收者类。
  4. MyApplication MyAppliction的特点是继承了DaggerAppliction类,并且在applicationInjector方法中构建了一个DaggerCookAppComponent注射器。

这就是Dagger 2在Android中的使用方案了,在这里我们可以看到,接收这类(MainActivity)中的代码非常简单,实现依赖注入只使用了:

@Inject
Chef chef;

在接收类里面完全没有多余的代码,如果我们要拓展可以SecondsActivity的话,在SecondsActivity我们要用到Menu类。

那么我们只需要在ActivityModules中增加:

@ContributesAndroidInjector
abstract SecondsActivity contributeSecondsActivity();

然后在SecondsActivity注入Menu:

@Inject
Menu menu;

可以看到,对于整个工程来说,实现使用Dagger2 For Android实现依赖注入要写的模版代码其实非常少,非常简洁。只需要进行一次配置就可以,不需要频繁写一堆模版代码。总的来说,Dagger2造成模版代码增加这个问题已经解决了。

Demo地址:目录下的Dagger2Simple就是Demo地址,上面的例子为Dagger2Simple中的simple Modules

Dagger2的优势

在这里我们总结下使用Dagger2带来的优点。

  1. 减少代码量,提高工作效率 例如上面的例子中,我们构建一个Chef对象的话,不使用Dagger2的情况下,需要在初始化Chef对象之前进行一堆前置对象(Menu、Map)的初始化,并且需要手工注入到对应的实例中。你想像下,如果我们再加一个Restaurant( 餐馆 )对象,并且需要把Chef注入到Restaurant中的话,那么初始化Restaurant对象时,需要的前置步骤就更繁琐了。 可能有人会觉得,这也没什么啊,我不介意手工初始化。但是如果你的系统中有N处需要初始化Restaurant对象的地方呢?使用Dagger2 的话,只需要用注解注入就可以了。
  2. 自动处理依赖关系 使用Dagger2的时候,我们不需要指定对象的依赖关系,Dagger2会自动帮我们处理依赖关系(例如Chef需要依赖Menu,Menu需要依赖Map,Dagger自动处理了这个依赖关系)。
  3. 采用静态编译,不影响运行效率 因为Dagger2是在编译期处理依赖注入的,所以不会影响运行效率在一定的程度上还能提高系统的运行效率(例如采用Dagger2实现单例,不用加锁效率更高)。
  4. 提高多人编程效率 在多人协作的时候,一个人用Dagger2边写完代码后,其它所有组员都能通过@Inject注解直接注入常用的对象。加快编程效率,并且能大大增加代码的复用性。

上面我们介绍完了Dagger2 For Android的基本用法了。可能有些读者意犹未尽,觉得这个例子太简单了。那么我们来尝试下构建一个更加复杂的系统,深度体验下Dagger2 For Android的优势。现在我们在上面这个例子的基础上拓展下,尝试开发一个简单的点餐Demo来深度体验下。

Dagger2应用实战

现在我们来看下如何使用Dagger2来开发一个简单的Demo,这里笔者开发的Demo是一个简单的点餐Demo。这个Demo的功能非常简单,提供了菜单展示、菜单添加/编辑/删除和下单功能。而下单功能只是简单地把菜品名用Snackbar显示到屏幕上。

Demo展

操作展示

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Demo地址:目录下的Dagger2Simple就是Demo地址,上面的例子为Dagger2Simple中的order Modules

代码目录

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这个Demo采用经典的MVP架构,我们先来简单分析下Demo的细节实现。

  1. 使用SharedPreferences提供简单的缓存功能(存储菜单)。
  2. 使用Gson把列表序列化成Json格式数据,然后以String的形式保存在SharedPreferences中。
  3. 使用Dagger2实现依赖注入功能。

这样基本就实现了一个简单的点菜Demo了。

Dagger在Demo中的应用解释

当我们使用SharedPreferences和Gson实现缓存功能的时候我们会发现,项目中很多地方都会需要这个SharedPreferences和Gson对象。所以我们可以得出两个结论:

  1. 项目中多个模块会用到一些公共实例。
  2. 这些公共实例应该是单例对象。

我们看看是如何通过使用Dagger2提供全局的Modules来实现这类型对象的依赖注入。

CookAppModules

@Module
public abstract class CookAppModules {

public static final String KEY_MENU = “menu”;
private static final String SP_COOK = “cook”;

@Singleton
@Provides
public static Set providerMenus(SharedPreferences sp, Gson gson){
Set menus;
String menuJson = sp.getString(KEY_MENU, null);
if (menuJson == null){
return new LinkedHashSet<>();
}
menus = gson.fromJson(menuJson, new TypeToken<Set>(){}.getType());
return menus;
}

@Singleton
@Provides
public static SharedPreferences providerSharedPreferences(Context context){
return context.getSharedPreferences(SP_COOK, Context.MODE_PRIVATE);
}

@Singleton
@Provides
public static Gson providerGson(){
return new Gson();
}

@Singleton
@Binds
public abstract Context context(OrderApp application);

}

在这里以dishes模块为例子,dishes中DishesPresenter是负责数据的处理的,所以我们会在DishesPresenter注入这些实例。

DishesPresenter

public class DishesPresenter implements DishesContract.Presenter{

private DishesContract.View mView;

@Inject
Set dishes;

@Inject
Gson gson;

@Inject
SharedPreferences sp;

@Inject
public DishesPresenter(){

}

@Override
public void loadDishes() {
mView.showDishes(new ArrayList<>(dishes));
}

@Override
public String order(Map<Dish, Boolean> selectMap) {
if (selectMap == null || selectMap.size() == 0) return “”;
StringBuilder sb = new StringBuilder();

for (Dish dish : dishes){
if (selectMap.get(dish)){
sb.append(dish.getName()).append(“、”);
}
}
if (TextUtils.isEmpty(sb.toString())) return “”;

return "烹饪: " + sb.toString();
}

@Override
public boolean deleteDish(String id) {
for (Dish dish : dishes){
if (dish.getId().equals(id)){
dishes.remove(dish);
sp.edit().putString(CookAppModules.KEY_MENU, gson.toJson(dishes)).apply();
return true;
}
}
return false;
}

@Override
public void takeView(DishesContract.View view) {
mView = view;
loadDishes();
}

@Override
public void dropView() {
mView = null;
}
}

上面的代码能很好地体验Dagger2的好处,假如我们项目中有比较复杂的对象在很多地方都会用到的话,我们可以通过这种方式来简化我们的代码。

Dishes模块的UI是由Activity加Fragment实现的,Fragment实现了主要的功能,而Activity只是简单作为Fragment的外层。它们分别是:DishesActivity和DishesFragment

DishesActivity依赖了DishesFragment对象,而在DishesFragment则依赖了DishesAdapter、RecyclerView.LayoutManager、DishesContract.Presenter对象。

我们先来分别看看DishesActivity与DishesFragment的关键代码。

DishesActivity

public class DishesActivity extends DaggerAppCompatActivity {

@Inject
DishesFragment mDishesFragment;


}

DishesFragment

public class DishesFragment extends DaggerFragment implements DishesContract.View{

RecyclerView rvDishes;

@Inject
DishesAdapter dishesAdapter;

@Inject
RecyclerView.LayoutManager layoutManager;

@Inject
DishesContract.Presenter mPresenter;

@Inject
public DishesFragment(){

}

}

DishesFragment通过Dagger2注入了DishesAdapter、RecyclerView.LayoutManager、DishesContract.Presenter,而这些实例是由DishesModules提供的。

DishesModules

@Module
public abstract class DishesModules {

@ContributesAndroidInjector
abstract public DishesFragment dishesFragment();

@Provides
static DishesAdapter providerDishesAdapter(){
return new DishesAdapter();
}

@Binds
abstract DishesContract.View dishesView(DishesFragment dishesFragment);

@Binds
abstract RecyclerView.LayoutManager layoutManager(LinearLayoutManager linearLayoutManager);

}

这里我们先说明下这几个注解的作用。

  • @ContributesAndroidInjector 你可以把它看成Dagger2是否要自动把需要的用到的Modules注入到DishesFragment中。这个注解是Dagger2 For Android简化代码的关键,下面的小节会通过一个具体例子来说明。

  • @Module 被这个注解标记的类可以看作为依赖对象的提供者,可以通过这个被标记的类结合其它注解来实现依赖关系的关联。

  • @Provides 主要作用就是用来提供一些第三方类库的对象或提供一些构建非常复杂的对象在Dagger2中类似工厂类的一个角色。

  • @Binds 主要作用就是确定接口与具体的具体实现类,这样说得比较抽象,我们还是看看例子吧。 在DishesFragment中有这么一句代码:

@Inject
DishesContract.Presenter mPresenter;

我们知道DishesContract.Presenter是一个接口而这个接口可能有很多不同的实现类,而@Binds的作用就是用来确定这个具体实现类的。以看看PresenterModules的代码:

@Module
public abstract class PresenterModules {
@Binds
abstract DishesContract.Presenter dishesPresenter(DishesPresenter presenter);


}

从这句代码可以看出,使用@Inject注入的DishesContract.Presenter对象的具体实现类是DishesPresenter。

Dagger2 For Android是如何注入依赖的?

我们在用Dagger2的时候是通过一些模版代码来实现依赖注入的( DaggerXXXComponent.builder().inject(xxx) 这种模版代码),但是在Demo中的DishesFragment根本没看到类似的代码啊,那么这些对象是什么时候注入到DishesFragment重的呢?

答案就是**@ContributesAndroidInjector**注解

我们先来看看Dagger2是通过什么方式来实现自动把依赖注入到DishesActivity中的。

ActivityModules

@Module
public abstract class ActivityModules {

@ContributesAndroidInjector(modules = DishesModules.class)
abstract public DishesActivity contributesDishActivity();

@ContributesAndroidInjector(modules = AddEditModules.class)
abstract public AddEditDishActivity contributesAddEditDishActivity();

}

没错,就是@ContributesAndroidInjector这个注解,modules就代表这个DishesActivity需要依赖哪个Modules。这篇教程我们不解释它的具体实现原理,你只需要知道@ContributesAndroidInjector的作用就可以了。

我们以前使用Dagger2的时候,需要些很多Component来辅助我们实现依赖注入,而现在我们整个App中只需要写一个Component就可以了。@ContributesAndroidInjector注解会帮助我们生成其它需要的Component,并且自动处理Component之间的关系,自动帮我们使用生成的Component来注入依赖。

我们先看看我们现在整个模块中唯一存在的Component是怎么使用的。

OrderAppComponent

@Singleton
@Component(modules = {
AndroidSupportInjectionModule.class,
LayoutManagerModules.class,
CookAppModules.class,
PresenterModules.class,
ActivityModules.class})
public interface OrderAppComponent extends AndroidInjector{

@Component.Builder
abstract class Builder extends AndroidInjector.Builder{
}

}

OrderApp

public class OrderApp extends DaggerApplication {

@Override
protected AndroidInjector<? extends DaggerApplication> applicationInjector() {
return DaggerOrderAppComponent.builder().create(this);
}
}

为了加深大家对@ContributesAndroidInjecto注解r的理解,我们稍微修改下DishesModules

@Module
public abstract class DishesModules {

//@ContributesAndroidInjector
//abstract public DishesFragment dishesFragment();

@Provides
static DishesAdapter providerDishesAdapter(){
return new DishesAdapter();
}

@Binds
abstract DishesContract.View dishesView(DishesFragment dishesFragment);

@Binds
abstract RecyclerView.LayoutManager layoutManager(LinearLayoutManager linearLayoutManager);

}

DishesActivity

public class DishesActivity extends DaggerAppCompatActivity {

//@Inject
DishesFragment mDishesFragment;

Toolbar toolbar;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_dishes);

DishesFragment dishesFragment
= (DishesFragment) getSupportFragmentManager().findFragmentById(R.id.content_fragment);

if (dishesFragment == null){
mDishesFragment = new DishesFragment();//新增代码
dishesFragment = mDishesFragment;
ActivityUtils.addFragmentToActivity(getSupportFragmentManager(), dishesFragment, R.id.content_fragment);
}
initView();

}

}

//DaggerFragment改为Fragment
public class DishesFragment extends Fragment implements DishesContract.View{
}

这个时候,我们运行的时候会发现,DishesFragment中的依赖注入失败了,运行时会抛出空指针异常,没注入需要的数据。导致这个原因是因为我们在这里使用new来创建DishesFragment实例的,为什么使用new的时候会Dagger2没有帮我们注入实例呢?

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

写在最后

最后我想说:对于程序员来说,要学习的知识内容、技术有太多太多,要想不被环境淘汰就只有不断提升自己,从来都是我们去适应环境,而不是环境来适应我们!

这里附上上述的技术体系图相关的几十套腾讯、头条、阿里、美团等公司2021年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。

相信它会给大家带来很多收获:

当程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!

!!(备注:Android)**

写在最后

最后我想说:对于程序员来说,要学习的知识内容、技术有太多太多,要想不被环境淘汰就只有不断提升自己,从来都是我们去适应环境,而不是环境来适应我们!

这里附上上述的技术体系图相关的几十套腾讯、头条、阿里、美团等公司2021年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。

相信它会给大家带来很多收获:

[外链图片转存中…(img-ff5Ncrs6-1711948920830)]

[外链图片转存中…(img-DAIxuGoJ-1711948920830)]

当程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值