Skip to content
JessYan edited this page Sep 29, 2019 · 272 revisions

Logo Official

概述

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. 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 的内存泄漏,并通过通知栏提示内存泄漏信息.

1 开发准备

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

1.1 导入框架

implementation 'me.jessyan:arms:2.5.2'

---------------------- 以下是扩展库 ----------------------

//想使用 Glide 请依赖 arms-imageloader-glide 扩展库, 使用方式请看 #4.1
implementation 'me.jessyan:arms-imageloader-glide:2.5.2' 

//想使用 AndroidAutoLayout 请依赖 arms-autolayout 扩展库, 使用方式请查看 #4.2
implementation 'me.jessyan:arms-autolayout:2.5.2' 

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 {
        //就算您使用 AndroidStudio 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, 请看 arms-autolayout

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

1.5 混淆

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

1.6 版本更新

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

如果你想自定义修改本框架,并且保持远程仓库代码的同步更新,建议使用 submodule 的方式引入本框架,关于 submodule 的具体使用方法请查看 官方文档

$ git submodule add git@github.com:JessYanCoding/MVPArms.git

> 如果您获得本框架的方式是通过 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 总结

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

3 功能使用

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

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

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

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

3.5 AndroidEventBus Tag

本框架使用 AndroidEventBus 实现事件总线 (v2.5.0 版本后, 框架内不再包含 AndroidEventBus, 框架使用者可自行在 AndroidEventBusEventBus 两个库中选择, 想选择哪个 EventBus 就依赖哪个 EventBus, 依赖后框架会自动检测您依赖的 EventBus 并自动完成注册), 此框架使用注解标记接受消息的方法, 注解可以指定 Tag, 便于索引, 统一将 Tag 的常量写到 EventBusTag 接口中, 便于管理, 如果想在 ActivityFragmentServicePresenter 中使用 AndroidEventBus 请重写 useEventBus() 方法, 返回 true 代表使用, 但框架已经默认返回 true, 为什么 MVPArms 使用的是 AndroidEventBus 而不是 greenrobotEventBus, 请看这里 我的回答

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 中引用即可

3.7 自定义 PopupWindow

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

3.8 快速实现 RecyclerView

本框架提供了 DefaultAdapterBaseHolder 基类快速实现 RecyclerView, 如不能实现您复杂的业务需求, 请自行引用其他三方库实现

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

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

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, AppManager 内部持有一个含有所有存活的 Activity (未调用 onDestroy) 的 List 集合, 和一个当前在最前端的 Activity (未调用 onStop), AppManager 封装有多种方法, 可以很方便的对它们进行任何操作, AppManager 是单例的, 可以通过静态方法 AppManager.getAppManager() 直接拿到 AppManager 实例, 这样我们可以在整个 App 的任何地方对任何 Activity 进行全局操作, 比如在 App 请求网络超时时让最前端的 Activity 显示连接超时的交互页面 (这个逻辑不用写到当前请求的 Activity 里, 可以在一个单例类里做全局的统一操作, 因为可以随时随地通过 AppManager 拿到当前的任何 Activity)

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 的使用

参考这里

4 Arms 扩展库

v2.5.0 开始, 将一些 Arms 核心库的代码抽取到了扩展库中, 以便于框架使用者可以更灵活的选择不同的三方库,并且还可以减轻 Arms 的体积,后续还会继续分拆更多的扩展库出来,让 Arms 的扩展性更强,体积更轻!

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

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"/>
Clone this wiki locally