Android组件化+Arouter通讯
阅读原文时间:2021年04月21日阅读:1

前言:

实际开发中越是大型的项目,代码量越多,而AndroidStudio编译的速度越慢。除了抬高电脑配置外,如何提高程序员的开发效率越是迫在眉睫。除此之外,团队合作开发,合并代码也是一个头疼的问题。虽然可以使用svn/git来规避一些问题,但团队中一个人的代码出了问题,导致自己也是被迫停止开发也是可能的。

组件化:

项目代码臃肿的时候,通常考虑拆分代码,分层的方式。组件化是将项目按照业务拆分成一个个组件,进而达到解耦分层。一个组件化必须能够单独调试,集成编译,数据传输,UI转跳,生命周期,代码边界这六大特征。

通过一个组件化模型来进一步了解:

通过上图可知:

  • 空壳App : 负责管理各个业务组件,和打包apk,没有具体的业务功能;
  • main组件 :属于业务组件,指定APP启动页面、主界面
  • movie组件 :属于业务组件,用于显示电影列表
  • collection组件 : 属于业务组件,用于显示收藏的电影列表
  • commonLibrary(基类库) : 属于功能组件,提供业务组件需要的通用的功能,例如:网络请求,图片加载,mvp框架,工具类,每个组件的数据实体等等。

1.先配置依赖库和版本号统一的管理

工欲善其事必先利其器,不能因配置问题导致开发拖延,因此先配置好需要的各种库。

本项目使用到以下库

  • android 官方的常见库
  • rxjava和rxandroid 异步操作库
  • retrofit,okHtttp 网络库
  • glide 图片加载库
  • sqlbrite轻量级数据操作库
  • arouter路由通讯库

先创建一个config.gradle,编写以下代码:

// 第三方库,App的版本号的管理和组件模式的切换配置
ext{
    isAlone=false; //false:作为Lib集成存在, true:作为application组件存在

    android = [
            compileSdkVersion : 27,
            minSdkVersion :15,
            targetSdkVersion :27,
            versionCode :1,
            versionName :"1.0",
    ]
    libsVersion=[
            // App dependencies version
            supportLibraryVersion = "27.0.2",
            support_v4 = "27.0.2",
            arouter_api = "1.3.1",
            arouter_compiler = "1.1.4",
            sqlBrite="1.1.1",
            rxJava="1.3.0",
            rxAndroid="1.2.1",
            retrofit="2.3.0",
            converter_gson="2.3.0",
            adapter_rxjava="2.3.0",
            okhttp="3.8.0",
            logging_interceptor="3.8.0",
            glide="3.8.0",
    ]
    dependencies = [
            //android 官方库
            appcompatV7               : "com.android.support:appcompat-v7:$rootProject.supportLibraryVersion",
            support_v4                : "com.android.support:support-v4:$rootProject.support_v4",
            design                     : "com.android.support:design:$rootProject.supportLibraryVersion",
            cardview                   : "com.android.support:cardview-v7:$rootProject.supportLibraryVersion",
            palette                    : "com.android.support:palette-v7:$rootProject.supportLibraryVersion",
            recycleview               : "com.android.support:recyclerview-v7:$rootProject.supportLibraryVersion",
            annotations               : "com.android.support:support-annotations:$rootProject.supportLibraryVersion",
            //路由通讯
            arouter_api                : "com.alibaba:arouter-api:$rootProject.arouter_api",
            arouter_compiler          : "com.alibaba:arouter-compiler:$rootProject.arouter_compiler",
            // SQLBrite
            sqlBrite :"com.squareup.sqlbrite:sqlbrite:$rootProject.sqlBrite",
            //RxJava
            rxJava                      :"io.reactivex:rxjava:$rootProject.rxJava",
            rxAndroid                  :"io.reactivex:rxandroid:$rootProject.rxAndroid",
            // OkHttp
            okhttp                     :"com.squareup.okhttp3:okhttp:$rootProject.okhttp",
            logging_interceptor       :"com.squareup.okhttp3:logging-interceptor:$rootProject.logging_interceptor",
            //retrofit
            retrofit                   :"com.squareup.retrofit2:retrofit:$rootProject.retrofit",
            converter_gson            :"com.squareup.retrofit2:converter-gson:$rootProject.converter_gson",
            adapter_rxjava            :"com.squareup.retrofit2:adapter-rxjava:$rootProject.adapter_rxjava",
            //glide
            glide                     :"com.github.bumptech.glide:glide:$rootProject.glide",
    ]
}

