基础依赖

1. 简介

MVP-Base 是一个整合了大量主流开源项目的 Android MVP 快速搭建框架, 其中包含 Dagger2RetrofitRxJava 以及 AutoDisposeRxCacheRx 系三方库, 本框架将它们结合起来, 并全部使用 Dagger2 管理并提供给开发者使用, 使用本框架就意味着您已经拥有一个 MVP + Dagger2 + Retrofit + RxJava 项目

1.1. 特性

  • Base 基类(IBaseActivity, IBaseFragment, BaseContext ...)
  • MVP 基类(IModel, IVIew, IPresenter ...)
  • 框架高度可自定义化 (ConfigModule), 可在不修改框架源码的情况下对 Retoift, Okhttp, RxCache, Gson 等框架的特有属性进行自定义化配置, 可在不修改框架源码的情况下向 BaseContext, BaseActivity, BaseFragment 的对应生命周期中插入任意代码, 并且框架独有的 ConfigModule 配置类, 可在不修改框架源码的情况下为框架轻松扩展任何新增功能。
  • 使用AutoDispose实现Rx解绑,避免使用RxJava的内存泄漏问题。
  • 独创的建造者模式 Module (GlobalConfigModule), 可实现使用 Dagger2 向框架任意位置注入自定义参数, 可轻松扩展任意自定义参数
  • 全局使用 Dagger2 管理 (将所有模块使用 Dagger2 连接起来, 绝不是简单的使用)
  • 全局监听整个 App 所有 Activity 以及 Fragment 的生命周期 (包括三方库), 并可向其生命周期内插入任意代码
  • 全局监听 Http Request(请求参数, Headers ...), Response (服务器返回的结果, Headers, 耗时 ...)等信息(包括 Glide 的请求), 可解析 json 后根据状态码做相应的全局操作以及数据加密, Cookie 管理等操作
  • 全局管理所有 Activity (包括三方库的 Activity), 可实现在整个 App 任意位置, 退出所有 Activity, 以及拿到前台 Activity 做相应的操作(如您可以在 App 任何位置做弹出 Dialog 的操作)
  • 全局 Rxjava 错误处理, 错误后自动重试, 捕捉整个应用的所有错误
  • 网络请求日志打印封装(提供解析后的服务器的请求信息和服务器的响应信息)
  • 框架内自有组件的缓存机制封装(框架内可缓存内容的组件都提供有接口供外部开发者自定义缓存机制)
  • 代码生成插件(Module模版, Activity模版一键生成所需要的所有类文件)

1.2. MVP是什么?

  • MVC : View层是各种Layout文件。 Controller对应的是Activity, Fragment。Activity既操作了UI, 又担任了一部分Controller的逻辑处理。很容易在一个Activity里写上千行的代码。所以MVC的方式在实际实现中,Activity所在的Controller是非常重的,各层次之间的耦合情况也比较严重,不方便单元测试。 base-framework

  • MVP :Presenter完全把Model和View进行了分离,主要的程序逻辑在 Presenter里实现。而且,Presenter与具体的View是没有直接关联的,而是通过定义好的接口进行交互,从而使得在变更View时候可以 保持Presenter的不变,即重用! base-framework

1.3. 框架结构

base-framework

1.4. Application结构

base-Application

1.5. 包结构

base-package

1.6. 开发须知

  • 开发者需要具有一定的 Android 开发能力,以及自我解决问题的能力
  • 开发者必须有使用 Dagger2 , Rxjava , Retrofit 的经验,没使用过也必须了解,不然很难上手

1.7. 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. RxPermissions 用于处理 Android 运行时权限的响应式库.
  8. Retrofit 是 Square 出品的网络请求库,极大的减少了 Http 请求的代码和步骤.
  9. Okhttp 同样 Square 出品,不多介绍,做 Android 的都应该知道.
  10. Gson 是 Google 官方的 Json Convert 框架.
  11. Butterknife 是 JakeWharton 大神出品的 View 注入框架.
  12. Glide 是本框架默认封装到扩展库 arms-imageloader-glide 中的图片加载库,可参照着 Wiki 更改为其他的图片加载库,Glide 的 API 和 Picasso 差不多,缓存机制比 Picasso 复杂,速度快,适合处理大型图片流,支持 gif 图片,Fresco 太大了!在 5.0 以下优势很大,5.0 以上系统默认使用的内存管理和 Fresco 类似.
  13. AudoDispose在 Android 上使用 RxJava 都知道的一个坑,就是生命周期的解除订阅,这个框架通过Google Lifecycle组件的LifecycleOwner接口解决了内存泄漏问题
  14. GlobalErrorTransformer一种基于RxJava2实现全局Error处理的实现方式。

2. 基础环境

  • AndroidStudio 3.4.+
  • java1.8
  • gradle 5.1.1

3. 如何使用

3.1. Release版本

  • 项目依赖
allprojects {
  repositories {
        maven {
               // 配置用户名和密码
               credentials {
                   username 'anonymous'
                   password ''
               }
               // 配置仓库地址
               url 'http://172.16.28.234:8083/repository/maven-releases/'
           }
  }
}
  • 添加依赖

android-support版本

    implementation 'com.jpxx.android.library:mvp-base-support:latest.integration'

3.2. 快照版本

  • 项目依赖
allprojects {
  repositories {
        maven {
               // 配置用户名和密码
               credentials {
                   username 'anonymous'
                   password ''
               }
               // 配置仓库地址
               url 'http://172.16.28.234:8083/repository/maven-snapshots/'
           }
  }
}
  • 添加依赖

