前言
以为工作需求,所以最近补了补之前没了解过的Dagger2的内容,基础篇已经发布。接下来就是Dagger2在Android中的应用了。当然,和我一样刚接触Dagger2的朋友,可以先看一下之前的基础文章:
正文
这篇文章的Demo实在是太好了。所以我就厚颜无耻的把他的代码拿过来用...这是一个外国哥们的文章,我猜他应该不会怪我的,哈哈...
进入正文之前,我们先看一下背景。代码需求很简单,从一个API上获取数据,然后加载到RecycleView上,并且会涉及到图片加载。
在这么一个需求之下,我们如果使用Dagger2为我们的工程提供相应的依赖呢?
简单罗列一下代码设计
- MainActivity.java:请求API并显示项目 RecyclerView
- Result.java:用于API响应的POJO,使用JSON Schema创建到POJO
- RandomUsersAdapter.java:适配器 RecyclerView
涉及以下依赖项和库。
- Retrofit
- GsonBuilder&Gson
- HttpLoggingInterceptor
- OkHttpClient
- Picasso
以上内容不重要,都是咱们日常开发常用的东西。怎么使用啥的,大家肯定都很属性,所以下文demo很多初始化啥的就跳过了,咱们的关注是Dagger2在Android中的应用。
注意,这里不是叭叭叭的贴代码,讲API,而是从一个业务出发,以Dagger2的角度讲解Daager2的依赖关系,相信你们会我和一样,看完一定会“拨云雾见青天”,哈哈~
一般解决方案
面对这种需求,我们的常规写法:
public class MainActivity extends AppCompatActivity {
// 省略变量声明
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 省略View的初始化
// 省略Gson初始化
// 省略HttpLoggingInterceptor及OkHttpClient初始化
// 不省略太多了,免得失去代入感,哈哈。
retrofit = new Retrofit.Builder()
.client(okHttpClient)
.baseUrl("https://randomuser.me/")
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
populateUsers();
}
// 网络请求
private void populateUsers() {
Call<RandomUsers> randomUsersCall = getRandomUserService().getRandomUsers(10);
randomUsersCall.enqueue(new Callback<RandomUsers>() {
@Override
public void onResponse(Call<RandomUsers> call, @NonNull Response<RandomUsers> response) {
if(response.isSuccessful()) {
mAdapter = new RandomUserAdapter();
mAdapter.setItems(response.body().getResults());
recyclerView.setAdapter(mAdapter);
}
}
// 省略请求失败
});
}
public RandomUsersApi getRandomUserService(){
return retrofit.create(RandomUsersApi.class);
}
}
public class RandomUserAdapter extends RecyclerView.Adapter<RandomUserAdapter.RandomUserViewHolder> {
private List<Result> resultList = new ArrayList<>();
public RandomUserAdapter() {}
@Override
public RandomUserViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_random_user,
parent, false);
return new RandomUserViewHolder(view);
}
@Override
public void onBindViewHolder(RandomUserViewHolder holder, int position) {
Result result = resultList.get(position);
// setText操作
holder.textView.setText(String.format("%s %s", result.getName().getFirst(),
result.getName().getLast()));
// 图片库加载图片
Picasso.with(holder.imageView.getContext())
.load(result.getPicture().getLarge())
.into(holder.imageView);
}
// 省略部分代码
}
写完上述代码之后,让我们挺一分钟,想一想我们刚才写的东西,是不是有明显的依赖关系?比如,我们的Activity依赖Retrofit,我们的Retrofit又依赖OkHttp等等这种关系。
而且,所有的初始化操作都其中在Activity做了处理,如果此时我们需要更多的Activity,难道还有一遍遍写重复的代码?
当然可能有朋友会说可以使用基类,或者单例等等的封装方式。不过今天我们通通不考虑这些,今天只聊Dagger2
上述的业务其实还很多内容没有考虑到,比如说缓存...因此我们的业务如果更精细一些会发现,还有依赖更多的模块:
- File/DB: 持久化缓存
- 内存Cache: 内存缓存
- OkHttp3Downloader: 下载模块
所以如果完整的展开代码,应该是这样的:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initViews();
// Gson依赖
GsonBuilder gsonBuilder = new GsonBuilder();
Gson gson = gsonBuilder.create();
// File持久化依赖
File cacheFile = new File(this.getCacheDir(), "HttpCache");
cacheFile.mkdirs();
// Cache内存依赖
Cache cache = new Cache(cacheFile, 10 * 1000 * 1000); //10 MB
// Log依赖
HttpLoggingInterceptor httpLoggingInterceptor = new
HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
@Override
public void log(@NonNull String message) {
Timber.i(message);
}
});
httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
// OkHttp依赖
OkHttpClient okHttpClient = new OkHttpClient()
.newBuilder()
.cache(cache)
.addInterceptor(httpLoggingInterceptor)
.build();
OkHttp3Downloader okHttpDownloader = new OkHttp3Downloader(okHttpClient);
// Picasso依赖
picasso = new Picasso.Builder(this).downloader(okHttpDownloader).build();
// Retrofit依赖
retrofit = new Retrofit.Builder()
.client(okHttpClient)
.baseUrl("https://randomuser.me/")
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
populateUsers();
}
作为“久经沙场”的老司机,这些都是家常便饭,便饭之余我们聊一些茶语饭后的话题:从这些初始化代码中抽象出一个依赖图。就如果Retrofit初始化时传了一个Gson,那就说明Retrofit依赖Gson...
因此,我们差不多能够梳理一个依赖关系图:
image
绿色框表示它们是依赖关系中的顶级的(任何模块都不想要依赖它),它们只会被依赖。
结合我们写过的初始化代码,这个图很好理解吧?我猜肯定有朋友这张图都已经在写下代码之时出现在脑海中了...
走到这一步,其实问题就已经显现出来了。这么庞大的初始化的过程,任谁都不会想再写第二遍。因此重构迫在眉睫。既然我们都已经捋清楚了我们所需要模块的依赖关系,那么接下来就是让Dagger2大展身手的时候了...
Dagger2登场
前置准备
最开始当然是引入我们的Dagger2模块,没啥好说的...
dependencies {
implementation 'com.google.dagger:dagger:2.13'
annotationProcessor 'com.google.dagger:dagger-compiler:2.13'
}
第1步:创建组件(Component)
组件将充当整个依赖关系图的公共接口。使用组件的最佳实践是仅暴露出最顶级的依赖关系。
这意味着,在Component中我们只提供在依赖图中绿色辨识的类。也就是:RandomUsersAPI和Picasso。
创建一个名为RandomUserComponent
的组件并对外暴露RandomUsersApi
和Picasso
。
@Component
public interface RandomUserComponent {
RandomUsersApi getRandomUserService();
Picasso getPicasso();
}
Component将提供最顶层的依赖:RandomUsersApi和Picasso。
第2步:创建模块(Module)
我们现在需要将MainActivity
中的代码移动到不同的模块去。所以接下来,我们需要基于依赖图去设计我们需要哪些模块。
首先是,RandomUsersModule
:
通过图,我们可以基本设计出来
RandomUsersModule
,构建它我们需要提供RandomUsersApi
、GsonConverterFactory
、Gson
和Retrofit
以及一个OkHttpClient。
@Module
public class RandomUsersModule {
@Provides
public RandomUsersApi randomUsersApi(Retrofit retrofit){
return retrofit.create(RandomUsersApi.class);
}
@Provides
public Retrofit retrofit(OkHttpClient okHttpClient,
GsonConverterFactory gsonConverterFactory, Gson gson){
return new Retrofit.Builder()
.client(okHttpClient)
.baseUrl("https://randomuser.me/")
.addConverterFactory(gsonConverterFactory)
.build();
}
@Provides
public Gson gson(){
GsonBuilder gsonBuilder = new GsonBuilder();
return gsonBuilder.create();
}
@Provides
public GsonConverterFactory gsonConverterFactory(Gson gson){
return GsonConverterFactory.create(gson);
}
}
写到这,我们会发现,想要提供一个OkHttpClient
需要提供太多的依赖,因此让我们创建一个OkHttpClientModule
,它提供OkHttpClient
、Cache
、HttpLoggingInterceptor
和File
以及一个Context。
@Module
public class OkHttpClientModule {
@Provides
public OkHttpClient okHttpClient(Cache cache, HttpLoggingInterceptor httpLoggingInterceptor){
return new OkHttpClient()
.newBuilder()
.cache(cache)
.addInterceptor(httpLoggingInterceptor)
.build();
}
@Provides
public Cache cache(File cacheFile){
return new Cache(cacheFile, 10 * 1000 * 1000); //10 MB
}
@Provides
public File file(Context context){
File file = new File(context.getCacheDir(), "HttpCache");
file.mkdirs();
return file;
}
@Provides
public HttpLoggingInterceptor httpLoggingInterceptor(){
HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
@Override
public void log(String message) {
Timber.d(message);
}
});
httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
return httpLoggingInterceptor;
}
}
写完这个,我们会发现没办法给它提供Context
,现在先不要着急,因为我们发现似乎还有一个模块也需要Context
:
没错,就是Picasso
,让我们创建一个PicassoModule
,并且为它提供:Picasso
和OkHttp3Downloader
。
@Module
public class PicassoModule {
@Provides
public Picasso picasso(Context context, OkHttp3Downloader okHttp3Downloader){
return new Picasso.Builder(context).
downloader(okHttp3Downloader).
build();
}
@Provides
public OkHttp3Downloader okHttp3Downloader(OkHttpClient okHttpClient){
return new OkHttp3Downloader(okHttpClient);
}
}
OK,我们的高层模块已准备就绪,不过大家还记不记得一个遗留问题:PicassoModule
和OkHttpClientModule
需求Context
。Context
的地位不言而喻,因此我们一定会遇到其他模块也需要Context
的情形。那么为什么不给它一个模块呢?
@Module
public class ContextModule {
Context context;
public ContextModule(Context context){
this.context = context;
}
@Provides
public Context context(){ return context.getApplicationContext(); }
}
到此,我们第2步的准备工作就完成了,接下来我们需要让它们需要互相提供依赖!
第3步:连接所有模块
现在,我们已经准备好所有模块和组件:
image
但是我们让彼此独立的模块,互相依赖呢?这是includes属性发挥作用的地方。includes属性包括当前模块中涉及的其他模块的依赖关系。
什么模块需要包括在内?
-
RandomUsersModule
需要OkHttpClientModule
-
OkHttpClientModule
需要ContextModule
-
PicassoModule
需要OkHttpClientModule
和ContextModule
。但由于已经OkHttpClientModule
与之相关ContextModule
,所以我们只包括OkHttpClientModule
。
//in RandomUsersModule.java
@Module(includes = OkHttpClientModule.class)
public class RandomUsersModule { ... }
//in OkHttpClientModule.java
@Module(includes = ContextModule.class)
public class OkHttpClientModule { ... }
//in PicassoModule.java
@Module(includes = OkHttpClientModule.class)
public class PicassoModule { ... }
通过提供上述内容,我们已经链接了所有模块。
image第4步:连通组件
现在,我们所有的模块(Module)都已连接,可以相互依赖了。那么接下来,我们只需告诉我们的顶层组件RandomUserComponent
,需要依赖哪些模块来使自己能够正常工作。
有了第3步的基础,这里应该很容易捋清关系吧?只不过这里不用includes,而是用modules。
@Component(modules = {RandomUsersModule.class, PicassoModule.class})
public interface RandomUserComponent {
RandomUsersApi getRandomUserService();
Picasso getPicasso();
}
image
第5步:Build就行了
是时候Build工程了。Dagger将使用Builder模式创建RandomUserComponent
。现在,我们的MainActivity
可以很容易地获得Picasso并RandomUsersApi
。
public class MainActivity extends AppCompatActivity {
RandomUsersApi randomUsersApi;
Picasso picasso;
....
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
...
RandomUserComponent daggerRandomUserComponent = DaggerRandomUserComponent.builder()
.contextModule(new ContextModule(this))
.build();
picasso = daggerRandomUserComponent.getPicasso();
randomUsersApi = daggerRandomUserComponent.getRandomUserService();
populateUsers();
...
}
...
}
大功告成,我们很“魔法”的完成了依赖注入,就酱,是不是cao简单哒?...
但是
每次调用<DaggerComponent>.build()
时,它都会创建所有对象或依赖项的新实例,我们都很清楚,这些内容单例就好了。为什么Dagger2不知道我们只需要一个Picasso
单例呢?
换句话说,我们如何告诉Dagger2为我们提供单实例依赖?这就是下一篇内容所要涉及的内容~
尾声
说实话,关于Dagger2怎么说呢?要不是因为组里有一个Dagger2大佬,还真没信心没动力去搞它。不过既然有资源,那就学一学吧。
唉,别tm更新啦,学不动啦~
个人公众号:咸鱼正翻身
网友评论