看到如此多的MVP+Dagger2+Retrofit+Rxjava项目,轻松拿star,心动了吗?

MVPArms

概述

MVPArms 是一个整合了大量主流开源项目的 Android MVP 快速搭建框架,其中包含 Dagger2 , Retrofit , Rxjava 以及 RxLifecycle , RxCacheRx 系三方库,并且提供 UI 自适应方案,本框架将它们结合起来,并全部使用 Dagger2 管理并提供给开发者使用,使用本框架开发你的项目就意味着你已经拥有一个 MVP + Dagger2 + Retrofit + Rxjava 项目

MVPArt 是一个新的 MVP 架构,适合中小型项目,旨在解决传统 MVP 类和接口太多,并且 PresenterView 通过接口通信过于繁琐,重用 Presenter 代价太大等问题

通知

特性

  • 通用框架,适合所有类型的项目,支持大型项目的开发,兼容组件化开发,可作为组件化的 Base

  • 框架高度可自定义化,可在不修改框架源码的情况下对 Retoift , Okhttp , RxCache , Gson 等框架的特有属性进行自定义化配置,可在不修改框架源码的情况下向 BaseApplication , BaseActivity , BaseFragment 的对应生命周期中插入代码

  • 全局使用 Dagger2 管理,独创的建造者模式 Module ,可实现使用 Dagger2 向框架任意位置注入自定义参数(将所有模块使用 Dagger2 连接起来,绝不是简单的使用)

  • 全局监听整个 App 所有 Activity 以及 Fragment 的生命周期(包括三方库),并可向其生命周期内插入代码

  • 全局监听 Http Request(请求参数, Headers ...), Response (服务器返回的结果, Headers ,耗时 ...)等信息(包括 Glide 的请求),可解析 json 后根据状态码做相应的全局操作以及数据加密, Cookie 管理等操作

  • 全局管理所有 Activity (包括三方库的 Activity),可实现在整个 App 任意位置,退出所有 Activity ,以及拿到前台 Activity 做相应的操作(如你可以在 App 任何位置做弹出 Dialog 的操作)

  • 全局 Rxjava 错误处理,错误后自动重试,捕捉整个应用的所有错误

  • 全局 UI 自适应

  • 图片加载类 ImageLoader 使用策略模式和建造者模式,轻松切换图片加载框架,方便功能扩展

  • 修改包名后就可以直接使用,快速接入(老项目接入请按下面的步骤)

框架结构

包结构

开发须知

  • 开发者需要具有一定的 Android 开发能力,以及自我解决问题的能力
  • 开发者必须有使用 Dagger2 , Rxjava , Retrofit 的经验,没使用过也必须了解,不然很难上手
  • 本框架为作者用业余时间维护,作者并没有义务为开发者做任何事,使用时或提问时请保持对作者以及维护者起码的 敬畏尊重

Libraries简介

  1. MvpGoogle官方出品的Mvp架构项目,含有多个不同的架构分支(此为Dagger分支).
  2. Dagger2Google根据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. RetrofitSquare出品的网络请求库,极大的减少了http请求的代码和步骤.
  10. Okhttp同样Square出品,不多介绍,做Android都应该知道.
  11. Autolayout鸿洋大神的Android全尺寸适配框架.
  12. GsonGoogle官方的Json Convert框架.
  13. ButterknifeJakeWharton大神出品的view注入框架.
  14. Androideventbus一个轻量级使用注解的Eventbus.
  15. TimberJakeWharton大神出品Log框架容器,内部代码极少,但是思想非常不错.
  16. Glide此库为本框架默认封装图片加载库,可参照着例子更改为其他的库,Api和Picasso差不多,缓存机制比Picasso复杂,速度快,适合处理大型图片流,支持gfit,Fresco太大了!,在5.0以下优势很大,5.0以上系统默认使用的内存管理和Fresco类似.
  17. LeakCanarySquare出品的专门用来检测AndroidJava的内存泄漏,通过通知栏提示内存泄漏信息.

1 开发准备

本框架建议直接使用 Gradle 远程依赖,框架已经提供了很多用于扩展的接口,足以满足日常需求,如非必须,请不要使用依赖 Module 的方式以及修改框架源码

1.1 导入框架

compile 'me.jessyan:arms:2.2.3' //使用Rxjava2
--------------------------------------------
compile 'me.jessyan:arms:1.6.3' //使用Rxjava1 (请注意 Rxjava1 的版本以后不再维护,建议使用 Rxjava2 版本)

1.2 引用config.build

本框架提供一个引用大量第三方库的config.gradle文件,用于第三方库版本管理,将config.gradle复制进根目录,并在项目的顶级build.gradle中引用它