android-support版本

    implementation 'com.jpxx.android.library:mvp-base-support:1.0.0-SNAPSHOT'

4. 配置

4.1. 配置versions.gradle

本框架提供一个含有大量第三方库的 versions.gradle 文件 (里面的所有第三方库并不会全部被引入到项目中, 只是作为变量方便项目中多个位置进行引用, 特别适用于多 Module 的项目), 用于第三方库的版本管理, 将 versions.gradle复制进根目录, 并在项目的顶级build.gradle 中引用它

buildscript {
    apply from: 'versions.gradle'
    addRepos(repositories, false)

    dependencies {
        classpath deps.android_gradle_plugin
    }
}

allprojects {
    addRepos(repositories, true)

    tasks.withType(Javadoc) {
        //有中文注释
        options.encoding = "UTF-8"
    }
}

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

4.2. 使用versions.gradle

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

         implementation rootProject.ext.deps.fastLib.mvp_base

也可以使用versions.gradle 来管理项目的基本信息,这样多个Module也可以直接使用同一个值

android {
    compileSdkVersion rootProject.ext.build_versions.compile_version
    buildToolsVersion rootProject.ext.build_versions.build_tools

    defaultConfig {
        applicationId "com.jpxx.android"
        //到versions.gradle修改相应信息
        minSdkVersion rootProject.ext.build_versions.min_sdk
        targetSdkVersion rootProject.ext.build_versions.target_sdk
        versionCode 1
        versionName "1.0"
    }
}

4.3. 配置 build.gradle

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

dependencies {
    //Arouter
    annotationProcessor rootProject.ext.deps.aRouter.compiler
    //butterKnife
    annotationProcessor rootProject.ext.deps.butterknife.compiler
    //glide
    annotationProcessor rootProject.ext.deps.glide.compiler
    //dagger2
    annotationProcessor rootProject.ext.deps.dagger2.compiler
}

4.4. 配置Java版本

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

4.5. 配置项目签名文件

项目签名文件建议放在signature文件夹下,同时配置signature.gradle

apply plugin: 'com.android.application'
apply from: '../signature/signature.gradle'

android {
  buildTypes {
          debug {
              signingConfig signingConfigs.debug_key

              buildConfigField "boolean", "DEV_MODE", "true"
          }

          release {
              signingConfig signingConfigs.release_key

              zipAlignEnabled true
              minifyEnabled true
              shrinkResources true
          }

          //above AS 3.0
          android.applicationVariants.all { variant ->
              variant.outputs.all {
                  def mApplicationId = variant.mergedFlavor.applicationId + (variant.buildType.applicationIdSuffix == null ? "" : variant.buildType.applicationIdSuffix)
                  if (variant.buildType.name == "debug") {
                      outputFileName = "v${variant.versionName}_${variant.versionCode}_${variant.productFlavors[0].name}_${variant.buildType.name}.apk"
                  } else {
                      outputFileName = "v${variant.versionName}_${variant.versionCode}_${variant.productFlavors[0].name}_${variant.buildType.name}_${releaseTime()}.apk"
                  }
              }
          }
      }
}

在项目signature目录下放置签名文件并同时新建signatrue.gradle

apply plugin: 'com.android.application'

android {
    signingConfigs {
        release_key {
            storeFile file("../signature/xxxx.jks")
            storePassword "xxxx"
            keyAlias "xxx"
            keyPassword "xxx"
        }

        debug_key {
            storeFile file("../signature/xxxx.jks")
            storePassword "xxxx"
            keyAlias "xxx"
            keyPassword "xxxx"
        }
    }
}

4.6. 配置Manifest

4.6.1. 权限

==依赖MVP-Base库后,自动导入如下权限==

    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
    <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" />

    <!--Android O 安装未知来源应用-->
    <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
    <!--存储权限-->
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

4.6.2. 指定 Application

本框架想要正常运行需要使用框架提供的 BaseContext, 因为本框架已经向开发者提供了可以在运行时动态的向 BaseContext 的任意生命周期中插入任意代码, 这样即使您不需要自定义 Application, 也可以做到初始化自己的业务

<application
        android:name="com.jpxx.base.BaseContext">
</application>

4.6.3. 配置框架自定义属性

        <meta-data
         android:name="app_packagename.GlobalConfiguration"
            android:value="ConfigModule"/>

4.7. 混淆

由于本框架依赖大量三方库, 所以已经在 MVP Module 下的proguard-rules.pro 中提供了本框架所依赖三方库的所有规则, 项目导入mvp-base基础库时,AndroidStudio自动合并该规则,无须另行配置。 混淆前务必注意将 Java Bean, 自定义组件 等必需的规则添加进 proguard-rules.pro