接下来,在Project的build.gradle中添加以下代码,进行配置引用。

apply from:"config.gradle"

在每一个module组件中,进行依赖。

先在CommonLibrary基类库中builde.gradle进行依赖:

apply plugin: 'com.android.library'
android {
    compileSdkVersion rootProject.ext.android.compileSdkVersion

    defaultConfig {
        minSdkVersion rootProject.ext.android.minSdkVersion
        targetSdkVersion rootProject.ext.android.targetSdkVersion
        versionCode rootProject.ext.android.versionCode
        versionName rootProject.ext.android.versionName
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = [moduleName: project.getName()]
            }
        }
    }
}
dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    //Android Support ,任何一个module依赖的官方包都在这里配置
    api(rootProject.ext.dependencies.appcompatV7) {
        exclude module: "support-annotations"
        exclude module: "support-v4"
    }
    api(rootProject.ext.dependencies.support_v4) {
        exclude module: "support-annotations"
    }
    api rootProject.ext.dependencies.recycleview
    api rootProject.ext.dependencies.design
    api rootProject.ext.dependencies.annotations
    // arouter 依赖
    api rootProject.ext.dependencies.arouter_api
    //rxJava
    api(rootProject.ext.dependencies.rxJava) {
        exclude module: "rxAndroid"
    }
    api rootProject.ext.dependencies.rxAndroid
    //sqlbrite
    api rootProject.ext.dependencies.sqlBrite
    //retrofit
    api rootProject.ext.dependencies.retrofit
    api rootProject.ext.dependencies.converter_gson
    api rootProject.ext.dependencies.adapter_rxjava
    // okhttp
    api rootProject.ext.dependencies.okhttp
    api rootProject.ext.dependencies.logging_interceptor
    //glide
    api rootProject.ext.dependencies.glide
}

最后,每一个业务组件(例如:movie组件和collection组件)的build.gradle依赖:

android {
    compileSdkVersion rootProject.ext.android.compileSdkVersion
    defaultConfig {
        minSdkVersion  rootProject.ext.android.minSdkVersion
        targetSdkVersion rootProject.ext.android.targetSdkVersion
        versionCode rootProject.ext.android.versionCode
        versionName rootProject.ext.android.versionName

        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        if (rootProject.ext.isAlone) {
            //   组件模式下设置applicationId
            applicationId "com.xingen.collection"
        }
        javaCompileOptions {
                annotationProcessorOptions {
                    arguments = [moduleName: project.getName()]
                }
        }
    }
}

dependencies {
    implementation project(':CommonLibrary')
    //集成模式下需要编译器生成路由通信的代码
    annotationProcessor rootProject.ext.dependencies.arouter_compiler
}

整个项目的结构,如下所示:

2. 编写CommonLibrary基类库

将每个业务组件需要用到的通用操作都封装到一个通用的commonlibrary基类库中,然后直接在每个业务组件中依赖该库。
如下图所示,将一些常见的操作,数据库实体类,网络操作,图片加载,mvp架构等等。 除此之外,统一的style风格,权限也应该定义在CommonLibrary中。

除此之外,每个组件的Application问题。如何获取每个组件的通用Application?

写一个超类BaseApplication,将一些常见初始化操作放到里面.

public class BaseApplication  extends Application {
    private static  BaseApplication instance;
    @Override
    public void onCreate() {
        super.onCreate();
        instance=this;
        initRouter();
    }
    public static BaseApplication getInstance(){
        return instance;
    }
    private void initRouter(){
        if (BuildConfig.DEBUG) {
            //一定要在ARouter.init之前调用openDebug
            ARouter.openDebug();
            ARouter.openLog();
        }
        ARouter.init(this);
    }
}

写一个工具类,让每个组件可以获取到这个BaseApplication。

public class ContextUtils {
    public static BaseApplication getAppContext(){
        return BaseApplication.getInstance();
    }
}

然后让每个组件的Application去继承这个BaseApplication。以movie组件为例。

public class MovieListApplication extends BaseApplication{
    @Override
    public void onCreate() {
        super.onCreate();
    }
}

最后在组件的Androidmanifest.xml中去引用该定义的MovieListApplication。

3. 集成和组件模式的切换

组件化具备每个组件单独调试和集成的特点。

在config.gradle中设置一个标志,便于切换。