// Top-level build file where you can add configuration options common to all sub-projects/modules.
apply from: "config.gradle" //这里表示引用config.gradle文件
buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.3.3'
    }
}
allprojects {
    repositories {
        jcenter()
        maven { url "https://jitpack.io" }//rxcahche 需要 jitpack 仓库
        maven { url "https://maven.google.com" }//Support-library 需要 Google 仓库
    }
}
task clean(type: Delete) {
    delete rootProject.buildDir
}

1.2.1 使用config.build

因为在顶级build.gradle中引用了它,所以在整个项目的所有build.gradle中都可以使用rootProject.xxx来使用它里面的内容

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile rootProject.ext.dependencies["junit"]
    compile rootProject.ext.dependencies["support-v4"]
    compile rootProject.ext.dependencies["gson"]
    compile rootProject.ext.dependencies["appcompat-v7"]
    compile rootProject.ext.dependencies["cardview-v7"]
    compile rootProject.ext.dependencies["autolayout"]
    compile rootProject.ext.dependencies["butterknife"]
    compile rootProject.ext.dependencies["androideventbus"]
    }

也可以使用它来管理一些项目的信息,这样有多个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"]
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

1.3 配置Build.gradle

1.3.1 依赖Dagger2

本框架全部使用Dagger2管理,所以必须依赖Dagger2,找到app的build.gradle,加入如下代码

apply plugin: 'com.android.application'
buildscript {
    repositories {
        jcenter()
    }
}
dependencies {
    annotationProcessor rootProject.ext.dependencies["butterknife-compiler"] //butterknife 插件,很多人因为没加这个而报错,切记!!!
    annotationProcessor rootProject.ext.dependencies["dagger2-compiler"]//依赖插件, annotationProcessor 是 AndroidStudio 自带并用来替换 APT 
    provided rootProject.ext.dependencies["javax.annotation"]//dagger2必须依赖jsr250 annotation
}

1.3.2 使用Lambda

本框架的 Demo ,默认使用 Lambda ,如你不想使用 Lambda ,请忽略以下的配置

  • 在项目根目录的 Build.gradle 中依赖 Lambda插件
buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.3.3'
        //lambda
        classpath 'me.tatarka:gradle-retrolambda:3.6.0'
    }
}

  • appBuild.gradle 中引入 Lambda 插件
apply plugin: 'me.tatarka.retrolambda'
android {
    compileOptions {
        targetCompatibility JavaVersion.VERSION_1_8
        sourceCompatibility JavaVersion.VERSION_1_8
    }
}

1.4 配置 AndroidManifest

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"/>

1.4.2 指定 Application

本框架想要正常运行需要使用框架提供的 BaseApplication ,当然你也可以自定义一个 Application 继承于它,也可以不用继承,直接将 BaseApplication 的代码复制到你自定义的 Application 里(里面只有几行代码),但是我并不推荐你使用后面的两种方式,因为本框架已经向开发者提供了 ConfigModule#injectAppLifecycle 方法,可以在运行时动态的向 BaseApplication 中插入任意代码,这样即使你不需要自定义 Application ,也可以做到初始化自己的业务

<application
        android:name="com.jess.arms.base.BaseApplication"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
</application>

1.4.3 配置 Autolayout

使用 Autolayout 自适应框架必须配置 Meta 属性,即设计图的宽高,详情参考 Autolayout ,本框架并不强制你使用 AutoLayout,如果你不想使用 AutoLayout,就不要配置下面的 meta-data

//配置设计图的宽高,配合AutoLauout控件使用,在设计图尺寸以外的其它尺寸手机上,也能达到和设计图一样的效果
        <meta-data
            android:name="design_width"
            android:value="1080"/>
        <meta-data
            android:name="design_height"
            android:value="1920"/>

1.4.4 配置框架自定义属性

本框架使用和Glide相同的方式来配置自定义属性,需要在AndroidManifest中声明它,详情

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

1.5 混淆

由于本框架依赖大量三方库,所以已经在 arms Module 下的 proguard-rules.pro 中提供了本框架所依赖三方库的所有规则,如果想使用它,请复制它替换 app Module 中的 proguard-rules.pro (Demo 并不能直接使用这个 proguard-rules.pro 进行混淆),混淆前务必注意将 Java Bean ,自定义组件 等必需的规则添加进 proguard-rules.pro

1.6 版本更新

  • 如通过 Gradle 远程依赖本框架请忽略

