一、概述
上一篇主要介紹了dagger的基本使用方法,這篇則分享一下我之前基於mvp架構的項目中的簡單應用,也作爲一個記錄;最近才瞭解到mvpclean,把dagger的依賴注入部分放在presentation層比較合適。
二、dagger架構思路
由於還不算很熟悉dagger,所以只是將網絡請求的httpclient以及數據庫採用依賴注入的方式,並沒有涉及到一些對象的作用域甚至包含、繼承等更復雜的組織形式,適合一些簡單的小型項目。
- 提供一個AppComponent,暴露出全局Context、數據中心、http請求幫助對象、數據庫幫助對象以及SharedPreferences幫助對象
- 提供一個ActivityComponent,依賴AppComponent,獲取暴露的依賴實例,同時暴露出activity對象實例
- 提供一個FragmentComponent,依賴AppComponent,獲取暴露的依賴實例,也暴露activity對象實例
注意:此處fragment直接依賴於AppComponent而並沒有採取上篇所述的依賴於ActivityComponent,並提供AppModule的形式,複雜的大型應用應考慮差異性,此處目的只是拿到一些全局的依賴實例
三、環境與版本
當時開發時沒有采取最新的版本,所以可能和現在有所差別,使用時應靈活調整。
Project的build.gradle如下:
dependencies {
classpath 'com.android.tools.build:gradle:2.3.3'
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
classpath "io.realm:realm-gradle-plugin:3.1.1"
}
module的gradle文件:
apply plugin: 'com.android.application'
apply plugin: 'android-apt'
apply plugin: 'realm-android'
android {
compileSdkVersion rootProject.ext.android.compileSdkVersion
buildToolsVersion rootProject.ext.android.buildToolsVersion
defaultConfig {
……
// realm所需,應該是跟.so文件有關
ndk {
abiFilters = ["armeabi"]
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
productFlavors {
google {
manifestPlaceholders = [UMENG_CHANNEL_VALUE: "google"]
}
}
// realm所需
packagingOptions {
exclude "lib/mips/librealm-jni.so"
exclude "lib/x86/librealm-jni.so"
exclude "lib/x86_64/librealm-jni.so"
}
// realm所需,請根據需要選擇
splits {
abi {
enable true
reset()
include 'armeabi', 'armeabi-v7a', 'arm64-v8a'
universalApk true
}
}
}
dependencies {
……
//network
compile "com.google.code.gson:gson:2.7"
compile "com.squareup.retrofit2:retrofit:2.2.0"
compile "com.squareup.retrofit2:converter-gson:2.2.0"
compile "com.squareup.retrofit2:adapter-rxjava2:2.2.0"
compile "com.squareup.okhttp3:okhttp:3.6.0"
compile "com.squareup.okhttp3:logging-interceptor:3.6.0"
compile "com.github.bumptech.glide:glide:3.7.0"
compile "com.github.bumptech.glide:okhttp3-integration:1.4.0@aar"
compile "org.jsoup:jsoup:1.10.1"
//di
compile "com.google.dagger:dagger:2.0.2"
apt "com.google.dagger:dagger-compiler:2.0.2"
provided "org.glassfish:javax.annotation:10.0-b28"
}
realm的配置也並沒有弄清楚,個人建議可以使用room或者greendao在配置方面更簡單。編譯有問題的話請百度下realm相關。
四、dagger編碼—AppComponent相關
AppComponent暴露全局的依賴實例,把依賴分爲兩塊,AppModule負責暴露的依賴實例,HttpModule負責網絡請求幫助類的依賴實例。
HttpModule代碼如下:
@Module
public class HttpModule {
@Singleton
@Provides
GsonBuilder provideGsonBuilder() {
return new GsonBuilder();
}
@Provides
@Singleton
public Gson getGson(GsonBuilder builder) {
return builder
.setDateFormat("yyyy-MM-dd HH:mm:ss")
.serializeNulls()
.setLenient()
.setFieldNamingStrategy(new AnnotateNaming())
.create();
}
@Singleton
@Provides
OkHttpClient.Builder provideOkHttpBuilder() {
return new OkHttpClient.Builder();
}
@Singleton
@Provides
OkHttpClient provideOkHttpClient(OkHttpClient.Builder builder) {
builder.connectTimeout(10, TimeUnit.SECONDS);
builder.readTimeout(20, TimeUnit.SECONDS);
builder.writeTimeout(20, TimeUnit.SECONDS);
builder.retryOnConnectionFailure(true);
builder.addNetworkInterceptor(new HttpHeadInterceptor());
builder.addInterceptor(getInterceptor());
builder.cache(getCache());
return builder.build();
}
@Singleton
@Provides
Retrofit.Builder provideRetrofitBuilder() {
return new Retrofit.Builder();
}
@Singleton
@Provides
@DataUrl // 非圖像數據Retrofit
Retrofit provideDataRetrofit(Retrofit.Builder builder, OkHttpClient client, Gson gson) {
return createRetrofit(builder, client, gson, DataApis.HOST);
}
@Provides
@Singleton
DataApis provideDataService(@DataUrl Retrofit retrofit) {
return retrofit.create(DataApis.class);
}
@Singleton
@Provides
@ImageUrl // 圖像數據Retrofit
Retrofit provideImageRetrofit(Retrofit.Builder builder, OkHttpClient client, Gson gson) {
return createRetrofit(builder, client, gson, ImageApis.HOST);
}
@Provides
@Singleton
ImageApis provideImageService(@ImageUrl Retrofit retrofit) {
return retrofit.create(ImageApis.class);
}
// 硬盤緩存
private Cache getCache() {
File cacheFile = new File(Constants.PATH_CACHE); // 自定義路徑
Cache cache = new Cache(cacheFile, 1024 * 1024 * 50);
return cache;
}
// http請求報文攔截器
private HttpLoggingInterceptor getInterceptor() {
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
if (BuildConfig.DEBUG) {
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY); // 測試
} else {
interceptor.setLevel(HttpLoggingInterceptor.Level.NONE); // 打包
}
return interceptor;
}
// http緩存攔截器
class HttpHeadInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
if (!SystemUtil.isNetworkConnected()) {
request = request.newBuilder()
.cacheControl(CacheControl.FORCE_CACHE)
.build();
}
Response response = chain.proceed(request);
if (SystemUtil.isNetworkConnected()) {
int maxAge = 0;
// 有網絡時, 不緩存, 最大保存時長爲0
response.newBuilder()
.header("Cache-Control", "public, max-age=" + maxAge)
.removeHeader("Pragma")
.build();
} else {
// 無網絡時,設置超時爲4周
int maxStale = 60 * 60 * 24 * 28;
response.newBuilder()
.header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale)
.removeHeader("Pragma")
.build();
}
return response;
}
}
// 生成Retrofit對象
private Retrofit createRetrofit(Retrofit.Builder builder, OkHttpClient client, Gson gson, String url) {
return builder
.baseUrl(url)
.client(client)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
}
}
@DataUrl限定符如下:
@Qualifier
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface DataUrl {
}
@ImageUrl限定符如下
@Qualifier
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface ImageUrl {
}
DataApis數據接口文件:
public interface DataApis {
// String HOST = "false"; // 測試地址
String HOST = "true"; // 生產地址
@Headers({
"Content-Type: application/json"
})
@POST("/api")
Flowable<Response<HttpResponse<User>>> login(@Body HttpRequest<UserRequestBody> user);
}
ImageApis圖片接口文件:
public interface ImageApis {
// String HOST = "false"; // 測試地址
String HOST = "true"; // 生產地址
@POST("/upload.go")
Flowable<Response<UploadImageResponse>> uploadCollectImage(@Body RequestBody body);
}
通過數據中心DataManager來管理網絡請求、數據庫、鍵值對數據(接口的方式)
網絡請求幫助類:
public interface HttpHelper {
}
public class RetrofitHelper implements HttpHelper {
private DataApis mDataApis;
private ImageApis mImageApis;
// 參數注意,必須是在某個module中標註過可以提供的
@Inject
public RetrofitHelper(DataApis dataApis, ImageApis imageApis) {
this.mDataApis = dataApis;
this.mImageApis = imageApis;
}
}
數據庫幫助類:
public interface DBHelper {
}
public class RealmHelper implements DBHelper {
private static final String DB_NAME = "myRealm.realm";
private Realm mRealm;
@Inject
public RealmHelper() {
mRealm = Realm.getInstance(new RealmConfiguration.Builder()
.deleteRealmIfMigrationNeeded()
.name(DB_NAME)
.build());
}
}
鍵值對幫助類:
public interface PreferencesHelper {
}
public class ImplPreferencesHelper implements PreferencesHelper {
private static final String SHAREDPREFERENCES_NAME = "my_sp";
private final SharedPreferences mSPrefs;
@Inject
public ImplPreferencesHelper() {
mSPrefs = App.getInstance().getSharedPreferences(SHAREDPREFERENCES_NAME, Context.MODE_PRIVATE);
}
}
數據中心DataManager:
public class DataManager implements HttpHelper, DBHelper, PreferencesHelper {
HttpHelper mHttpHelper;
DBHelper mDbHelper;
PreferencesHelper mPreferencesHelper;
public DataManager(HttpHelper httpHelper, DBHelper dbHelper, PreferencesHelper preferencesHelper) {
mHttpHelper = httpHelper;
mDbHelper = dbHelper;
mPreferencesHelper = preferencesHelper;
}
}
接下來就可以編寫AppModule了:
@Module
public class AppModule {
private final App application;
public AppModule(App application) {
this.application = application;
}
@Provides
@Singleton
App provideApplicationContext() {
return application;
}
@Provides
@Singleton
HttpHelper provideHttpHelper(RetrofitHelper retrofitHelper) {
return retrofitHelper;
}
@Provides
@Singleton
DBHelper provideDBHelper(RealmHelper realmHelper) {
return realmHelper;
}
@Provides
@Singleton
PreferencesHelper providePreferencesHelper(ImplPreferencesHelper implPreferencesHelper) {
return implPreferencesHelper;
}
@Provides
@Singleton
DataManager provideDataManager(HttpHelper httpHelper, DBHelper DBHelper, PreferencesHelper preferencesHelper) {
return new DataManager(httpHelper, DBHelper, preferencesHelper);
}
}
最後在AppComponent暴露出依賴實例:
@Singleton
@Component(modules = {AppModule.class, HttpModule.class})
public interface AppComponent {
App getContext(); // 提供App的Context
DataManager getDataManager(); // 數據中心
RetrofitHelper retrofitHelper(); // 提供http幫助類
RealmHelper realmHelper(); // 提供數據庫幫助類
ImplPreferencesHelper preferencesHelper(); // 提供sp幫助類
}
最後是在自定義application中依賴注入AppComponent:
public class App extends Application {
private static App instance;
public static AppComponent appComponent;
public static synchronized App getInstance() {
return instance;
}
@Override
public void onCreate() {
super.onCreate();
instance = this;
if (appComponent == null) {
appComponent = DaggerAppComponent.builder()
.appModule(new AppModule(instance))
.httpModule(new HttpModule())
.build();
}
}
public static AppComponent getAppComponent() {
return appComponent;
}
}
注意:
1.在AppComponent中暴露實例後,纔可供後續依賴的component使用
2.HttpModule上採用@DataUrl和@ImageUrl是爲了處理接口地址不同時Retrofit依賴問題
3.AppModule中的provideHttpHelper的參數實例化時構造函數需要的參數是從HttpModule中獲取的,這也就是component層級組合modules的作用
五、dagger編碼—ActivityComponent相關
ActivityComponent依賴於AppComponent,獲取暴露的實例
ActivityModule如下:
@Module
public class ActivityModule {
private Activity mActivity;
public ActivityModule(Activity activity) {
this.mActivity = activity;
}
@Provides
@ActivityScope
public Activity provideActivity() {
return mActivity;
}
}
@ActivityScope作用域:
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface ActivityScope {
}
ActivityComponent如下:
@ActivityScope
@Component(dependencies = AppComponent.class, modules = ActivityModule.class)
public interface ActivityComponent {
Activity getActivity();
void inject(MainActivity activity);
}
此後在ActivityComponent中註冊的activity在component依賴注入後就可以使用AppComponent暴露的實例:
public abstract class BaseActivity<T extends BasePresenter> extends AppCompatActivity implements BaseView {
@Inject
protected T mPresenter;
protected ActivityComponent getActivityComponent() {
return DaggerActivityComponent.builder()
.appComponent(App.getAppComponent())
.activityModule(getActivityModule())
.build();
}
protected ActivityModule getActivityModule(){
return new ActivityModule(this);
}
getActivityComponent().inject(this);
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
……
initInject();
}
protected abstract void initInject();
}
public class MainActivity extends BaseActivity<MainActivityPresenter> implements MainActivityContract.View{
@Override
protected void initInject() {
getActivityComponent().inject(this);
}
}
可以在基類的onCreate中的抽象方法中依賴注入,繼承基類的子類實現改抽象方法即可,Fragment也類似就不多做展示。
另外Presenter提供的依賴如下:
public class MainActivityPresenter extends RxPresenter<MainActivityContract.View> implements MainActivityContract.Presenter {
private DataManager mDataManager;
@Inject
public MainActivityPresenter(DataManager mDataManager) {
this.mDataManager = mDataManager;
}
}
該處構造函數提供了BaseActivity中Presenter的依賴實例,而參數則是從ActivityComponent依賴的AppComponent暴露的實例獲取
六、總結
以上就是我之前mvp架構中dagger的使用,還有很多不合理之處需改善,打算學習完MvpClean和架構組件後重構代碼,有錯誤和建議歡迎提出。