4.8. 注意

  1. 安装release包时如遇到找不到GlobalConfiguration,收到ClassNotFoundException日志,请参考如下解决方案,将GlobalConfiguration保存在主dex中。

    • builde.gradle同级目录增加multidex-rules.pro

    • 增加如下配置

        buildTypes {
        release {
                  zipAlignEnabled true
                  minifyEnabled true
                  shrinkResources true
      
                  multiDexKeepProguard file('multidex-rules.pro')
              }
      
    • ==multidex-rules.pro==文件中增加如下代码

      -keep class * implements com.jpxx.base.ui.mvp.integration.ConfigModule
      
    • 参考链接 https://developer.android.com/studio/build/multidex

    • 具体实现可参考app-demo

5. 快速开始

5.1. ConfigModule

ConfigModule 用来给框架配置各种自定义属性和功能, 配合GlobalConfigModule使用, 功能非常强大

  • 新建一个类实现 ConfigModule 接口, 并在 AndroidManifest 中声明
public class GlobalConfiguration implements ConfigModule {
    //返回值[1, Long.Max_VALUE]越小,优先级越高。越先被执行。
    //组件化模式开发时,基础组件Common-SDK的优先级必须是1. 其他组件的优先级大于1,如此可以保证其他组件的配置覆盖基础组件里的配置。
    @Override
    public int priority() {
        return 10;
    }

    @Override
    public void applyOptions(Context context, GlobalConfigModule.Builder builder) {
     //使用 builder 可以为框架配置一些配置信息
     builder.baseurl(ApiURLPath.getCustomApiBaseUrl())
            .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>
     <meta-data
         android:name="app_packagename.GlobalConfiguration"
         android:value="ConfigModule"/>
</application>

5.2. AppComponent

Application 的生命周期和 App 是一致的, 所以适合提供一些单例对象, 本框架使用 Dagger2 管理, 使用 AppComponent 来提供全局所有的单例对象, 现在在 App 的任何地方, 都可通过 BaseContext (可自定义 Application, 实现 App 接口即可) 的 getAppComponent() (非静态) 方法 (快捷方法 CBaseUtils.obtainAppComponentFromContext(context)), 拿到 AppComponent 里面声明的所有单例对象

/**
 * ================================================
 * 可通过 {obtainAppComponentFromContext(Context)} 拿到此接口的实现类
 * 拥有此接口的实现类即可调用对应的方法拿到 Dagger 提供的对应实例
 * ================================================
 */
@Singleton
@Component(modules = {AppModule.class, ClientModule.class, GlobalConfigModule.class})
public interface AppComponent {
    Application application();

    /**
     * 用于管理网络请求层, 以及数据缓存层
     *
     * @return {@link IRepositoryManager}
     */
    IRepositoryManager repositoryManager();

    /**
     * 网络请求框架
     *
     * @return {@link OkHttpClient}
     */
    OkHttpClient okHttpClient();

    /**
     * Json 序列化库
     *
     * @return {@link Gson}
     */
    Gson gson();

    /**
     * 缓存文件根目录 (RxCache 和 Glide 的缓存都已经作为子文件夹放在这个根目录下), 应该将所有缓存都统一放到这个根目录下
     * 便于管理和清理
     *
     * @return {@link File}
     */
    File cacheFile();

    /**
     * 用来存取一些整个 App 公用的数据, 切勿大量存放大容量数据, 这里的存放的数据和 {@link Application} 的生命周期一致
     *
     * @return {@link Cache}
     */
    Cache<String, Object> extras();

    /**
     * 用于创建框架所需缓存对象的工厂
     *
     * @return {@link Cache.Factory}
     */
    Cache.Factory cacheFactory();

    /**
     * 返回一个全局公用的线程池,适用于大多数异步需求。
     * 避免多个线程池创建带来的资源消耗。
     *
     * @return {@link ExecutorService}
     */
    ExecutorService executorService();

    void inject(AppComponentDelegate delegate);

    @Component.Builder
    interface Builder {
        @BindsInstance
        Builder application(Application application);

        Builder globalConfigModule(GlobalConfigModule globalConfigModule);

        AppComponent build();
    }
}

5.3. RepositoryManager

RepositoryManager 用来管理网络请求层以及数据缓存层, 以后可能添加数据库储存层, 专门提供给 Model 层做数据处理

  • 自行定义 Retrofit Service, 如下, 熟练 Retrofit 请忽略

    public interface ContentService {
        /**
         * 获取处罚通告分页
         * @return
         */
        @GET("punishNoticeCharge")
        Observable<APIResponseListData<NotificationMessages>> issueGetPunishMessageList(@Query("pageIndex") int pageIndex, @Query("pageSize") int pageSize);
    }
    
  • 自行定义 RxCache Provider, 如下, 熟练 RxCache 请忽略
public interface CommonCache {
    @LifeCache(duration = 2, timeUnit = TimeUnit.MINUTES)
    Observable<Reply<APIResponseListData<NotificationMessages>>> getNotificationMessagesCache(Observable<APIResponseListData<NotificationMessages>> messages, DynamicKey lastPageIndex, EvictProvider evictProvider);
}
  • Model 中通过 RepositoryManager#obtainRetrofitService()RepositoryManager#obtainCacheService() 使用这些服务
    @Override
    public Observable<APIResponseListData<NotificationMessages>> getNotificationMessages(int pageIndex, boolean update) {
        //使用rxcache缓存,上拉刷新则不读取缓存,加载更多读取缓存
        return Observable.just(mRepositoryManager
                .obtainRetrofitService(ContentService.class)
                .issueGetPunishMessageList(pageIndex, USERS_PER_PAGE))
                .flatMap((Function<Observable<APIResponseListData<NotificationMessages>>, ObservableSource<APIResponseListData<NotificationMessages>>>) apiResponseDataObservable -> mRepositoryManager.obtainCacheService(CommonCache.class)
                        .getNotificationMessagesCache(apiResponseDataObservable
                                , new DynamicKey(pageIndex)
                                , new EvictDynamicKey(update))
                        .map(Reply::getData));
    }

5.4. MVP 实战

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

5.4.1. Contract

这里根据 Google 官方的 MVP 架构,可以在 Contract 中定义 MVP 类的接口, 便于管理, 本框架无需定义 Presenter 接口, 所以在 Contract 中只定义 ViewModel 的接口

/**
 * 展示 Contract 的用法
 * Contract 是一个契约,将Model、View、Presenter 进行约束管理,方便后期类的查找、维护。
 *
 */
public interface MessagesContract {
    //对于经常使用的关于UI的方法可以定义到IView中,如显示隐藏进度条,和显示文字消息
    interface View extends IView {
        FragmentActivity provideContext();

        void loadMoreComplete();
    }

    //Model层定义接口,外部只需关心Model返回的数据,无需关心内部细节,如是否使用缓存
    interface Model extends IModel {
        Observable<APIResponseListData<NotificationMessages>> getNotificationMessages(int pageIndex, boolean update);
    }
}

5.4.2. View

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

public class MessagesFragment extends BaseFragment<MessagesPresenter> implements MessagesContract.View{
    @BindView(R.id.recyclerView)
    LQRRecyclerView mRecyclerView;
    @BindView(R.id.swipeRefreshLayout)
    SwipeRefreshLayout mSwipeRefreshLayout;

    @Inject
    LQRAdapterForRecyclerView<NotificationMessages> mAdapter;

    @Override
    public void setupFragmentComponent(@NonNull AppComponent appComponent) {
        DaggerMessagesComponent
                .builder()
                .appComponent(appComponent)
                .view(this)
                .build()
                .inject(this);
    }

    @Override
    public int getLayoutResourceId() {
        return R.layout.frament_messages;
    }

    @Override
    public void initView() {
        mRecyclerView.setAdapter(mAdapter);

        mRecyclerView.setLoadingMoreEnabled(true);
    }

    @Override
    public void initListener() {
        mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
            @Override
            public void onRefresh() {
                mPresenter.requestNotificationMessages(true);
            }
        });

        mRecyclerView.setLoadingListener(new LQRRecyclerView.LoadingListener() {
            @Override
            public void onLoadMore() {
                mPresenter.requestNotificationMessages(false);
            }
        });
    }

    @Override
    public void initData(@Nullable Bundle savedInstanceState) {
    }

    @Override
    public void onFragmentVisibleChange(boolean isVisible) {
        super.onFragmentVisibleChange(isVisible);
        if(isVisible){
            mPresenter.requestNotificationMessages(true);
        }
    }

    @Override
    public void showLoading() {
        mSwipeRefreshLayout.setRefreshing(true);
    }

    @Override
    public void hideLoading() {
        mSwipeRefreshLayout.setRefreshing(false);
    }

    @Override
    public void loadMoreComplete() {
        mRecyclerView.loadMoreComplete();
    }

    @Override
    public FragmentActivity provideContext() {
        return getActivity();
    }
}

5.4.3. Model

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

@ActivityScope
public class MessagesModel extends BaseModel implements MessagesContract.Model {
    public static final int USERS_PER_PAGE = 10;

    @Inject
    public MessagesModel(IRepositoryManager repositoryManager) {
        super(repositoryManager);
    }

    @Override
    public Observable<APIResponseListData<NotificationMessages>> getNotificationMessages(int pageIndex, boolean update) {
        //使用rxcache缓存,上拉刷新则不读取缓存,加载更多读取缓存
        return Observable.just(mRepositoryManager
                .obtainRetrofitService(ContentService.class)
                .issueGetPunishMessageList(pageIndex, USERS_PER_PAGE))
                .flatMap((Function<Observable<APIResponseListData<NotificationMessages>>, ObservableSource<APIResponseListData<NotificationMessages>>>) apiResponseDataObservable -> mRepositoryManager.obtainCacheService(CommonCache.class)
                        .getNotificationMessagesCache(apiResponseDataObservable
                                , new DynamicKey(pageIndex)
                                , new EvictDynamicKey(update))
                        .map(Reply::getData));
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
    void onPause() {
    }
}

5.4.4. Presenter

PresenterMVP 中的大部分作用是实现业务逻辑代码, 从 Model 层获取数据, 在调用 View 层显示数据, 首先必须实现 BasePresenter, 并指定 ViewModel 的范型, 注意一定要指定 Contract 中定义的接口, Presenter 需要的 ViewModel, 都使用 Dagger2 来注入, 这样即解藕又方便测试。

@ActivityScope
public class MessagesPresenter extends BasePresenter<MessagesContract.Model, MessagesContract.View> {
    @Inject
    Application mApplication;
    @Inject
    LQRAdapterForRecyclerView<NotificationMessages> mAdapter;
    @Inject
    RuntimePermissionUtil mPermissionUtil;

    private int lastPageIndex = 0;
    private boolean isFirst = true;

    @Inject
    public MessagesPresenter(MessagesContract.Model model, MessagesContract.View rootView) {
        super(model, rootView);
    }

    public void requestNotificationMessages(final boolean pullToRefresh) {
        //请求外部存储权限用于适配android6.0的权限管理机制
        mPermissionUtil.externalStorage("xxxxxx", new RuntimePermissionUtil.RequestPermission() {
            @Override
            public void onRequestPermissionSuccess() {
                requestFromModel(pullToRefresh);
            }

            @Override
            public void onRequestPermissionFailure(String permissionName) {
                if(pullToRefresh){
                    mRootView.hideLoading();//隐藏下拉刷新的进度条
                } else{
                    mRootView.loadMoreComplete();
                }
            }

            @Override
            public void onRequestPermissionFailureWithAskNeverAgain(String permissionName) {
                if(pullToRefresh){
                    mRootView.hideLoading();//隐藏下拉刷新的进度条
                } else{
                    mRootView.loadMoreComplete();
                }
            }
        });
    }

    private void requestFromModel(boolean pullToRefresh) {
        if (pullToRefresh) lastPageIndex = 0;//下拉刷新默认只请求第一页

        //关于RxCache缓存库的使用请参考 http://www.jianshu.com/p/b58ef6b0624b

        boolean isEvictCache = pullToRefresh;//是否驱逐缓存,为ture即不使用缓存,每次下拉刷新即需要最新数据,则不使用缓存

        if (pullToRefresh && isFirst) {//默认在第一次下拉刷新时使用缓存
            isFirst = false;
            isEvictCache = false;
        }

        mModel.getNotificationMessages(lastPageIndex, isEvictCache)
                .subscribeOn(Schedulers.io())
                .compose(GlobalErrorProcessor.processGlobalError(true))
                .doOnSubscribe(disposable -> {
                    if (pullToRefresh){
                        mRootView.showLoading();//显示下拉刷新的进度条
                    } else {
                        // show loading more footer
                    }
                })
                .subscribeOn(AndroidSchedulers.mainThread())
                .observeOn(AndroidSchedulers.mainThread())
                .doFinally(() -> {
                    if (pullToRefresh){
                        mRootView.hideLoading();//隐藏下拉刷新的进度条
                    } else{
                        mRootView.loadMoreComplete();
                    }
                })
                .as(RxLifecycleUtils.bindLifecycle(lifecycleOwner))
                .subscribe(new ApiObserver<APIResponseListData<NotificationMessages>>() {
                    @Override
                    public void success(APIResponseListData<NotificationMessages> messagesResponse) {
                        lastPageIndex ++;

                        if (pullToRefresh)
                            mAdapter.setData(messagesResponse.getData().getContent());
                        else
                            mAdapter.addMoreData(messagesResponse.getData().getContent());
                    }

                    @Override
                    public void dealError(APIError apiError) {
                        super.dealError(apiError);
                    }
                });
    }

    /**
     * 资源的释放
     */
    @Override
    public void onDestroy(LifecycleOwner owner) {
        super.onDestroy(owner);

        this.mAdapter = null;
        this.mApplication = null;
    }
}

5.4.5. MVP Module

这里的 Module 可以提供当前业务逻辑所对应的 ViewModel 接口Contract中定义的接口) 的实现类, Model 需要AppComponent中提供的RepositoryManager来实现网络请求和数据缓存, 所以需要通过Component 依赖 AppComponent 来拿到这个对象

@Module
public abstract class MessagesModule {
    @Binds
    abstract MessagesContract.Model bindModel(MessagesModel model);

    @ActivityScope
    @Provides
    static RuntimePermissionUtil providePermissionUtil(MessagesContract.View view) {
        return new RuntimePermissionUtil(view.provideContext());
    }

    @ActivityScope
    @Provides
    static LQRAdapterForRecyclerView<NotificationMessages> provideMessagesAdapter(MessagesContract.View view){
        return new MessagesAdapter(view.provideContext());
    }
}

5.4.6. MVP Component

这里需要注意的是此 Component 必须依赖AppComponent, 这样才能提供 Model 需要的RepositoryManager, 提供 inject() 方法就能将ModuleAppComponent 中提供的对象注入到对应的类中, inject() 方法中的参数不能是接口。

@ActivityScope
@Component(modules = MessagesModule.class, dependencies = AppComponent.class)
public interface MessagesComponent {
    void inject(MessagesFragment activity);

    @Component.Builder
    interface Builder {
        @BindsInstance
        MessagesComponent.Builder view(MessagesContract.View view);
        MessagesComponent.Builder appComponent(AppComponent appComponent);
        MessagesComponent build();
    }
}

5.4.7. Dagger Scope

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

5.5. MVP 总结

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

6. 功能使用

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

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

  • GlobalConfigModule 需依赖于ConfigModule使用
public class GlobalConfiguration implements ConfigModule {
    @Override
    public void applyOptions(@NonNull Context context, @NonNull GlobalConfigModule.Builder builder) {
        if (!BuildConfig.DEV_MODE) { //Production 时, 让框架不再打印 Http 请求和响应的信息
            builder.printHttpLogLevel(RequestInterceptor.Level.NONE);
        }

        // 切换production和staging环境域名
        builder.baseurl(BuildConfig.API_BASE_URL)

                //可根据当前项目的情况以及环境为框架某些部件提供自定义的缓存策略, 具有强大的扩展性
//                .cacheFactory(new Cache.Factory() {
//                    @NonNull
//                    @Override
//                    public Cache build(CacheType type) {
//                        switch (type.getCacheTypeId()){
//                            case CacheType.EXTRAS_TYPE_ID:
//                                return new IntelligentCache(500);
//                            case CacheType.CACHE_SERVICE_CACHE_TYPE_ID:
//                                return new Cache(type.calculateCacheSize(context));//自定义 Cache
//                            default:
//                                return new LruCache(200);
//                        }
//                    }
//                })

                //可以自定义一个单例的线程池供全局使用
                .executorService(Executors.newCachedThreadPool())
                .gsonConfiguration((context1, gsonBuilder) -> {//这里可以自己自定义配置 Gson 的参数
                    gsonBuilder
                            .serializeNulls()//支持序列化值为 null 的参数
                            .enableComplexMapKeySerialization();//支持将序列化 key 为 Object 的 Map, 默认只能序列化 key 为 String 的 Map
                })
                .retrofitConfiguration((context1, retrofitBuilder) -> {
                    //这里可以自己自定义配置 Retrofit 的参数, 甚至您可以替换框架配置好的 OkHttpClient 对象
                    // (但是不建议这样做, 这样做您将损失框架提供的很多功能)

                    //需要 请求加密,响应解密时,传入 requestDataEncryptCallBack, responseDataDecryptCallBack
                    RequestDataEncryptCallBack requestDataEncryptCallBack = new RequestDataEncryptCallBackImpl(); // 请求加密
                    ResponseDataDecryptCallBack responseDataDecryptCallBack = new ResponseDataDecryptCallBackImpl(); //响应解密

                    retrofitBuilder.addConverterFactory(APIResponseDataConverter.create(
                            new GsonBuilder().serializeNulls().create(),
                            requestDataEncryptCallBack,
                            responseDataDecryptCallBack)
                    );
                })
                .addInterceptor(new AuthTokenInterceptor())
                .okhttpConfiguration((context1, okhttpBuilder) -> {//这里可以自己自定义配置 Okhttp 的参数
                    // HTTPS 详情请百度
                    // HttpsUtils.SSLParams sslParams; // https 自签名证书信息
                    // okhttpBuilder.sslSocketFactory(sslParams.sSLSocketFactory, sslParams.trustManager);
                    okhttpBuilder.writeTimeout(10, TimeUnit.SECONDS);
                    okhttpBuilder.connectTimeout(10, TimeUnit.SECONDS);
                    okhttpBuilder.readTimeout(10, TimeUnit.SECONDS);

                    // 不使用代理
                    okhttpBuilder.proxySelector(new SafeProxySelector());

                    // 通知栏 log 日志
                    if(BuildConfig.DEV_MODE){
                        okhttpBuilder.addInterceptor(new ChuckInterceptor(context1));
                    }
                })
                .rxCacheConfiguration((context1, rxCacheBuilder) -> {//这里可以自己自定义配置 RxCache 的参数
                    rxCacheBuilder.useExpiredDataIfLoaderNotAvailable(true);
                    //想自定义 RxCache 的缓存文件夹或者解析方式, 如改成 FastJson, 请 return rxCacheBuilder.persistence(cacheDirectory, new FastJsonSpeaker());
                    //否则请 return null;
                    return null;
                });
    }
}

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

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

如果需要使用 Rxjava 的全局错误处理,可以参考使用Demo中提供的GlobalErrorProcessor

public class GlobalErrorProcessor {
    /**
     * @param noticeErrorInfo true, 弹出错误提醒; false, 不弹
     * @param <T>             数据包装类型名,必须是BaseResponseMessage的子类
     * @return
     */
    public static <T extends APIResponse> GlobalErrorTransformer<T> processGlobalError(boolean noticeErrorInfo) {
        //onNextInterceptor
        Function<T, Observable<T>> onNextInterceptor =
                new Function<T, Observable<T>>() {
                    @Override
                    public Observable<T> apply(T it) throws Exception {
                        if(it.isOK()){
                            return Observable.just(it);
                        } else{
                            return Observable.error(new APIError.ServerError(it.getErrorCode(), it.getErrorMsg()));
                        }
                    }
                };

        //onErrorResumeNext
        Function<Throwable, Observable<T>> onErrorResumeNext =
                new Function<Throwable, Observable<T>>() {
                    @Override
                    public Observable<T> apply(Throwable error) throws Exception {
                        if (error instanceof HttpException) {
                            HttpException httpException = (HttpException) error;
                            return Observable.error(new APIError.ServerError(String.valueOf(httpException.code()), httpException.response().errorBody().string()));
                        } else if (error instanceof ConnectException
                                || error instanceof SocketTimeoutException) {
                            return Observable.error(new APIError.ConnectFailedException(error));
                        } else {
                            return Observable.error(error);
                        }
                    }
                };

        //onErrorRetrySupplier
        Function<Throwable, RetryConfig> onErrorRetrySupplier =
                new Function<Throwable, RetryConfig>() {
                    @Override
                    public RetryConfig apply(Throwable error) throws Exception {
                        if (error instanceof APIError.ServerError) {
                            return new RetryConfig();   //server error 不重试
                        } else if (error instanceof APIError.ConnectFailedException) {
                            // 默认重试1次
                            return new RetryConfig(new Suppiler<Single<Boolean>>() {
                                @Override
                                public Single<Boolean> call() {
                                    return Single.just(true);
                                }
                            });
                        } else if(error instanceof HttpProxyException){
                            return new RetryConfig();   //不重试
                        } else {
                            return new RetryConfig();   //其他异常都不重试
                        }
                    }
                };

        //onErrorConsumer
        Consumer<Throwable> onErrorConsumer = new Consumer<Throwable>() {
            @SuppressLint("CheckResult")
            @Override
            public void accept(Throwable error) throws Exception {
                if (error instanceof APIError.ServerError) {
                    APIError.ServerError serverError = (APIError.ServerError) error;
                    if (noticeErrorInfo) {
                        MainLooper.runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                ToastHelper.toastMessage(serverError.errorMsg, Gravity.CENTER, DToast.DURATION_SHORT);
                            }
                        });
                    }
                } else {
                    KLog.w("rx stream Exception", "其它异常: " + Log.getStackTraceString(error));
                }
            }
        };

        return new GlobalErrorTransformer<T>(
                onNextInterceptor,
                onErrorResumeNext,
                onErrorRetrySupplier,
                onErrorConsumer
        );
    }
}
  • Rxjava中使用
                mModel.getNotificationMessages(lastPageIndex, isEvictCache)
                          .subscribeOn(Schedulers.io())
              .compose(GlobalErrorProcessor.processGlobalError(true))
                  .observeOn(AndroidSchedulers.mainThread())
              .subscribe(new Consumer<APIResponseListData<NotificationMessages>>() {
                    @Override
                    public void accept(APIResponseListData<NotificationMessages> notificationMessagesAPIResponseListData) throws Exception {

                    }
                })