如果你获得本框架的方式是通过clone或者下载:

  1. 直接可以通过命令行git pull origin master拉取最新的版本并自动合并
  2. 如果你修改了包名还得执行命令git rm --cache -r app/src/main/java/me/jessyan/mvparms,下次拉取时就不会拉取Demo的内容

如果你获得本框架的方式是通过fork到自己仓库后,clone或下载:

  1. git remote add arms https://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 app/src/main/java/me/jessyan/mvparms,下次拉取时就不会拉取Demo的内容

2 快速开始

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>

2.2 AppComponent

Application生命周期是和App是一样的,所以适合提供一些单例对象,本框架使用Dagger2管理,使用AppComponent来提供全局所有的单例对象,所以需要自定义一个Application继承自BaseApplication,即可在App的任何地方,通过BaseApplicationgetAppComponent()方法,拿到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);
}

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) {
        Observable<List<User>> users = mRepositoryManager.obtainRetrofitService(UserService.class)
                .getUsers(lastIdQueried, USERS_PER_PAGE);
        //使用rxcache缓存,上拉刷新则不读取缓存,加载更多读取缓存
        return mRepositoryManager.obtainCacheService(CommonCache.class)
                .getUsers(users
                        , new DynamicKey(lastIdQueried)
                        , new EvictDynamicKey(update))
                .flatMap(new Func1<Reply<List<User>>, Observable<List<User>>>() {
                    @Override
                    public Observable<List<User>> call(Reply<List<User>> listReply) {
                        return Observable.just(listReply.getData());
                    }
                });
    }

2.4 MVP实战

定义业务逻辑MVP,继承MVP各自的基类即可,这里可以稍微粗力度的定义MVP类,即无需每个FragmentActivity(每个页面)都定义不同的MVP类,可以按照相同的业务逻辑使用一组MVP

2.4.1 Contract

这里根据Google官方的MVP项目,可以在Contract中定义MVP的接口,便于管理,此框架无需定义Presenter接口,所以Contract只定义ModelView的接口

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);
    }
}

2.4.2 View

一般让 ActivityFragment 实现 Contract 中定义的 View 接口,供 Presenter 调用对应方法操作 UI , BaseActivity 默认注入 Presenter ,如想使用 Presenter ,必须指定 Presenter 的范型(虽然只可以指定一个范型,但是可以自行生成并持有多个 Presenter ,达到重用的目的),和实现setupActivityComponent 来提供 Presenter 需要的 ComponentModule(如这个页面逻辑简单并不需要 Presenter ,那就不指定范型,也不实现方法)

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() {
    }
}

2.4.3 Model

Model 实现 ContractModel 接口,并且继承 BaseModel ,然后通过 IRepositoryManager 拿到需要的 ServiceCachePresenter 提供需要的数据(是否使用缓存请自行选择)

@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) {
        mRepositoryManager.obtainRetrofitService(UserService.class)
                             .getUsers();
    }
}

2.4.4 Presenter

PresenterMVP中的大部分的作用为通过从Model层接口获取数据,在调用View层接口显示数据,首先实现BasePresenter,指定ModelView的范型,注意一定要指定Contract中定义的接口,Presenter需要的ModelView,都使用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) {
    }
}

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;
    }
}

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);
}

2.4.7 Dagger Scope

在上面的代码中 ActivityScope 大量出现在 ModuleComponent 中,Dagger2 使用 Scope 限制每个 Module 中提供的对象的生命周期, Dagger2 默认只提供一个 @Singleton Scope 即单例,本框架提供 @ActvityScope@FragmentScope ,如有其他需求请自行实现, ModuleComponent 定义相同的 ScopeModule 中提供的对象的生命周期会和 Component 的生命周期相绑定(即在 Component 生命周期内,如需多次使用到 Moudle 中提供的对象,但只会调用一次@Provide 注解的方法得到此对象)

2.4.8 MVP总结

  • 以后每个业务逻辑都重复构造这些类,只是换个名字而已,值得注意的是MVP刚开始用时确实会觉得平白无故多了很多类,非常繁琐麻烦,但是等页面代码逻辑越来多时,你会发现其中的好处,逻辑清晰,解耦,便于团队协作,测试容易,错误好定位,所以现在本框架提供Template 自动生成代码解决这个痛点,让开发者更加愉快的使用本框架

3 功能使用

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

