看到如此多的MVP+Dagger2+Retrofit+Rxjava項目,輕松拿star,心動了嗎?

原文地址: http://www.lxweimin.com/p/4bbecd0bb027

Logo

概述

MVPArms 是一個整合了大量主流開源項目的 Android MVP 快速搭建框架,其中包含 Dagger2 , Retrofit , Rxjava 以及 RxLifecycle , RxCacheRx 系三方庫,并且提供 UI 自適應方案,本框架將它們結合起來,并全部使用 Dagger2
管理并提供給開發者使用,使用本框架開發你的項目就意味著你已經擁有一個 MVP + Dagger2 + Retrofit + Rxjava 項目

MVPArt 是一個新的 MVP 架構,適合中小型項目,旨在解決傳統 MVP 類和接口太多,并且 PresenterView 通過接口通信過于繁瑣,重用 Presenter 代價太大等問題

通知

擴展項目, 了解一下:

特性

  • 通用框架, 適合所有類型的項目, 支持大型項目的開發, 兼容組件化開發, 可作為組件化的 Base

  • Base 基類(BaseActivity, BaseFragment, BaseApplication ...)

  • MVP 基類(IModel, IVIew, IPresenter ...)

  • 框架高度可自定義化 (ConfigModule), 可在不修改框架源碼的情況下對 Retoift, Okhttp, RxCache, Gson 等框架的特有屬性進行自定義化配置, 可在不修改框架源碼的情況下向 BaseApplication, BaseActivity, BaseFragment 的對應生命周期中插入任意代碼, 并且框架獨有的 ConfigModule 配置類, 可在不修改框架源碼的情況下為框架輕松擴展任何新增功能

  • 獨創的 RxLifeCycle 應用方式, 可在不繼承 RxLifeCycle 提供的 ActivityFragment 的情況下, 正常使用 RxLifeCycle 的所有功能, 且使用方式不變

  • 獨創的建造者模式 Module (GlobalConfigModule), 可實現使用 Dagger2 向框架任意位置注入自定義參數, 可輕松擴展任意自定義參數

  • 全局使用 Dagger2 管理 (將所有模塊使用 Dagger2 連接起來, 絕不是簡單的使用)

  • 全局監聽整個 App 所有 Activity 以及 Fragment 的生命周期 (包括三方庫), 并可向其生命周期內插入任意代碼

  • 全局監聽 Http Request(請求參數, Headers ...), Response (服務器返回的結果, Headers, 耗時 ...)等信息(包括 Glide 的請求), 可解析 json 后根據狀態碼做相應的全局操作以及數據加密, Cookie 管理等操作

  • 全局管理所有 Activity (包括三方庫的 Activity), 可實現在整個 App 任意位置, 退出所有 Activity, 以及拿到前臺 Activity 做相應的操作(如您可以在 App 任何位置做彈出 Dialog 的操作)

  • 全局 Rxjava 錯誤處理, 錯誤后自動重試, 捕捉整個應用的所有錯誤

  • 全局 UI 自適應

  • 圖片加載類 ImageLoader 使用策略模式和建造者模式, 輕松切換圖片加載框架, 方便功能擴展

  • 網絡請求日志打印封裝(提供解析后的服務器的請求信息和服務器的響應信息, 按可自定義的任意格式輸出打印日志, 內置一個漂亮的打印格式模板)

  • 框架內自有組件的緩存機制封裝(框架內可緩存內容的組件都提供有接口供外部開發者自定義緩存機制)

  • 代碼生成插件(MVPArms 全家桶一鍵生成所需要的所有類文件)

  • Demo 修改包名后就可以直接使用, 快速接入(老項目接入請按下面的步驟)

框架結構

Architecture

包結構

package

開發須知

  • 開發者需要具有一定的 Android 開發能力,以及自我解決問題的能力
  • 開發者必須有使用 Dagger2 , Rxjava , Retrofit 的經驗,沒使用過也必須了解,不然很難上手
  • 本框架為作者用業余時間維護,作者并沒有義務為開發者做任何事,使用時或提問時請保持對作者以及維護者起碼的 敬畏尊重

Libraries簡介

  1. Mvp 是 Google 官方出品的 Mvp 架構項目,含有多個不同的架構分支(此為 Dagger 分支).
  2. Dagger2 是 Google 根據 Square 的 Dagger1 出品的依賴注入框架,通過 Apt 編譯時生成代碼,性能優于使用運行時反射技術的依賴注入框架.
  3. RxJava 提供優雅的響應式 API 解決異步請求以及事件處理.
  4. RxAndroid 為 Android 提供響應式 API.
  5. Rxlifecycle,在 Android 上使用 RxJava 都知道的一個坑,就是生命周期的解除訂閱,這個框架通過綁定 Activity 和 Fragment 的生命周期完美解決該問題.
  6. RxCache 是使用注解,為 Retrofit 加入二級緩存 (內存,磁盤) 的緩存庫.
  7. RxErroHandlerRxJava 的錯誤處理庫,可在出現錯誤后重試.
  8. RxPermissions 用于處理 Android 運行時權限的響應式庫.
  9. Retrofit 是 Square 出品的網絡請求庫,極大的減少了 Http 請求的代碼和步驟.
  10. Okhttp 同樣 Square 出品,不多介紹,做 Android 的都應該知道.
  11. AndroidAutoSize 是今日頭條屏幕適配方案終極版,一個極低成本的 Android 屏幕適配方案,該庫沒有引入到 Arms,所以框架使用者可自由選擇屏幕適配方案.
  12. Gson 是 Google 官方的 Json Convert 框架.
  13. Butterknife 是 JakeWharton 大神出品的 View 注入框架.
  14. AndroidEventBus 是一個輕量級的 EventBus,該庫沒有引入到 Arms,所以框架使用者可自由選擇 EventBus.
  15. Timber 是 JakeWharton 大神出品的 Log 框架容器,內部代碼極少,但是思想非常不錯.
  16. Glide 是本框架默認封裝到擴展庫 arms-imageloader-glide 中的圖片加載庫,可參照著 Wiki 更改為其他的圖片加載庫,Glide 的 API 和 Picasso 差不多,緩存機制比 Picasso 復雜,速度快,適合處理大型圖片流,支持 gif 圖片,Fresco 太大了!在 5.0 以下優勢很大,5.0 以上系統默認使用的內存管理和 Fresco 類似.
  17. LeakCanary 是 Square 出品的專門用來檢測 AndroidJava 的內存泄漏,并通過通知欄提示內存泄漏信息.

