Android Dagger2 MVP架構 一看就明白
Dagger2介紹
好了,介紹一下Dagger2吧!
Dagger2 是Google 的新一代依賴注入框架(依賴注入不講,你都看到這篇文章了,那你應該懂,如果不懂,請度娘、谷哥之,此文不廢話),Dagger2是Dagger1的分支,但兩個框架沒有嚴格的繼承關係,亦如Struts1 和Struts2 的關係!
那就有人問了,為什麼要用Dagger2?
回答:解耦(DI的特性),易於測試(DI的特性),高效(不使用反射,google官方說名比Dagger快13%),易混淆(apt方式生成程式碼,混淆後依然正常使用)
學習成本
開啟官網,映入眼簾的第一句話便是:
Dagger ‡ A fast dependency injector for Android and Java. - Google
如果你是個想要很簡單,並且速度很快的就能上手使用Dagger2的客官,你可以點選視窗右上角的X,關閉該文章了!
Dagger2的學習曲線相對比較陡峭,需要理解的概念也較多,需要一點一點的理解,Dagger2概念還是推薦看下面這篇文章。
本文只講基礎概念和使用,具體的概念請參見官方文件或其他博文!
關鍵的註解
@Inject
這個註解是用來說明該註解下方的屬性或方法需要依賴注入。(如果使用在類構造方法上,則該類也會被註冊在DI容器中作為注入物件。很重要,理解這個,就能理解Presenter注入到Activity的步驟!)
@Provider
在@Module註解的類中,使用@Provider註解,說明提供依賴注入的具體物件
@Component
簡單說就是,可以通過Component訪問到Module中提供的依賴注入物件。假設,如果有兩個Module,AModule、BModule,如果Component只註冊了AModule,而沒有註冊BModule,那麼BModule中提供的物件,無法進行依賴注入!
@SubComponent
該註解從名字上就能知道,它是子Component,會完全繼承父Component的所有依賴注入物件!
@Sigleton
被註解的物件,在App中是單例存在的!
@Scope
用來標註依賴注入物件的適用範圍。
@Named(@)
因為Dagger2 的以來注入是使用型別推斷的,所以同一型別的物件就無法區分,可以使用@Named註解區分同一型別物件,可以理解為物件的別名!
Android Studio 配置Dagger2(eclipse 請點選右上角X)
Step 1
專案根目錄下的build.gradle。根目錄、根目錄、根目錄,重要事情說三遍!
在dependencies程式碼塊中加入
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
加完成後的build.gradle如下
buildscript {
repositories {
jcenter()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.1.0'
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
//配置DBFlow
}
}
allprojects {
repositories {
maven { url "https://www.jitpack.io" }
jcenter()
mavenCentral()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
如果你使用DB-Flow 或 ButterKnif已經新增過了,就不用添加了!
allprojects的內容不需要與我貼出來的完全一致。
Step 2
使用Dagger2的專案下的build.gradle,一般都是app資料夾下的。
在build.gradle檔案頂部 apply plugin: ‘com.android.application’ 下方新增
apply plugin: 'android-apt'
dependencies程式碼塊中新增Dagger2的依賴關係
//使用APT生成工具,生成需要的DI程式碼
apt 'com.google.dagger:dagger-compiler:2.5'
//JSR250的jar包,使用這個和使用glassFish的那個一樣,僅為了使用@Inject 和@Named註解
provided 'javax.annotation:jsr250-api:1.0'
//Dagger2 的依賴
compile 'com.google.dagger:dagger:2.5'
Step3 Make Project,使依賴生效!
配置單例物件,上程式碼!(還是配置,配置、配置)
分析一下,我們一般都需要哪些東西是單例的,Http 請求類,SharedPreference等等。
程式碼結構如下
Step 1 建立ActivityScope
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface ActivityScope {
}
該類用於區分與@Sigleton或其他@Scope的作用域。
Strp2 建立module
我們首先來分析一下,需要哪些類是單例的,單例建立的,都和Application關聯起來。
1、提供 shredPreference,建立AppModule
@Module
public class AppModule {
private Context context;
public AppModule(DaggerApplication application) {
this.context = application;
}
@Singleton
@Provides
public Context ProviderApplicationContext(){
return context;
}
@Singleton
@Provides
@Named("default")
public SharedPreferences providerDefaultSharedPreferences(){
return PreferenceManager.getDefaultSharedPreferences(context);
}
@Singleton
@Provides
@Named("encode")
public SharedPreferences providerEncodeSharedPreferences(){
return context.getSharedPreferences("encode",Context.MODE_PRIVATE);
}
}
2、因為是使用的Retrofit 所以要提供 OkhttpClient ,RetrofitClient
建立OkhttpModule
@Module
public class OkhttpModule {
@Singleton
@Provides
@Named("cache")
public OkHttpClient providerAutoCacheOkHttpClient(){
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
Interceptor cacheInterceptor = new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response response = chain.proceed(request);
String cacheControl = request.cacheControl().toString();
if (TextUtils.isEmpty(cacheControl)) {
cacheControl = "public, max-age=" + 3600 * 6 + " ,max-stale=2419200";
}
return response.newBuilder()
.header("Cache-Control", cacheControl)
.removeHeader("Pragma")
.build();
}
};
return new OkHttpClient.Builder()
.addNetworkInterceptor(interceptor)
.addNetworkInterceptor(cacheInterceptor)
.retryOnConnectionFailure(true)
.connectTimeout(10, TimeUnit.SECONDS)
.build();
}
@Singleton
@Provides
@Named("default")
public OkHttpClient providerOkHttpClient(){
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
return new OkHttpClient.Builder()
.addNetworkInterceptor(interceptor)
.retryOnConnectionFailure(true)
.connectTimeout(10, TimeUnit.SECONDS)
.build();
}
}
建立RetrofitModule
@Module
public class RetrofitModule {
@Singleton
@Provides
public LocalRetrofit providerLocalRetrofit(@Named("default") OkHttpClient okHttpClient){
return new LocalRetrofit(okHttpClient);
}
@Singleton
@Provides
public TaobaoRetrofit providerTaobaoRetrofit(@Named("cache") OkHttpClient okHttpClient){
return new TaobaoRetrofit(okHttpClient);
}
}
這裡因為有時候的http請求是針對多個地址的,所以我又封裝了兩個提供retrofit的類
TaobaoRetrofit
private static final String BASE_URL = "http://ip.taobao.com/";
private static Retrofit retrofit;
public TaobaoRetrofit(OkHttpClient okHttpClient) {
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.client(okHttpClient)
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.addConverterFactory(FastJsonConverterFactory.create())
.build();
}
public Retrofit getRetrofit() {
return retrofit;
}
LocalRetrofit
public class LocalRetrofit {
private static final String BASE_URL = "http://xxxxxx.xxx.xxxx/";
private static Retrofit retrofit;
public LocalRetrofit(OkHttpClient okHttpClient) {
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.client(okHttpClient)
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.addConverterFactory(FastJsonConverterFactory.create())
.build();
}
public Retrofit getRetrofit() {
return retrofit;
}
}
我希望網路請求中的Service對呼叫者是黑盒,呼叫者只需要知道呼叫哪個Service即可,建立過程不需要了解,所以又提供了ServiceModule
LocalServiceModule
@Module
public class LocalServiceModule {
@Singleton
@Provides
public UserService providerUserService(LocalRetrofit retrofit){
return retrofit.getRetrofit().create(UserService.class);
}
}
TaobaoIPLocationServiceModule
@Module
public class TaobaoIPLocationServiceModule {
@Singleton
@Provides
public TaobaoIPLocationService proidverIPLocationServiceModule(TaobaoRetrofit taoBaoRetrofitClient) {
return taoBaoRetrofitClient.getRetrofit().create(TaobaoIPLocationService.class);
}
}
單例的module 建立完畢!
Step 3 建立AppCompontent(個人感覺類於Spring的Context類)
@Singleton
//關鍵程式碼在這!component會把Module裡的提供的物件,註冊到容器裡
@Component(modules = {AppModule.class,
OkhttpModule.class,
RetrofitModule.class,
LocalServiceModule.class,
TaobaoIPLocationServiceModule.class})
public interface AppComponent {
//SubComponent 繼承當前Component
MainComponent addSub(MainModule mainModule);
}
Step 4 make Project
點選綠色下箭頭按鈕,make project。
Dagger2會自動生成Dagger字首的Dagger注入工具。
Step5 改造Application
public class DaggerApplication extends Application {
private static AppComponent appComponent;
@Override
public void onCreate() {
super.onCreate();
}
public static DaggerApplication get(Context context) {
return (DaggerApplication) context.getApplicationContext();
}
private void setupApplicationComponent() {
//Dagger開頭的注入類DaggerAppComponent
appComponent = DaggerAppComponent.builder()
//此時appModule方法是過時方法,因為我們沒有使用到任何一個module中提供的物件
.appModule(new AppModule(this))
.build();
}
//獲取AppComponent 以便於SubComponent繼承
public AppComponent getAppComponent() {
if(appComponent == null){
this.setupApplicationComponent();
}
return appComponent;
}
}
正片 MVP
提供了那麼多物件,到底怎麼用???
下面是真正的正片、正片、正片。
[碼字。。。。好累 (?_?)]
Step1 建立Activity的module
因為Activity的類的構造器,我們無法加入@Inject註解,所以必須提供Module才能提供View介面的例項化物件。
@Module
public class MainModule {
private MainContract.View view;
//構造方法傳遞View 介面的例項化物件
public MainModule(MainContract.View view){
this.view = view;
}
//在DI容器中提供View介面的例項化物件
@ActivityScope
@Provides
public MainContract.View providerView(){
return view;
}
}
Step2 建立Activity的Conponent
//生命週期管理
@ActivityScope
//很重要!這個Component應該是AppComponent的子Component,所以要使用這個註解
//不使用@Component註解的Dependents屬性是因為希望能統一管理子Component
@Subcomponent(modules = MainModule.class)
public interface MainComponent {
//方法引數中,只能傳遞被注入物件!要在哪個類中注入,寫哪個類,注入到父類沒用!
void inject(MainActivity mainActivity);
}
Step3 改造AppComponent(重要)
在AppComponent類中新增一行
MainComponent addSub(MainModule mainModule);
程式碼如下
@Singleton
@Component(modules = {AppModule.class,
OkhttpModule.class,
RetrofitModule.class,
LocalServiceModule.class,
TaobaoIPLocationServiceModule.class})
public interface AppComponent {
MainComponent addSub(MainModule mainModule);
}
Step3 MVP模式類中使用Dagger2
1、建立MainContract(不需要改造)
public interface MainContract {
interface View{
void showLocationInfo(TaobaoIPLocationInfo taobaoIPLocationInfo);
void showError(String message);
}
interface presenter{
}
}
2、建立Presenter(注意@Inject)
public class MainPresenter implements MainContract.presenter {
private final MainContract.View view;
private final SharedPreferences sharedPreferences;
private final TaobaoIPLocationService locationService;
private final UserService userService;
//此處關鍵,用來提供Presenter 的例項化物件
@Inject
public MainPresenter(MainContract.View view,
//注入Default SharedPreferences
@Named("default") SharedPreferences sharedPreferences,
TaobaoIPLocationService locationService,
UserService userService) {
this.view = view;
this.sharedPreferences = sharedPreferences;
this.locationService = locationService;
this.userService = userService;
}
//IP定位測試
public void main(){
locationService.getIPInfo("myip")
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<TaobaoIPLocationInfo>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
view.showError(e.getMessage());
}
@Override
public void onNext(TaobaoIPLocationInfo taobaoIPLocationInfo) {
view.showLocationInfo(taobaoIPLocationInfo);
}
});
}
}
3、Activity(需要關注addSub方法、Inject方法)
public class MainActivity extends AppCompatActivity implements MainContract.View{
//注入presenter 物件
@Inject
MainPresenter mainPresenter;
private TextView city;
private TextView cityCode;
private TextView ip;
private TextView isp;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
setupActivityComponent();
bindView();
mainPresenter.main();
}
private void bindView() {
city = (TextView) findViewById(R.id.city);
cityCode = (TextView) findViewById(R.id.cityCode);
ip = (TextView) findViewById(R.id.ip);
isp = (TextView) findViewById(R.id.isp);
}
/**
* 初始化屬於自己Activity的Component物件
* 本例將MainComponent新增成為AppComponent的子Component
*/
private void setupActivityComponent() {
DaggerApplication.get(this)
.getAppComponent()
//將AppComponent繼承然後轉換成MainComponent
//MainModule的構造器中傳遞的是View介面的例項化物件
.addSub(new MainModule(this))
//注入到當前類中
.inject(this);
}
/**
* MVP Presenter 中的回撥
* @param taobaoIPLocationInfo IP定位後的返回資訊
*/
@Override
public void showLocationInfo(TaobaoIPLocationInfo taobaoIPLocationInfo) {
city.setText(String.format("定位城市:%s", taobaoIPLocationInfo.getData().getCity()));
cityCode.setText(String.format("定位城市程式碼:%s", taobaoIPLocationInfo.getData().getCity_id()));
ip.setText(String.format("地位地區IP:%s", taobaoIPLocationInfo.getData().getIp()));
isp.setText(String.format("isp服務提供商:%s", taobaoIPLocationInfo.getData().getIsp()));
}
/**
* MVP Presenter 中的回撥
*/
@Override
public void showError(String message) {
Toast.makeText(this,message,Toast.LENGTH_LONG).show();
}
}