组件化架构

组件化是什么?

就是将一个app分成多个Module,每个Module都是一个组件(也可以是一个基础库供组件依赖),开发的过程中我们可以单独调试部分组件,组件间不需要互相依赖,但可以相互调用,最终发布的时候所有组件以lib的形式被主app工程依赖并打包成1个apk。

组件化的优点:

加快编译速度:每个业务功能都是一个单独的工程,可独立编译运行,拆分后代码量较少,编译自然变快。

提高协作效率:解耦使得组件之间此互不打扰,实现代码隔离;团队中每个人负责自己的组件,降低团队成员熟悉项目的成本,只需熟悉责任组件即可;对测试来说,只需重点测试改动的组件,而不是全盘回归测试。

功能复用:组件类似我们引用的第三方库,只需维护好每个组件,一建引用集成即可。

除此之外,还能实现一些组件间的AOP:比如借助拦截器,可以动态给组件调用添加不同的中间处理逻辑(登录态,埋点等)

组件化方案:

总的来讲可以分为两类:

基于 Uri 设计的路由(代表性的有阿里的ARoute,美团的WMRoute

工作原理类似于仓库管理员:大家先把类全部放到仓库中,有人需要的时候,仓库管理员就根据所提供的字符串找出存放在仓库中的类。

基于总线方案(CC

工作原理类似于电话接线员(中介者模式),调用组件时,总线根据字符串找到对应的组件类并将调用信息转发给该组件类,组件执行完成后再通过组件总线将结果返回给调用方。就像调用后台接口一样

优缺点:

路由的优点:配置简单,侵入性低,使用者多,多端兼容性好(指的是对URI的兼容)。

路由的缺点:接口下沉,默认生成映射表的性能稍低(通过注解+扫描所有dex文件+反射)推荐使用插件的方式(Transform API+ASM)来加快初始化的速度。

CC的优点:生成映射表的效率高(Transform API+ASM),跨进程,改造旧工程较容易。

CC的缺点:配置复杂(好在文档很详细),侵入性高,多端兼容性一般(指的是对URI的兼容),不更新了。

方案选择

为了更好地扩展、兼容,决定采用路由方案。

组件化框架图

具体实现

要解决的问题如下:

业务组件如何实现单独运行调试?

业务组件间如何实现页面的跳转?

业务组件间如何实现组件间通信/方法调用?

业务组件间如何获取fragment实例?

业务组件如何获取Application实例、如何获取Application onCreate()回调(用于任务初始化)?

业务组件间资源命名冲突问题?

业务组件单独运行调试

众所周知的两种gradle插件:

Application 插件,id: com.android.application

Library 插件,id: com.android.library

第一个用于生成apk,第二个用于生成aar。

那么我们可以在gradle.properties中自定义一个参数来控制在单独调试的时候用application插件,在集成的时候用library插件

//gradle.properties

#组件独立调试开关, 每次更改值后要同步工程

isModule = false

//build.gradle

//注意gradle.properties中的数据类型都是String类型,使用其他数据类型需要自行转换

if (isModule.toBoolean()){

    apply plugin: 'com.android.application'

}else {

    apply plugin: 'com.android.library'

}

作为一个APP肯定少不了APPID以及启动activity,我们一样可以通过isModule来控制:

//build.gradle (module_A)

android {

...

    defaultConfig {

...

        if (isModule.toBoolean()) {

            // 独立调试时添加 applicationId ,集成调试时移除

            applicationId "com.codemao.grow.A"

        }

...

    }

    sourceSets {

        main {

            // 独立调试与集成调试时使用不同的 AndroidManifest.xml 文件

            if (isModule.toBoolean()) {

                manifest.srcFile 'src/main/debug/AndroidManifest.xml'

            } else {

                manifest.srcFile 'src/main/AndroidManifest.xml'

            }

        }

    }

...

}

其中debug/AndroidManifest.xml大概长这样:

//moduleManifest/AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"

    package="com.codemao.grow.A" >

    <application android:name=".ModuleApplication"

        android:allowBackup="true"

        android:label="A"

        android:theme="@style/Theme.AppCompat">

        <activity android:name=".ModuleActivity">

            <intent-filter>

                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />

            </intent-filter>

        </activity>

    </application>

</manifest>

AndroidManifest.xml大概是这样的:

<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android"

    package="com.codemao.grow.A">

    <application>

        <activity android:name=".ModuleActivity"></activity>

    </application>

</manifest>

业务组件间的页面跳转

通过路由(跳转路由)来实现,这里以Aroute来举例:

// 在支持路由的页面上添加注解(必选)

// 这里的路径需要注意的是至少需要有两级,/xx/xx

@Route(path = "/test/activity")

public class YourActivity extend Activity {

    ...

}

ARouter.getInstance().build("/test/activity").navigation();

更详细的使用快戳我

业务组件间的通信

一样是通过路由(服务路由)来实现:

// 声明接口,其他组件通过接口来调用服务

public interface HelloService extends IProvider {

    String sayHello(String name);

}

// 实现接口

@Route(path = "/yourservicegroupname/hello", name = "测试服务")

public class HelloServiceImpl implements HelloService {

    @Override

    public String sayHello(String name) {

    return "hello, " + name;

    }

    @Override

    public void init(Context context) {

    }

}

HelloService helloService = ARouter.getInstance().navigation(HelloService.class);

或者

HelloService helloService=(HelloService)ARouter.getInstance().build("/yourservicegroupname/hello").navigation();

更详细的使用快戳我

业务组件间获取Fragment

还是通过路由

// 获取Fragment

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

更详细的使用快戳我

业务组件获取Application实例并感应其生命周期

通常我们会在Application的onCreate中做一些初始化工作,我们可以将所有初始化都放在APP壳工程里进行,可我们希望是壳工程和组件间实现代码隔离,由组件各自实现自己的初始化工作。

这里我们有三种种方法:

方法一:通过服务路由

例如:

@Route(path = "/MainModule/MainModule", name = "模块初始化")

public class MainModule implements IProvider {

    private static final String TAG = "MainModule";

    @Override

    public void init(Context context) {

        Log.e(TAG, "MainModule调用了初始化" );

    }

}

方法二:通过注册一个ContentProvider

以Lifecycle为例:

<provider

    android:name="android.arch.lifecycle.LifecycleRuntimeTrojanProvider"

    android:authorities="xx.xx.xxxx.lifecycle-trojan"

    android:exported="false"

    android:multiprocess="true" />

public class LifecycleRuntimeTrojanProvider extends ContentProvider {

    @Override

    public boolean onCreate() {

        LifecycleDispatcher.init(getContext());

        ProcessLifecycleOwner.init(getContext());

        return true;

    }

    @Nullable

    @Override

    public Cursor query(@NonNull Uri uri, String[] strings, String s, String[] strings1,

            String s1) {

        return null;

    }

    ...

  //return null or 0;

}

我们都知道ContentProvider的onCreate的调用时机介于Application的attachBaseContext和onCreate之间,Provider的onCreate优先于Application的onCreate执行,并且此时的Application已经创建成功,而Provider里的context正是Application的对象

方法三:通过AppLifecycle插件

使用详情戳链接

业务组件间资源命名冲突

通过在gradle中设置:

android {

resourcePrefix "moduleA_"

}

注意:resourcePrefix 只能限定Xml资源,并不能限定图片资源,图片资源仍需要自身注意设置前缀名。

最后

为了不使用接口下沉来暴露组件提供的服务,我们为每个组件创建一个公开的export组件,供其它组件调用。

就像:

这样做会使组件更加内聚,而不是将所有组件的服务接口都下沉到同一个export组件中。

组件化后的工程结构大概是这样:

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容