6.4. 全局请求加密,响应解密

public class GlobalConfiguration implements ConfigModule {
    @Override
    public void applyOptions(@NonNull Context context, @NonNull GlobalConfigModule.Builder builder) {
        builder.retrofitConfiguration((context1, retrofitBuilder) -> {
                    //这里可以自己自定义配置 Retrofit 的参数, 甚至您可以替换框架配置好的 OkHttpClient 对象
                    // (但是不建议这样做, 这样做您将损失框架提供的很多功能)

                    //需要 请求加密,响应解密时,传入 requestDataEncryptCallBack, responseDataDecryptCallBack
                    RequestDataEncryptCallBack requestDataEncryptCallBack = new RequestDataEncryptCallBackImpl(); // 请求加密
                    ResponseDataDecryptCallBack responseDataDecryptCallBack = new ResponseDataDecryptCallBackImpl(); //响应解密

                    retrofitBuilder.addConverterFactory(APIResponseDataConverter.create(
                            new GsonBuilder().serializeNulls().create(),
                            requestDataEncryptCallBack,
                            responseDataDecryptCallBack)
                    );
                });
    }
}
  • 请求数据加密
public class RequestDataEncryptCallBackImpl implements RequestDataEncryptCallBack {
    @Override
    public String doRequestDataEncrypt(ByteString rawRequestData) {
        return null;
    }
}
  • 响应数据解密
