JessYan edited this page Nov 16, 2018 · 252 revisions

Logo

概述

MVPArms 是一个整合了大量主流开源项目的 Android MVP 快速搭建框架, 其中包含 Dagger2RetrofitRxJava 以及 RxLifecycleRxCacheRx 系三方库, 并且提供 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 修改包名后就可以直接使用, 快速接入(老项目接入请按下面的步骤)

框架结构

包结构

Application 结构 (MVPArms 使用者贡献)

开发须知

  • 开发者需要具有一定的 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复杂,速度快,适合处理大型图片流,支持 gif 图片,Fresco太大了!在5.0以下优势很大,5.0以上系统默认使用的内存管理和Fresco类似.
  17. LeakCanarySquare出品的专门用来检测AndroidJava的内存泄漏,通过通知栏提示内存泄漏信息.

1 开发准备

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

1.1 导入框架

implementation 'me.jessyan:arms:2.5.0' //使用Rxjava2, 已兼容 AndroidStudio v3.0 以及 Gradle Plugin v3.0

--------------------------------------------

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

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
}

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"]
    }
}

1.3 配置 build.gradle

1.3.1 依赖 Dagger2

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

dependencies {
    annotationProcessor rootProject.ext.dependencies["butterknife-compiler"] //Butterknife 插件, 很多人因为没加这个而报错, 切记!!!
    annotationProcessor rootProject.ext.dependencies["dagger2-compiler"]//依赖插件
}

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

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

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, 请看

1.4.4 配置框架自定义属性

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

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

1.5 混淆

由于本框架依赖大量三方库, 所以已经在 arms Module 下的 proguard-rules.pro 中提供了本框架所依赖三方库的所有规则, 如果想使用它, 请复制它替换 demo 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 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 的内容

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 来提供全局所有的单例对象, 现在在 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);
}

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

2.4 MVP 实战

定义业务逻辑 MVP, 继承 MVP 中各自的基类即可, 这里可以稍微粗力度的定义 MVP 类, 即无需每个页面 (ActivityFragment) 都定义不同的 MVP 类, 可以按照相同的业务逻辑使用一组 MVP 类 (即使您使用 MVPArms 全家桶 一键生成这些文件, 也建议将以下基础看完)

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

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

    }
}

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

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

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 中定义相同的 Scope 后, Module 中提供的对象的生命周期会和 Component 的生命周期进行绑定 (即在 Component 的生命周期内, 如需多次使用到 Moudle 中提供的对象, Dagger 只会调用一次带有 @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 请求的结果, 可以先解析成 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 如增加 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());
    }
}
  • 实现 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, 通过方法传入
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模式

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

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

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

3.14 框架中 RxLifecycle 的使用

参考这里

You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.