ext{
    isAlone=false; //false:作为Lib集成存在, true:作为application组件存在
}

组件的组件模式是指单独调试的Application项目,集成模式是指library项目,依赖到空壳app中。

这里以movie组件为案例,来介绍,在其build.gradle中。

//控制组件模式和集成模式
if (rootProject.ext.isAlone) {
    apply plugin: 'com.android.application'
} else {
    apply plugin: 'com.android.library'
}
android {
      defaultConfig {
        if (rootProject.ext.isAlone) {
            //   组件模式下设置applicationId
            applicationId "com.xingen.movie"
        }
      }
}

每次切换模式,AndroidStudio需要Sync 下,在运行对应的项目。

4. 一些常见问题处理

4.1 组件在集成模式和组件模式下的代码和AndroidManifest.xml

在组件模式下,需要单独调试,具备MainActivity和Applciation类。

这里以Movie组件为例,在java文件夹下创建一个debug文件夹,放入对应MainActivity和Applciation类。

在main文件夹下创建一个module文件夹,放入组件模式下的AndroidManifest.xml。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.xingen.movie">
<!-- 组件模式下使用的AndroidManifest.xml  -->
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:name="debug.MovieListApplication"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme.OverSystemBar">
        <activity android:name="debug.MovieListActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

而集成模式下的Androidmanifext.xml,不能有Application和主的Activity,不能声明App名称、图标等属性。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.xingen.movie">
    <!-- 集成模式下开发模式:
        1. 不能有Application和主的Activity
        2. 不能声明App名称、图标等属性
     -->
    <application android:theme="@style/AppTheme.OverSystemBar">
        <activity android:name="debug.MovieListActivity"></activity>
    </application>

</manifest>

最后在gradle配置,自由切换集成模式和组件模式下的不同代码。

android {
    sourceSets {
        main {
            //控制两种模式下的资源和代码配置情况
            if (rootProject.ext.isAlone) {
                manifest.srcFile 'src/main/module/AndroidManifest.xml'
            } else {
                manifest.srcFile 'src/main/AndroidManifest.xml'
                //集成开发模式下排除debug文件夹中的所有Java文件
                java {
                    exclude 'debug/**'
                }
            }
        }
    }

}

最终,movie组件项目的结构,如下所示:

4.2 资源命名冲突

若是多个组件项目中都具备相同名字的图标,那么集成打包App会报错。

最好的解决方式:就是每个组件的资源文件(图片,xml)都约定好以什么组件名为前缀。

4.3 library依赖问题

第三方依赖库可能到引入重复包问题,通过exclude module去除指定重复包,具体详情查看1.先配置依赖库和版本号统一的管理

5. 阿里Arouter通讯

模块之间的通讯通过Arouter来实现。

例如,main模块中要获取到movie模块中的一个Fragment类,但两个模块又不能直接依赖。通过Arouter的暴露服务来通讯可以解决。

先在CommonLibraray中定义一个IProvider子类,定义一个服务.

public interface MovieListProvider extends IProvider {
    Fragment createFragment();
}

定义该服务的路径:

public final class RouterPath {
    /**
     * 电影列表的路径
     */
   public static  final  String PATH_MOVIE_LIST="/MovieService/movie_list";
}

然后在Movie组件中,实现该服务。

@Route(path = RouterPath.PATH_MOVIE_LIST)
public class MovieListProviderImpl implements MovieListProvider {
    @Override
    public Fragment createFragment() {
        MovieListFragment fragment=MovieListFragment.newInstance();
        MovieListPresenter presenter=new MovieListPresenter(fragment, NetClient.getInstance(), SQLClient.getInstance());
        return fragment;
    }
    @Override
    public void init(Context context) {

    }
}

最后在main组件中获取该服务对象,从而获取到movie组件中的fragment。

public class MainActivity extends BaseActivity implements NavigationView.OnNavigationItemSelectedListener {
    @Autowired
    MovieListProvider movieListProvider;

    @Override
    public void init(Bundle savedInstanceState) 
        ARouter.getInstance().inject(this);

        Fragment fragment = movieListProvider.createFragment();
        getSupportFragmentManager().beginTransaction().add(R.id.movielist_content_layout, fragment).commit();
    }

}

当然,还有很多关于Arouter的用法,请点击查看Arouter文档

6. 运行效果

单独Movie组件效果:

全部组件集成到空壳App的效果:

本项目的地址:https://github.com/13767004362/ModulePattern


资源参考