<a name="1"></a>

1 開發準備

本框架建議直接使用 Gradle 遠程依賴, 框架已經提供了很多用于擴展的接口, 足以滿足日常需求, 如非必須, 請不要使用依賴 Module 的方式以及修改框架源碼

<a name="1.1"></a>

1.1 導入框架

implementation 'me.jessyan:arms:2.5.0'

---------------------- 以下是擴展庫 ----------------------

//想使用 Glide 請依賴 arms-imageloader-glide 擴展庫, 使用方式請看 #4.1
implementation 'me.jessyan:arms-imageloader-glide:2.5.0' 

//想使用 AndroidAutoLayout 請依賴 arms-autolayout 擴展庫, 使用方式請查看 #4.2
implementation 'me.jessyan:arms-autolayout:2.5.0' 

<a name="1.2"></a>

1.2 引用 config.gradle

本框架提供一個含有大量第三方庫的 config.gradle 文件 (里面的所有第三方庫并不會全部被引入到項目中, 只是作為變量方便項目中多個位置進行引用, 特別適用于多 Module 的項目), 用于第三方庫的版本管理, 將 config.gradle 復制進根目錄, 并在項目的頂級 build.gradle 中引用它

apply from: "config.gradle" //這里表示引用config.gradle文件
buildscript {
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:x.y.z'
    }
}