public class ResponseDataDecryptCallBackImpl implements ResponseDataDecryptCallBack {
    @Override
    public String doDataDecrypt(String rawResponse) {
        JsonObject encryptData = JsonElementHelper.jsonObjectValue(new JsonParser().parse(rawResponse));
        if(encryptData == null){
            return null;
        }

        Boolean encryptValue = JsonElementHelper.booleanValue(encryptData.get("encrypt"));
        if(encryptValue != null && encryptValue){
            //解密
            try {
                String encryptDataResponse = JsonElementHelper.stringValue(encryptData.get("data"));
                if(encryptDataResponse == null){
                    return null;
                }
                String decryptResponseBodyStr = new String(EncryptUtils.decryptBase64_3DES(encryptDataResponse.getBytes(),
                        secretKey.getBytes(),
                        "desede/CBC/PKCS5Padding",
                        iv.getBytes()), encoding);
                JsonElement decryptResponseBodyJsonElement = new JsonParser().parse(decryptResponseBodyStr);
                encryptData.add("data", decryptResponseBodyJsonElement);

                return encryptData.toString();
            } catch (Exception e) {
                KLog.e(e);
                //解密失败
                return null;
            }
        } else{
            //不需要解密
            return rawResponse;
        }
    }
}

6.5. 权限管理