GlobalConfigModule使用建造者模式将App的全局配置信息封装进Module(使用Dagger注入到需要配置信息的地方),可以配置CacheFile,Interceptor等,甚至于Retrofit,Okhttp,RxCache都可以自定义配置,因为使用的是建造者模式所以如你有其他配置信息需要使用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);
    }
}

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请求和响应结果的处理类,
                    // 这里可以比客户端提前一步拿到服务器返回的结果,可以做一些操作,比如token超时,重新获取
                    @Override
                    public Response onHttpResultResponse(String httpResult, Interceptor.Chain chain, Response response) {
                        /* 这里可以先客户端一步拿到每一次http请求的结果,可以解析成json,做一些操作,如检测到token过期后
                           重新请求token,并重新执行请求 */
                        try {
                            if (!TextUtils.isEmpty(httpResult) && RequestInterceptor.isJson(response.body())) {
                                JSONArray array = new JSONArray(httpResult);
                                JSONObject object = (JSONObject) array.get(0);
                                String login = object.getString("login");
                                String avatar_url = object.getString("avatar_url");
                                Timber.w("Result ------> " + login + "    ||   Avatar_url------> " + avatar_url);
                            }
                        } catch (JSONException 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将新的请求,请求成功后,将返回的response  return出去即可
                        如果不需要返回新的结果,则直接把response参数返回出去 */
                        return response;
                    }
                    // 这里可以在请求服务器之前可以拿到request,做一些操作比如给request统一添加token或者header以及参数加密等操作
                    @Override
                    public Request onHttpRequestBefore(Interceptor.Chain chain, Request request) {
                        /* 如果需要再请求服务器之前做一些操作,则重新返回一个做过操作的的requeat如增加header,不做操作则直接返回request参数
                           return chain.request().newBuilder().header("token", tokenId)
                                  .build(); */
                        return request;
                    }
                });
    }
}

3.3 全局错误处理及发生错误时重新执行

如果需要使用Rxjava的全局错误处理,需在全局配置类中通过GlobalConfigModule.Builder.responseErroListener()方法传入ResponseErroListener,并在每次使用Rxjava调用subscribe时,使用ErrorHandleSubscriber,并传入AppComponent中提供的RxErrorHandler,此Subscribe,默认已经实现OnError方法,如想自定义可以重写OnError方法

public class GlobalConfiguration implements ConfigModule {
    @Override
    public void applyOptions(Context context, GlobalConfigModule.Builder builder) {
        builder.responseErrorListener((context1, e) -> {
                    /* 用来提供处理所有错误的监听
                       rxjava必要要使用ErrorHandleSubscriber(默认实现Subscriber的onError方法),此监听才生效 */
                    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) {
     }
});

3.4 ImageLoader 如何扩展以及切换图片请求框架

本框架默认使用 Glide 实现图片加载功能,使用 ImageLoader 提供统一的接口, ImageLoader 使用策略模式和建造者模式,可以动态切换图片请求框架(比如说切换成 Picasso ),并且加载图片时传入的参数也可以随意扩展( loadImage 方法在需要扩展参数时,调用端也不需要改动,全部通过 Builder 扩展,比如你想让内部的图片加载框架,清除缓存你只需要定义个 boolean 字段,内部根据这个字段 if|else,其他操作同理,当需要切换图片请求框架或图片请求框架升级后变更了 Api 时,这里可以将影响范围降到最低,所以封装 ImageLoader 是为了屏蔽这个风险)

  • ! 本框架默认提供了 GlideImageLoaderStrategyImageConfigImpl 简单实现了图片加载逻辑,方便快速使用,但开发中难免会遇到复杂的使用场景,所以本框架推荐即使不切换图片请求框架继续使用 Glide ,也请按照下面的方法,自行实现图片加载策略,因为默认实现的 GlideImageLoaderStrategy 是直接打包进框架的,如果是远程依赖,当遇到满足不了需求的情况,你将不能扩展里面的逻辑