allprojects {
    repositories {
        google() //AndroidStudio v3.0 可以使用 google() 替代 maven { url "https://maven.google.com" }
        jcenter()
        maven { url "https://jitpack.io" }//注意!!! RxCache 是托管于 jitpack 倉庫的, 如果沒有這一段代碼將永遠依賴不了 RxCache
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

<a name="1.2.1"></a>

1.2.1 使用 config.gradle

因為在頂級 build.gradle 中引用了 config.gradle, 所以在整個項目的所有 build.gradle 中都可以使用 rootProject.xxx 來引用 config.gradle 中聲明的內容

dependencies {
    testImplementation rootProject.ext.dependencies["junit"]
    implementation rootProject.ext.dependencies["support-v4"]
    implementation rootProject.ext.dependencies["gson"]
    implementation rootProject.ext.dependencies["appcompat-v7"]
    implementation rootProject.ext.dependencies["cardview-v7"]
    implementation rootProject.ext.dependencies["autolayout"]
    implementation rootProject.ext.dependencies["butterknife"]
    implementation rootProject.ext.dependencies["androideventbus"]
    }

也可以使用 config.gradle 來管理一些項目的基本信息, 這樣有多個 module 也可以直接使用同一個值

android {
    compileSdkVersion rootProject.ext.android["compileSdkVersion"]
    buildToolsVersion rootProject.ext.android["buildToolsVersion"]
    useLibrary 'org.apache.http.legacy'

    defaultConfig {
        minSdkVersion rootProject.ext.android["minSdkVersion"]
        targetSdkVersion rootProject.ext.android["targetSdkVersion"]
        versionCode rootProject.ext.android["versionCode"]
        versionName rootProject.ext.android["versionName"]
    }
}

<a name="1.3"></a>

1.3 配置 build.gradle

<a name="1.3.1"></a>

1.3.1 依賴 Dagger2

本框架全部使用 Dagger2 管理, 所以必須依賴 Dagger2, 找到 appbuild.gradle, 加入如下代碼

dependencies {
    annotationProcessor rootProject.ext.dependencies["butterknife-compiler"] //Butterknife 插件, 很多人因為沒加這個而報錯, 切記!!!
    annotationProcessor rootProject.ext.dependencies["dagger2-compiler"]//依賴插件
}

<a name="1.3.2"></a>

1.3.2 使用 Lambda

本框架的 Demo, 默認使用 Lambda, 如您不想使用 Lambda 或使用 AndroidStudio v3.0 (兼容 Java8, 默認支持 Lambda, 但需手動指定 targetCompatibility), 請忽略以下的配置

  • 在項目根目錄的頂級 build.gradle 中依賴 Lambda 插件
buildscript {
    dependencies {
        classpath 'com.android.tools.build:gradle:x.y.z'
        //lambda
        classpath 'me.tatarka:gradle-retrolambda:3.6.0'
    }
}
  • appbuild.gradle 中引入 Lambda 插件
apply plugin: 'me.tatarka.retrolambda'

android {
    compileOptions {
        //就算您使用 AndroidStuido v3.0, 也需要配置以下參數
        targetCompatibility JavaVersion.VERSION_1_8
        sourceCompatibility JavaVersion.VERSION_1_8
    }
}

<a name="1.4"></a>

1.4 配置 AndroidManifest

<a name="1.4.1"></a>

1.4.1 添加權限

    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>

<a name="1.4.2"></a>

1.4.2 指定 Application

本框架想要正常運行需要使用框架提供的 BaseApplication, 當然您也可以自定義一個 Application 繼承于它, 也可以不用繼承, 直接將 BaseApplication 的代碼復制到您自定義的 Application 中 (里面只有幾行代碼), 但是我并不推薦您使用后面的兩種方式, 因為本框架已經向開發者提供了 ConfigModule#injectAppLifecycle 方法, 可以在運行時動態的向 BaseApplication 的任意生命周期中插入任意代碼, 這樣即使您不需要自定義 Application, 也可以做到初始化自己的業務

<application
        android:name="com.jess.arms.base.BaseApplication">
</application>

<a name="1.4.3"></a>

1.4.3 配置 AndroidAutoSize

使用前請依賴 AndroidAutoSize

dependencies {
    implementation 'me.jessyan:autosize:x.y.z'
}

使用 AndroidAutoSize 屏幕適配框架必須配置 meta-data 屬性, 即設計圖的寬高, 詳情請參考 AndroidAutoSize, 本框架并不強制您使用 AndroidAutoSize, 如果您不想使用 AndroidAutoSize, 就不要依賴 AndroidAutoSize 也不要配置下面的 meta-data 屬性

 <!-- 只要依賴 AutoSize 就必須填寫設計圖尺寸, 否則報錯, 不想使用 AutoSize 就不要依賴 AutoSize 只要填寫完設計圖的尺寸, AutoSize 就會自動啟動, 以下 dp 尺寸是根據公式 px / (dpi / 160) 求出, 運算時使用測試機的 dpi 即可, AndroidAutoSize 的詳細介紹請看這里 https://juejin.im/post/5bce688e6fb9a05cf715d1c2 -->
        <meta-data
            android:name="design_width_in_dp"
            android:value="360"/>
        <meta-data
            android:name="design_height_in_dp"
            android:value="640"/>

如果您想使用 鴻神AndroidAutoLayout, 請看 arms-autolayout

<a name="1.4.4"></a>

1.4.4 配置框架自定義屬性

本框架使用和 Glide v3.0 相同的方式來配置自定義屬性, 需要在 AndroidManifest 中聲明它, 詳情

        <!--arms配置-->
        <meta-data
            android:name="me.jessyan.mvparms.demo.app.GlobalConfiguration"
            android:value="ConfigModule"/>

如果您想使用 Glide, 請看 arms-imageloader-glide

<a name="1.5"></a>

1.5 混淆

由于本框架依賴大量三方庫, 所以已經在 arms Module 下的 proguard-rules.pro 中提供了本框架所依賴三方庫的所有規則, 如果想使用它, 請復制它替換 demo Module 中的 proguard-rules.pro (Demo 并不能直接使用這個 proguard-rules.pro 進行混淆), 混淆前務必注意將 Java Bean, 自定義組件 等必需的規則添加進 proguard-rules.pro

<a name="1.6"></a>

1.6 版本更新

  • 如通過 Gradle 遠程依賴本框架請忽略

如果您獲得本框架的方式是通過 clone 或者下載:

  1. 可直接通過命令行輸入 git pull origin master 拉取最新的版本并自動合并
  2. 如果您修改了包名還得執行命令 git rm --cache -r demo/src/main/java/me/jessyan/mvparms, 下次拉取時就不會拉取 Demo 的內容

如果您獲得本框架的方式是通過 fork 到自己賬號后, clone 或下載:

  1. 輸入命令 git remote add arms git@github.com:JessYanCoding/MVPArms.git 添加遠程倉庫, arms 是遠程倉庫的代號, 可自定義, 以后都可以通過這個代號對遠程倉庫進行操作
  2. git fetch arms 拉取遠程倉庫最新的代碼
  3. git merge arms/master --allow-unrelated-histories 合并遠程倉庫的代碼到當前分支
  4. 后面如果本框架有更新就只用重復 2、3步, --allow-unrelated-histories 只用在第一次合并時添加
  5. 如果您修改了包名還得執行命令 git rm --cache -r demo/src/main/java/me/jessyan/mvparms, 下次拉取時就不會拉取 Demo 的內容

<a name="2"></a>

2 快速開始

<a name="2.1"></a>

2.1 ConfigModule

ConfigModule 用來給框架配置各種自定義屬性和功能, 配合 GlobalConfigModule 使用, 功能非常強大

  • 新建一個類實現 ConfigModule 接口, 并在 AndroidManifest 中聲明
public class GlobalConfiguration implements ConfigModule {
    @Override
    public void applyOptions(Context context, GlobalConfigModule.Builder builder) {
     //使用 builder 可以為框架配置一些配置信息
     builder.baseurl(Api.APP_DOMAIN)
            .cacheFile(New File("cache"));
    }

    @Override
    public void injectAppLifecycle(Context context, List<AppLifecycles> lifecycles) {
     //向 Application的 生命周期中注入一些自定義邏輯
    }

    @Override
    public void injectActivityLifecycle(Context context, List<Application.ActivityLifecycleCallbacks> lifecycles) {
    //向 Activity 的生命周期中注入一些自定義邏輯
    }

    @Override
    public void injectFragmentLifecycle(Context context, List<FragmentManager.FragmentLifecycleCallbacks> lifecycles) {
    //向 Fragment 的生命周期中注入一些自定義邏輯
    }
}
<application>
     <!--arms 配置-->
     <meta-data
         android:name="me.jessyan.mvparms.demo.app.GlobalConfiguration"
         android:value="ConfigModule"/>
</application>

<a name="2.2"></a>

2.2 AppComponent

Application 的生命周期和 App 是一致的, 所以適合提供一些單例對象, 本框架使用 Dagger2 管理, 使用 AppComponent 來提供全局所有的單例對象, 現在在 App 的任何地方, 都可通過 BaseApplication (可自定義 Application, 實現 App 接口即可) 的 getAppComponent() (非靜態) 方法 (快捷方法 ArmsUtils.obtainAppComponentFromContext(context)), 拿到 AppComponent 里面聲明的所有單例對象

@Singleton
@Component(modules = {AppModule.class, ClientModule.class, GlobalConfigModule.class})
public interface AppComponent {
    Application Application();

    //用于管理網絡請求層,以及數據緩存層
    IRepositoryManager repositoryManager();

    //Rxjava錯誤處理管理類
    RxErrorHandler rxErrorHandler();

    OkHttpClient okHttpClient();

    //圖片管理器,用于加載圖片的管理類,默認使用glide,使用策略模式,可替換框架
    ImageLoader imageLoader();

    //gson
    Gson gson();

    //緩存文件根目錄(RxCache和Glide的的緩存都已經作為子文件夾在這個目錄里),應該將所有緩存放到這個根目錄里,便于管理和清理,可在GlobeConfigModule里配置
    File cacheFile();

    //用于管理所有activity
    AppManager appManager();

    void inject(AppDelegate delegate);
}

<a name="2.3"></a>

2.3 RepositoryManager

RepositoryManager 用來管理網絡請求層以及數據緩存層, 以后可能添加數據庫儲存層, 專門提供給 Model 層做數據處理, 在 v1.5 版本前是使用 ServiceManagerCacheManager 來管理, 在 v1.5 版本之后統一使用 RepositoryManager 來管理

  • 自行定義 Retrofit Service, 如下, 熟練 Retrofit 請忽略
public interface CommonService {

    String HEADER_API_VERSION = "Accept: application/vnd.github.v3+json";

    @Headers({HEADER_API_VERSION})
    @GET("/users")
    Observable<List<User>> getUsers(@Query("since") int lastIdQueried, @Query("per_page") int perPage);
}
  • 自行定義 RxCache Provider, 如下, 熟練 RxCache 請忽略
public interface CommonCache {

    @LifeCache(duration = 2, timeUnit = TimeUnit.MINUTES)
    Observable<Reply<List<User>>> getUsers(Observable<List<User>> oUsers, DynamicKey idLastUserQueried, EvictProvider evictProvider);

}
  • Model 中通過 RepositoryManager#obtainRetrofitService()RepositoryManager#obtainCacheService() 使用這些服務
public Observable<List<User>> getUsers(int lastIdQueried, boolean update) {
    //使用rxcache緩存,上拉刷新則不讀取緩存,加載更多讀取緩存
    return Observable.just(mRepositoryManager
            .obtainRetrofitService(UserService.class)
            .getUsers(lastIdQueried, USERS_PER_PAGE))
            .flatMap(new Function<Observable<List<User>>, ObservableSource<List<User>>>() {
                @Override
                public ObservableSource<List<User>> apply(@NonNull Observable<List<User>> listObservable) throws Exception {
                    return mRepositoryManager.obtainCacheService(CommonCache.class)
                            .getUsers(listObservable
                                    , new DynamicKey(lastIdQueried)
                                    , new EvictDynamicKey(update))
                            .map(listReply -> listReply.getData());
                }
            });
}

<a name="2.4"></a>

2.4 MVP 實戰

定義業務邏輯 MVP, 繼承 MVP 中各自的基類即可, 這里可以稍微粗力度的定義 MVP 類, 即無需每個頁面 (ActivityFragment) 都定義不同的 MVP 類, 可以按照相同的業務邏輯使用一組 MVP 類 (即使您使用 MVPArms 全家桶 一鍵生成這些文件, 也建議將以下基礎看完)

<a name="2.4.1"></a>

2.4.1 Contract

這里根據 Google 官方的 MVP 架構,可以在 Contract 中定義 MVP 類的接口, 便于管理, 本框架無需定義 Presenter 接口, 所以在 Contract 中只定義 ViewModel 的接口

public interface UserContract {
    //對于經常在日常開發中使用到的關于 UI 的方法可以定義到 IView 中, 如顯示隱藏進度條, 和顯示文字消息
    interface View extends IView {
        void setAdapter(DefaultAdapter adapter);
        void startLoadMore();
        void endLoadMore();
    }
    //Model 層定義接口, 外部只需關心 Model 返回的數據, 無需關心內部細節, 即是否使用緩存
    interface Model extends IModel{
        Observable<List<User>> getUsers(int lastIdQueried, boolean update);
    }
}

<a name="2.4.2"></a>

2.4.2 View

一般讓 ActivityFragment 實現 Contract 中定義的 View 接口, 供 Presenter 調用對應方法響應 UI, BaseActivity 默認注入 Presenter, 如想使用 Presenter, 必須將范型指定為 Presenter 的實現類 (雖然框架只可以指定一個范型, 但是可以自行生成并持有多個 Presenter, 達到復用的目的, 如何復用 Presenter?), 還需要實現 setupActivityComponent 來提供 Presenter 需要的 ComponentModule (如這個頁面邏輯簡單, 并不需要 Presenter, 那就不要指定范型, 也不要實現 setupActivityComponent 方法)

public class UserActivity extends BaseActivity<UserPresenter> implements UserContract.View {

    @Override
    protected void setupActivityComponent(AppComponent appComponent) {
        DaggerUserComponent
                .builder()
                .appComponent(appComponent)
                .userModule(new UserModule(this))
                .build()
                .inject(this);

    }

    @Override
    public int initView(Bundle savedInstanceState) {
        return R.layout.activity_user;
    }

    @Override
    protected void initData() {

    }
}

<a name="2.4.3"></a>

2.4.3 Model

Model 必須實現 ContractModel 接口, 并且繼承 BaseModel, 然后通過 IRepositoryManager 拿到需要的 ServiceCache, 為 Presenter 提供需要的數據 (是否使用緩存請自行選擇)

@ActivityScope
public class UserModel extends BaseModel implements UserContract.Model{
    
     @Inject
    public UserModel(IRepositoryManager repositoryManager) {
        super(repositoryManager);
    }
    
    @Override
    public Observable<List<User>> getUsers(int lastIdQueried, boolean update) {
        return mRepositoryManager.obtainRetrofitService(UserService.class)
                             .getUsers();
    }
}

<a name="2.4.4"></a>

2.4.4 Presenter

PresenterMVP 中的大部分作用是實現業務邏輯代碼, 從 Model 層獲取數據, 在調用 View 層顯示數據, 首先必須實現 BasePresenter, 并指定 ViewModel 的范型, 注意一定要指定 Contract 中定義的接口, Presenter 需要的 ViewModel, 都使用 Dagger2 來注入, 這樣即解藕又方便測試, 怎么注入?

@ActivityScope
public class UserPresenter extends BasePresenter<UserContract.Model, UserContract.View> {

    @Inject
    public UserPresenter(UserContract.Model model, UserContract.View rootView) {
        super(model, rootView);
    }
    //這里定義業務方法, 響應用戶的交互
    public void requestUsers(final boolean pullToRefresh) {
    }
}

<a name="2.4.5"></a>

2.4.5 MVP Module

這里的 Module 可以提供當前業務邏輯所對應的 ViewModel 接口 (Contract 中定義的接口) 的實現類, Model 需要 AppComponent 中提供的 RepositoryManager 來實現網絡請求和數據緩存, 所以需要通過 Component 依賴 AppComponent 來拿到這個對象

@Module
public class UserModule {
    private UserContract.View view;

    //構建UserModule時,將View的實現類傳進來,這樣就可以提供View的實現類給presenter
    public UserModule(UserContract.View view) {
        this.view = view;
    }
   
    @ActivityScope
    @Provides
    UserContract.View provideUserView(){
        return this.view;
    }

    @ActivityScope
    @Provides
    UserContract.Model provideUserModel(UserModel model){
        return model;
    }
}

<a name="2.4.6"></a>

2.4.6 MVP Component

這里需要注意的是此 Component 必須依賴 AppComponent, 這樣才能提供 Model 需要的 RepositoryManager, 提供 inject() 方法就能將 ModuleAppComponent 中提供的對象注入到對應的類中, inject() 方法中的參數不能是接口, 怎么注入?

@ActivityScope
@Component(modules = UserModule.class, dependencies = AppComponent.class)
public interface UserComponent {
    void inject(UserActivity activity);
}

<a name="2.4.7"></a>

2.4.7 Dagger Scope

在上面的代碼中 ActivityScope 大量的出現在 ModuleComponent 中, Dagger2 使用 Scope 限制每個 Module 中提供的對象的生命周期, Dagger2 默認只提供一個 @Singleton Scope 即單例, 本框架默認只提供 @ActvityScope@FragmentScope, 如有其他需求請自行實現, 在 ModuleComponent 中定義相同的 Scope 后, Module 中提供的對象的生命周期會和 Component 的生命周期進行綁定 (即在 Component 的生命周期內, 如需多次使用到 Moudle 中提供的對象, Dagger 只會調用一次帶有 @Provide 注解的方法, 得到此對象)

<a name="2.4.8"></a>

2.4.8 MVP 總結

  • 以后每個業務邏輯都需要重復構造這些類 (如何復用 Presenter?), 只是換個名字而已, 值得注意的是 MVP 剛開始使用時, 確實會覺得平白無故多了很多類, 非常的繁瑣麻煩, 但是等業務邏輯代碼越來越多時, 您會發現其中的好處, 邏輯清晰、解耦、便于團隊協作、易測試、易定位錯誤, 并且現在本框架也提供了 Template 自動生成代碼 來解決這個痛點, 讓開發者更加愉快的使用本框架

<a name="3"></a>

3 功能使用

<a name="3.1"></a>

3.1 App 全局配置信息(使用 Dagger 注入)

GlobalConfigModule 使用建造者模式將 App 的全局配置信息封裝進 Module (使用 Dagger 注入到需要配置信息的地方), 可以配置 CacheFileInterceptor 等, 甚至于 RetrofitOkhttpRxCache 都可以自定義配置, 因為使用的是建造者模式, 所以如您有其他配置信息需要使用 Dagger 注入, 直接就可以添加進 Builder, 并且不會影響到其他地方

public class GlobalConfiguration implements ConfigModule {
    @Override
    public void applyOptions(Context context, GlobalConfigModule.Builder builder) {
       //使用 builder 可以為框架配置一些配置信息
       builder.baseurl(Api.APP_DOMAIN)
              .gsonConfiguration((context12, gsonBuilder) -> {//這里可以自己自定義配置Gson的參數
                    gsonBuilder
                            .serializeNulls()//支持序列化null的參數
                            .enableComplexMapKeySerialization();//支持將序列化key為object的map,默認只能序列化key為string的map
                })
                .retrofitConfiguration((context1, retrofitBuilder) -> {//這里可以自己自定義配置Retrofit的參數,甚至您可以替換系統配置好的okhttp對象
//                    retrofitBuilder.addConverterFactory(FastJsonConverterFactory.create());//比如使用fastjson替代gson
                })
                .okhttpConfiguration((context1, okhttpBuilder) -> {//這里可以自己自定義配置Okhttp的參數
                    okhttpBuilder.writeTimeout(10, TimeUnit.SECONDS);
                }).rxCacheConfiguration((context1, rxCacheBuilder) -> {//這里可以自己自定義配置RxCache的參數
            rxCacheBuilder.useExpiredDataIfLoaderNotAvailable(true);
    }
}

<a name="3.2"></a>

3.2 全局捕捉 Http 請求和響應

全局配置類 中通過 GlobalConfigModule.Builder.globalHttpHandler() 方法傳入 GlobalHttpHandler

public class GlobalConfiguration implements ConfigModule {
    @Override
    public void applyOptions(Context context, GlobalConfigModule.Builder builder) {
        builder.globalHttpHandler(new GlobalHttpHandler() {

                    /**
                     * 這里可以先客戶端一步拿到每一次 Http 請求的結果, 可以先解析成 Json, 再做一些操作, 如檢測到 token 過期后
                     * 重新請求 token, 并重新執行請求
                     *
                     * @param httpResult 服務器返回的結果 (已被框架自動轉換為字符串)
                     * @param chain {@link okhttp3.Interceptor.Chain}
                     * @param response {@link Response}
                     * @return
                     */
                    @Override
                    public Response onHttpResultResponse(String httpResult, Interceptor.Chain chain, Response response) {
                        if (!TextUtils.isEmpty(httpResult) && RequestInterceptor.isJson(response.body().contentType())) {
                            try {
                                List<User> list = ArmsUtils.obtainAppComponentFromContext(context).gson().fromJson(httpResult, new TypeToken<List<User>>() {
                                }.getType());
                                User user = list.get(0);
                                Timber.w("Result ------> " + user.getLogin() + "    ||   Avatar_url------> " + user.getAvatarUrl());
                            } catch (Exception e) {
                                e.printStackTrace();
                                return response;
                            }
                        }
    
                        /* 這里如果發現 token 過期, 可以先請求最新的 token, 然后在拿新的 token 放入 Request 里去重新請求
                        注意在這個回調之前已經調用過 proceed(), 所以這里必須自己去建立網絡請求, 如使用 Okhttp 使用新的 Request 去請求
                        create a new request and modify it accordingly using the new token
                        Request newRequest = chain.request().newBuilder().header("token", newToken)
                                             .build();
    
                        retry the request
    
                        response.body().close();
                        如果使用 Okhttp 將新的請求, 請求成功后, 再將 Okhttp 返回的 Response return 出去即可
                        如果不需要返回新的結果, 則直接把參數 response 返回出去即可*/
                        return response;
                    }
    
                    /**
                     * 這里可以在請求服務器之前拿到 {@link Request}, 做一些操作比如給 {@link Request} 統一添加 token 或者 header 以及參數加密等操作
                     *
                     * @param chain {@link okhttp3.Interceptor.Chain}
                     * @param request {@link Request}
                     * @return
                     */
                    @Override
                    public Request onHttpRequestBefore(Interceptor.Chain chain, Request request) {
                        /* 如果需要在請求服務器之前做一些操作, 則重新構建一個做過操作的 Request 并 return, 如增加 Header、Params 等請求信息, 不做操作則直接返回參數 request
                        return chain.request().newBuilder().header("token", tokenId)
                                              .build(); */
                        return request;
                    }
                });
    }
}

<a name="3.3"></a>

3.3 全局錯誤處理及發生錯誤時重新執行

如果需要使用 Rxjava 的全局錯誤處理, 需要在 全局配置類 中通過 GlobalConfigModule.Builder.responseErroListener() 方法傳入 ResponseErroListener, 并在 Rxjava 每次訂閱調用 subscribe() 方法時, 傳入 ErrorHandleSubscriber 實例, ErrorHandleSubscriber 實例的創建需要傳入 AppComponent 中提供的 RxErrorHandler, ErrorHandleSubscriber 已經默認實現了 OnError 方法, 如想自定義可以重寫 OnError 方法

public class GlobalConfiguration implements ConfigModule {
    @Override
    public void applyOptions(Context context, GlobalConfigModule.Builder builder) {
        builder.responseErrorListener((context1, e) -> {
                    /* 用來提供處理所有錯誤的監聽, RxJava 訂閱時必須傳入 ErrorHandleSubscriber 實例, 此監聽才生效 */
                    Timber.w("------------>" + e.getMessage());
                    ArmsUtils.SnackbarText("net error");
                });
    }
}
  • Rxjava中使用
Observable
.just(1)
.retryWhen(new RetryWithDelay(3,2))//遇到錯誤時重試, 第一個參數為重試幾次, 第二個參數為重試的間隔, 單位為秒
.subscribe(new ErrorHandleSubscriber<Integer>(mErrorHandler) {
     @Override
     public void onNext(Integer Integer) {
 
     }
});

<a name="3.4"></a>

3.4 ImageLoader 如何擴展以及切換圖片請求框架

本框架默認使用 Glide 實現圖片加載功能, 使用 ImageLoader 為業務層提供統一的圖片請求接口, ImageLoader 使用策略模式和建造者模式, 可以動態切換圖片請求框架 (比如說切換成 Picasso), 并且加載圖片時傳入的參數也可以隨意擴展 ( loadImage() 方法在需要擴展參數時, 調用端也不需要改動, 全部通過 Builder 擴展, 比如您想讓內部的圖片加載框架, 清除緩存您只需要定義個 boolean 字段, 內部的圖片加載框架根據這個字段 if|else 做不同的操作, 其他操作同理, 當需要切換圖片請求框架或圖片請求框架升級后變更了 Api 時, 這里可以將影響范圍降到最低, 所以封裝 ImageLoader 是為了屏蔽這個風險)

  • 本框架默認提供了 GlideImageLoaderStrategyImageConfigImpl 簡單實現了圖片加載邏輯 (v2.5.0 版本后, 需要依賴 arms-imageloader-glide 擴展庫, 并自行通過 GlobalConfigModule.Builder#imageLoaderStrategy(new GlideImageLoaderStrategy); 完成注冊), 方便快速使用, 但開發中難免會遇到復雜的使用場景, 所以本框架推薦即使不切換圖片請求框架繼續使用 Glide, 也請按照下面的方法, 自行實現圖片加載策略, 因為默認實現的 GlideImageLoaderStrategy 是直接打包進框架的, 如果是遠程依賴, 當遇到滿足不了需求的情況, 您將不能擴展里面的邏輯

  • 使用 ImageLoader 必須傳入一個實現了 BaseImageLoaderStrategy 接口的圖片加載實現類從而實現動態切換, 所以首先要實現BaseImageLoaderStrategy, 實現時必須指定一個繼承自 ImageConfig 的實現類, 使用建造者模式, 可以儲存一些信息, 比如 URLImageViewPlaceholder 等, 可以不斷的擴展, 供圖片加載框架使用

public class PicassoImageLoaderStrategy implements BaseImageLoaderStrategy<PicassoImageConfig> {
     @Override
    public void loadImage(Context ctx, PicassoImageConfig config) {
                        Picasso.with(ctx)
                .load(config.getUrl())
                .into(config.getImageView());
    }
}
  • 實現 ImageConfig, 使用建造者模式 (創建新的 PicassoImageConfig 適用于新項目, 如果想重構之前的項目, 使用其他圖片加載框架, 為了避免影響之前的代碼, 請繼續使用默認提供的 ImageConfigImpl 或者您之前自行實現的 ImageConfig, 繼續擴展里面的屬性)
public class PicassoImageConfig extends ImageConfig {

    private PicassoImageConfig(Buidler builder) {
        this.url = builder.url;
        this.imageView = builder.imageView;
        this.placeholder = builder.placeholder;
        this.errorPic = builder.errorPic;
    }

    public static Buidler builder() {
        return new Buidler();
    }


    public static final class Buidler {
        private String url;
        private ImageView imageView;
        private int placeholder;
        protected int errorPic;

        private Buidler() {
        }

        public Buidler url(String url) {
            this.url = url;
            return this;
        }

        public Buidler placeholder(int placeholder) {
            this.placeholder = placeholder;
            return this;
        }

        public Buidler errorPic(int errorPic){
            this.errorPic = errorPic;
            return this;
        }

        public Buidler imagerView(ImageView imageView) {
            this.imageView = imageView;
            return this;
        }

        public PicassoImageConfig build() {
            if (url == null) throw new IllegalStateException("url is required");
            if (imageView == null) throw new IllegalStateException("imageview is required");
            return new PicassoImageConfig(this);
        }
    }
}
  • App 剛剛啟動初始化時, 通過 GlobalConfigModule 傳入上面擴展的 PicassoImageLoaderStrategy, 也可以在 App 運行期間通過 AppComponent 拿到 ImageLoader 對象后, 調用 setLoadImgStrategy(new PicassoImageLoaderStrategy) 替換之前的實現 (默認使用 Glide)
方法一: 通過GlobalConfigModule傳入
public class GlobalConfiguration implements ConfigModule {
    @Override
    public void applyOptions(Context context, GlobalConfigModule.Builder builder) {
        builder.imageLoaderStrategy(new PicassoImageLoaderStrategy);
    }
}

方法二: 拿到 AppComponent 中的 ImageLoader, 通過方法傳入
ArmsUtils.obtainAppComponentFromContext(context)
    .imageLoader()
    .setLoadImgStrategy(new PicassoImageLoaderStrategy());


使用方法:
ArmsUtils.obtainAppComponentFromContext(context)
    .imageLoader()
    .loadImage(mApplication, PicassoImageConfig
                .builder()
                .url(data.getAvatarUrl())
                .imagerView(mAvater)
                .build());

<a name="3.5"></a>

3.5 AndroidEventBus Tag

本框架使用 AndroidEventBus 實現事件總線 (v2.5.0 版本后, 框架內不再包含 AndroidEventBus, 框架使用者可自行在 AndroidEventBusEventBus 兩個庫中選擇, 想選擇哪個 EventBus 就依賴哪個 EventBus, 依賴后框架會自動檢測您依賴的 EventBus 并自動完成注冊), 此框架使用注解標記接受消息的方法, 注解可以指定 Tag, 便于索引, 統一將 Tag 的常量寫到 EventBusTag 接口中, 便于管理, 如果想在 ActivityFragmentServicePresenter 中使用 AndroidEventBus 請重寫 useEventBus() 方法, 返回 true 代表使用, 但框架已經默認返回 true, 為什么 MVPArms 使用的是 AndroidEventBus 而不是 greenrobotEventBus, 請看這里 我的回答

<a name="3.6"></a>

3.6 AutoLayout 組件

本框架使用 AndroidAutoLayout 框架 (v2.5.0 版本后, 框架不再包含 AndroidAutoLayout, 框架使用者可自行選擇屏幕適配方案, 推薦使用 AndroidAutoSize, 如想繼續使用 AndroidAutoLayout, 需要自行依賴 arms-autolayout 擴展庫, autolayout 子包中的 AutoLayout 組件已移至此擴展庫中), 實現控件自適應, 此框架要讓組件自適應, 必須讓它的父控件, 重新測量, 和重寫 LayoutParams, 而 AndroidAutoLayout 官方只默認提供了三個 ViewGroup, AutoRelativeLayoutAutoLinearLayoutAutoFrameLayout 實現了這些操作, 為了方便開發者使用, 本框架提供了一些常用的 AutoLayout 組件, 在框架的 widget 包下的 autolayout 子包中, 在 xml 中引用這些組件即可使子控件自適應, 并且還提供了一個
Template (在文件的最后部分) 用于生成自適應所需要的的 Auto 系列 View, 假如需要使 ScrollView 的子控件自適應, 使用此 Template 輸入 ScrollView, 即可自動生成 AutoScrollView, 在 xml 中引用即可

<a name="3.7"></a>

3.7 自定義 PopupWindow

框架提供一個使用建造者模式的自定義 PopupWindow 組件 CustomPopupWindow, 自己實現布局后就可以直接使用這個類實現 PopupWindow, 因為使用建造者模式, 所以可以隨意擴展自定義參數

<a name="3.8"></a>

3.8 快速實現 RecyclerView

本框架提供了 DefaultAdapterBaseHolder 基類快速實現 RecyclerView, 如不能實現您復雜的業務需求, 請自行引用其他三方庫實現

  • BaseHolder 默認初始化了 ButterKnifeAndroidAutoLayout, 繼承后不僅可以直接注入 View, 布局還可以自適應屏幕
  • RecyclerView 默認是不提供 Item 的點擊事件的, 使用 DefaultAdapter 調用 setOnItemClickListener 方法即可實現 Item 的點擊事件

<a name="3.9"></a>

3.9 權限管理(適配 Android 6.0 權限管理)

本框架使用 RxPermissions, 用于權限管理 (適配 Android 6.0), 并提供 PermissionUtil 工具類, 一行代碼即可實現權限請求, 適配 Android 6.0 權限管理詳解

PermissionUtil.launchCamera(new PermissionUtil.RequestPermission() {

            @Override
            public void onRequestPermissionSuccess() {
                launchCapture();//請求權限成功后做一些操作
            }

            @Override
            public void onRequestPermissionFailure(List<String> permissions) {
                mRootView.showMessage("Request permissions failure");
            }

            @Override
            public void onRequestPermissionFailureWithAskNeverAgain(List<String> permissions) {
                mRootView.showMessage("Need to go to the settings");
            }

        }, mRxPermissions, mErrorHandler);

<a name="3.10"></a>

3.10 Gradle 配置啟動 Debug 模式

在主 Module (app)build.gradle 中配置是否開啟打印 Log 或者是否使用 LeakCanary 等調試工具

  • build.gradle 中配置
android {

    buildTypes {
        debug {
            //這兩個變量是自定義的, 自己也可以自定義其他字段, 這些字段會默認生成到 **BuildConfig** 類中, 在 **App** 中可以根據這些字段執行一些操作
            buildConfigField "boolean", "LOG_DEBUG", "true"
            buildConfigField "boolean", "USE_CANARY", "true"
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }

        release {
            buildConfigField "boolean", "LOG_DEBUG", "false"
            buildConfigField "boolean", "USE_CANARY", "false"
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }  
  • 在代碼中使用 (比如在 App 初始化時做一些初始化的設置)
    @Override
    public void injectAppLifecycle(Context context, List<AppLifecycles> lifecycles) {
        lifecycles.add(new AppLifecycles() {
      
            @Override
            public void onCreate(Application application) {
                if (BuildConfig.LOG_DEBUG) {
                    //Timber 日志打印
            Timber.plant(new Timber.DebugTree());
        }
        if (BuildConfig.USE_CANARY) {
                    //leakCanary 內存泄露檢查
            LeakCanary.install(this);
        }
            }
        });
    }

<a name="3.11"></a>

3.11 AppManager (管理所有的 Activity)

AppManager 用于管理所有的 Activity, AppManager 內部持有一個含有所有存活的 Activity (未調用 onDestroy) 的 List 集合, 和一個當前在最前端的 Activity (未調用 onStop), AppManager 封裝有多種方法, 可以很方便的對它們進行任何操作, AppManager 是單例的, 可以通過靜態方法 AppManager.getAppManager() 直接拿到 AppManager 實例, 這樣我們可以在整個 App 的任何地方對任何 Activity 進行全局操作, 比如在 App 請求網絡超時時讓最前端的 Activity 顯示連接超時的交互頁面 (這個邏輯不用寫到當前請求的 Activity 里, 可以在一個單例類里做全局的統一操作, 因為可以隨時隨地通過 AppManager 拿到當前的任何 Activity)

<a name="3.12"></a>

3.12 AppDelegate(代理 Application 的生命周期)

AppDelegate 可以代理 Application 的生命周期, 在對應的生命周期, 執行對應的邏輯, 因為 Java 只能單繼承, 所以當遇到某些三方庫需要繼承于它的 Application 的時候, 就只有自定義 Application 并繼承于三方庫的 Application, 這時就不用再繼承 BaseApplication, 只用在自定義 Application 中對應的生命周期調用 AppDelegate 的對應方法 (Application 一定要實現 APP 接口), 框架就能照常運行, 并且 Application 中對應的生命周期可使用以下方式擴展

public class GlobalConfiguration implements ConfigModule {

@Override
    public void injectAppLifecycle(Context context, List<AppLifecycles> lifecycles) {
        //AppLifecycles 的所有方法都會在基類 Application 對應的生命周期中被調用, 所以可以在對應的方法中擴展一些自己需要的邏輯
        lifecycles.add(new AppLifecycles() {
            private RefWatcher mRefWatcher;//leakCanary觀察器

            @Override
            public void onCreate(Application application) {
                if (BuildConfig.LOG_DEBUG) {
                    //Timber日志打印
                    Timber.plant(new Timber.DebugTree());
                }
                //leakCanary內存泄露檢查
                this.mRefWatcher = BuildConfig.USE_CANARY ? LeakCanary.install(application) : RefWatcher.DISABLED;
            }

            @Override
            public void onTerminate(Application application) {
                this.mRefWatcher = null;
            }
        });
    }
}

<a name="3.13"></a>

3.13 ActivityDelegate 和 FragmentDelegate

這里實現的思想太牛逼, 所以請看我寫的 科普文章

<a name="3.14"></a>

3.14 框架中 RxLifecycle 的使用

參考這里

<a name="4"></a>

4 Arms 擴展庫

v2.5.0 開始, 將一些 Arms 核心庫的代碼抽取到了擴展庫中, 以便于框架使用者可以更靈活的選擇不同的三方庫,并且還可以減輕 Arms 的體積,后續還會繼續分拆更多的擴展庫出來,讓 Arms 的擴展性更強,體積更輕!

<a name="4.1"></a>

4.1 arms-imageloader-glide

v2.5.0 之后 Arms 核心庫不再包含 Glide, 如果想使用 Glide, 請自行依賴 arms-imageloader-glide 擴展庫, arms-imageloader-glide 擴展庫中實現了基于 GlideImageLoaderStrategy, 如果想使用其他圖片加載框架, 請自行實現 ImageLoaderStrategy, 關于 ImageLoaderStrategy 的介紹, 請看 這里

  • 依賴 arms-imageloader-glide
implementation 'me.jessyan:arms-imageloader-glide:x.y.z'
  • 注冊 GlideImageLoaderStrategy
public class GlobalConfiguration implements ConfigModule {
    @Override
    public void applyOptions(Context context, GlobalConfigModule.Builder builder) {
        builder.imageLoaderStrategy(new GlideImageLoaderStrategy());
    }
}
  • 使用方式
ArmsUtils.obtainAppComponentFromContext(context)
    .imageLoader()
    .loadImage(mApplication, ImageConfigImpl
                .builder()
                .url(data.getAvatarUrl())
                .imagerView(mAvater)
                .build());

<a name="4.2"></a>

4.2 arms-autolayout

v2.5.0 之后 Arms 核心庫不再包含 AndroidAutoLayout, 現在可以自行選擇屏幕適配方案, 建議使用 AndroidAutoSize, 如果還想繼續使用 AndroidAutoLayout, 就請依賴 arms-autolayout 擴展庫, AndroidAutoLayoutAndroidAutoSize 可以在項目中共存, 所以舊項目可以在新頁面使用 AndroidAutoSize, 舊頁面繼續使用 AndroidAutoLayout, 等有時間了再將舊頁面慢慢替換為 AndroidAutoSize

  • 依賴 arms-autolayout
implementation 'me.jessyan:arms-autolayout:x.y.z'
  • 配置設計圖尺寸
        <meta-data
            android:name="design_width"
            android:value="1080"/>
        <meta-data
            android:name="design_height"
            android:value="1920"/>

公眾號

掃碼關注我的公眾號 JessYan,一起學習進步,如果框架有更新,我也會在公眾號上第一時間通知大家

公眾號

Hello 我叫 JessYan,如果您喜歡我的文章,可以在以下平臺關注我

-- The end

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容