适配 Android 6.0 权限管理,详细使用请看rx-permissions工具库

6.6. Gradle 配置启动 Debug 模式

android {
    defaultConfig {
        buildConfigField "boolean", "DEV_MODE", "false"
    }

    buildTypes {
            buildConfigField "boolean", "DEV_MODE", "true"
        }
}
  • 在代码中使用 (比如在 App 初始化时做一些初始化的设置)
    @Override
    public void applyOptions(@NonNull Context context, @NonNull GlobalConfigModule.Builder builder) {
        if (!BuildConfig.DEV_MODE) { //Production 时, 让框架不再打印 Http 请求和响应的信息
            builder.printHttpLogLevel(RequestInterceptor.Level.NONE);
        }
   }

6.7. ARouter

传送门 组件化开发,多模块解耦,全局拦截器,帮助 Android App 进行组件化改造的路由框架

6.7.1. 配置ARouter annotation

  • Java ```groovy javaCompileOptions {
      annotationProcessorOptions {
          arguments = [
                  AROUTER_MODULE_NAME: project.getName(),
                  // 可选是否生成路由文档
                   AROUTER_GENERATE_DOC: "enable"
          ]
      }
    
    }

annotationProcessor rootProject.ext.deps.aRouter.compiler


- Kotlin
```groovy
kapt {
    arguments {
        arg("AROUTER_MODULE_NAME", project.getName())
           arg("AROUTER_GENERATE_DOC", "enable")
    }
}