  • 使用 ImageLoader 必须传入一个实现了 BaseImageLoaderStrategy 接口的图片加载实现类从而实现动态切换,所以首先要实现BaseImageLoaderStrategy ,实现时必须指定一个继承自 ImageConfig 的实现类,使用建造者模式,可以储存一些信息,比如 URL , ImageView , Placeholder 等,可以不断的扩展,供图片加载框架使用

public class PicassoImageLoaderStrategy implements BaseImageLoaderStrategy<PicassoImageConfig> {
     @Override
    public void loadImage(Context ctx, PicassoImageConfig config) {
                        Picasso.with(ctx)
                .load(config.getUrl())
                .into(config.getImageView());
    }
}
  • 实现 ImageCofig 使用建造者模式(创建新的 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,通过方法传入
mApplication
    .getAppComponent()
    .imageLoader()
    .setLoadImgStrategy(new PicassoImageLoaderStrategy());
使用方法
mApplication
    .getAppComponent()
    .imageLoader()
    .loadImage(mApplication, PicassoImageConfig
                .builder()
                .url(data.getAvatarUrl())
                .imagerView(mAvater)
                .build());

3.5 AndroidEventBus Tag

本框架使用 AndroidEventBus 实现事件总线,此框架使用注解标记目标方法,统一将 Tag 的常量写到 EventBusTag 接口中,便于管理,如果要在当前对象中使用 AndroidEventBus 请在需要使用的 Activity , Fragment , Presenter 中重写 useEventBus() ,返回 true 代表使用,默认返回 true ,为什么 MVPArms 使用 AndroidEventBus 而不是 greenrobotEventBus ,请看这里 我的回答

3.6 AutoLayout组件

本框架使用AutoLayout框架,实现控件自适应,此框架要让组件自适应,必须让它的父控件,重新测量,和重写LayoutParams,而官方只默认提供了三个ViewGroup,AutoRelativeLayout,AutoLinearLayout,AutoFrameLayout实现了这些操作,为了方便开发者使用,本框架提供了一些常用的AutoLayout组件,在框架的widget包下的autolayout包中,在xml中引用即可使子控件自适应,并且还提供一个 Template(在最后面)用于生成自适应所需要的的Auto系列View,如需要使ScrollView的子控件自适应,使用此Template输入ScrollView,即可生成AutoScrollView,在xml中引用即可

3.7 自定义PopupWindow

框架提供一个使用建造者模式的自定义 PopupWindow 组件: CustomPopupWindow,自己实现布局后就可以直接使用这个类实现 PopupWindow ,使用建造者模式,随意扩展自定义参数

3.8 快速实现RecycleView

本框架提供DefaultAdapterBaseHolder基类快速实现Recycleview.

  • BaseHolder默认初始化了ButterKnifeAutoLayout,继承后不仅可以直接注入View,布局还可以自适应屏幕
  • RecycleView默认是不提供Item的点击事件的,使用DefaultAdapter调用setOnItemClickListener可以实现Item的点击事件

3.9 权限管理(适配Android6.0权限管理)

本框架使用RxPermissions用于权限管理(适配android6.0),并提供PermissionUtil工具类一行代码实现权限请求.适配Android6.0权限管理详解

PermissionUtil.launchCamera(new PermissionUtil.RequestPermission() {
            @Override
            public void onRequestPermissionSuccess() {
                launchCapture();//请求权限成功后做一些操作
            }
            @Override
            public void onRequestPermissionFailure() {
                mRootView.showMessage("Request permissons failure");
            }
        }, mRxPermissions, mErrorHandler);

3.10 Gradle配置启动DeBug模式

在主项目(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);
                }
            }
        });
    }

3.11 AppManager(管理所有的Activity)

AppManager用于管理所有的Activity,内部持有一个含有所有存活的Activity(未调用onDestroy)的List,和一个当前在最前端的Activity(未调用onPause),AppManager封装有多种方法,可以很方便的对它们进行操作,也可以在未持有AppManager的情况下,通过EventBus远程遥控它的所有方法,这样我们可以在整个app的任何地方对任何Activity进行全局操作,比如在app请求网络超时时让最前端的Activity显示连接超时的交互页面(这个逻辑不用写到当前请求的Activity里,可以在一个单例类里做全局的统一操作,因为可以随时通过AppManager拿到当前的Activity)

  • 远程遥控通过 AppManager.post(Message) 实现,通过 Message 对象中不同的 what 区分不同的方法和 Handler 同理,并且可以根据自己的需求不断扩展

  • 如何扩展自己的需求? 在 ConfigModule#injectAppLifecycle(Context, List) 中通过 AppLifecycles#onCreate(Application),在 App 初始化时,配置满足自己需求的 HandleListener

    @Override
    public void injectAppLifecycle(Context context, List<AppLifecycles> lifecycles) {
        lifecycles.add(new AppLifecycles() {
            @Override
            public void onCreate(Application application) {
                ArmsUtils.obtainAppComponentFromContext(application).appManager().setHandleListener(new AppManager.HandleListener() {
                    @Override
                    public void handleMessage(AppManager appManager, Message message) {
                        switch (message.what) {
                            //case 0:
                            //do something ...
                            //   break;
                        }
                    }
                });
            }
        });
    }
    
  • 如何使用? 通过 AppManager.post(Message) 发送不同 whatMessage 对象即可``` Message msg = new Message(); msg.what = 0; AppManager.post(msg); //like EventBus

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;
            }
        });
    }
}

3.13 ActivityDelegate 和 FragmentDelegate

这里实现的思想太牛逼,所以请看我写的 文章