kapt rootProject.ext.deps.aRouter.compiler

6.7.2. 全局路径降级服务

服务只有被调用的时候才会触发初始化操作

  • 全局路径找不到时的降级服务
// 实现DegradeService接口,并加上一个Path内容任意的注解即可
@Route(path = "/xxx/xxx")
public class DegradeServiceImpl implements DegradeService {
@Override
public void onLost(Context context, Postcard postcard) {
    // do something.
}

@Override
public void init(Context context) {

}
}

6.7.3. 全局路径替换服务

// 实现PathReplaceService接口,并加上一个Path内容任意的注解即可
@Route(path = "/xxx/xxx") // 必须标明注解
public class PathReplaceServiceImpl implements PathReplaceService {
    /**
    * For normal path.
    *
    * @param path raw path
    */
    String forString(String path) {
            return path;    // 按照一定的规则处理之后返回处理后的结果
    }

        /**
    * For uri type.
    *
    * @param uri raw uri
    */
        Uri forUri(Uri uri) {
            return url;    // 按照一定的规则处理之后返回处理后的结果
        }
}

6.7.4. 拦截器

拦截器使用注解@Interceptor声明,priority值越小,优先级越高。内部必须在处理后调用onContinue or onInterrupt, 否则路由不会继续。

在每次navigation时都会被触发,拦截器会在ARouter初始化的时候异步初始化,如果第一次路由的时候拦截器还没有初始化结束,路由会等待,直到初始化完成。

@Interceptor(priority = 1)
public class LoginStateInterceptor implements IInterceptor {
    @Override
    public void process(Postcard postcard, InterceptorCallback callback) {
        /**
         * 不需要拦截,直接调用onContinue;
         * 如果需要拦截,跳转到登录页并调用onInterrupt。
         * eg:
         *   ARouterUtils.navigation(ARouterPathHub.APP_SIGN_IN);
         * 另外支持登录完成后再继续执行之前被拦截的跳转动作,需自行实现。
         * eg: 未登录状态点击通知中心,被拦截,跳转到登录页,登录成功后,再自动跳转到通知中心。
         */
        callback.onContinue(postcard);
//        callback.onInterrupt(needClientException)
    }

    @Override
    public void init(Context context) {

    }
}

6.7.5. APP多Tab首页如何组件化开发?

目前APP首页实现方式大多是单Activity多fragment架构, 如何拆分fragment?

拆分后各fragment组件如何实现通信

  • 获取fragment

    // 获取Fragment
    Fragment fragment = (Fragment) ARouter.getInstance().build("/test/fragment").navigation();
    

    获取fragment后将它加入到viewpager adapter中。

  • 可以用依赖注入绑定服务的方式,实现fragment代理服务对象,由fragment实现该服务方法供绑定方调用fragment内部方法。

    • 通过依赖注入解耦:服务管理(一) 暴露服务

      // 声明接口,其他组件通过接口来调用服务
      public interface HelloService extends IProvider {
          String sayHello(String name);
      }
      
      // 实现接口
      @Route(path = "/test/fragment/hello", name = "测试fragment服务")
      public class HelloFragment extends BaseFragment implements HelloService {
      
          @Override
          public String sayHello(String name) {
             return "hello, " + name;
          }
      
          @Override
          public void init(Context context) {
      
          }
      }
      
  • 通过依赖注入解耦:服务管理(二) 发现服务

    HelloService helloService = (HelloService) ARouter.getInstance().build("/test/fragment/hello").navigation();
    helloService.sayHello("Hi, I am super man!!!");
    

6.7.6. 使用注意事项

  • 多模块开发时,路径分组不能相同,建议加上模块名称区分。如: m_share/, m_push/
  • 项目为新页面加路径后,必须在每次运行时,Rebuild Project一次,以生成路由文档。

6.8. AppManager (管理所有的 Activity)

AppManager 用于管理所有的 Activity, AppManager 内部持有一个含有所有存活的 Activity(未调用 onDestroy) 的 List 集合, AppManager 封装有多种方法, 可以很方便的对它们进行任何操作, AppManager 是单例的, 可以通过静态方法 AppManager.getInstance() 直接拿到 AppManager 实例, 这样我们可以在整个 App 的任何地方对任何 Activity 进行全局操作。

6.9. FileProvider

自定义FileProvider4UtilCode继承了FileProvider,导入MVP-Base库时自动依赖。内部初始化了AppUtils和ActivityUtils, 可以很方便的拿到Application Context和当前处于栈顶的Activity。

<provider
          android:name="com.jpxx.base.util.FileProvider4UtilCode"
          android:authorities="${applicationId}.fileProvider"
          android:exported="false"
          android:grantUriPermissions="true"
          android:multiprocess="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_provider_paths" />
</provider>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <!--debug : FileProvider.SimplePathStrategy.getUriForFile(), Failed to find configured root that contains-->
    <!--修复没有获取外置存储卡读写权限时,FileProvider路径问题 详见 : com.jpxx.base.Config.getDataPath()-->
    <cache-path name="app_cache" path="." />
    <!--修复没有获取外置存储卡读写权限时,FileProvider路径问题 详见 : com.jpxx.base.Config.getDataPath()-->
    <files-path name="app_file" path="." />
    <!--代表外部存储区域的根目录下的文件 Environment.getExternalStorageDirectory()/目录  详见 : com.jpxx.base.Config.getDataPath()-->
    <external-path name="app_external" path="." />
    <!--sdcard根路径,防止以上路径都没匹配到,FileProvider crash-->
    <root-path name="android_files" path="."/>
</paths>

6.10. ActivityUtils

可以很方便的拿到当前处于栈顶的Activity, 比如在 App 请求网络超时时让最前端的 Activity 显示连接超时的交互页面 (这个逻辑不用写到当前请求的 Activity 里, 可以在一个单例类里做全局的统一操作)

6.11. 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) {
                    //日志打印
                }
            }

            @Override
            public void onTerminate(Application application) {
            }
        });
    }
}

6.12. ActivityDelegate 和 FragmentDelegate

实现思想请参考 科普文章

源码地址:http://172.16.28.234:8081/dev-plat/android/base

源码地址:http://sources.jpsycn.com/dev-plat/android/base.git

Copyright © jpsycn.com 2018 all right reserved,powered by Gitbook该文件修订时间: 2019-09-10 14:26:16

results matching ""

    No results matching ""