Skip to content

Latest commit

 

History

History
381 lines (268 loc) · 12 KB

组件化架构的搭建.md

File metadata and controls

381 lines (268 loc) · 12 KB

一、组件化概述

1.为什么要做组件化?

组件化会从项目中剥离出功能组件业务组件

功能组件是一个有独立功能的组件,例如分享组件、日志组件、工具类组件等,这些组件与业务无关,但可以为业务组件提供某些功能。拆分出的功能组件可以被不同的业务组件依赖,也可以被其他项目的业务组件依赖。

业务组件需要根据项目的具体情况进行拆分,将一个相对独立的业务拆分成一个单独的模块。每个业务组件都是一个独立的工程,可独立编译运行,且不同业务组件之间没有依赖关系。在集成打包时所有的业务组件会被打包成一个APK。

组件化的项目架构如下图所示:

图片1

app壳工程是一个空模块,没有任何业务逻辑。在开发时,各个业务组件能够作为一个APP独立运行,而在打包时,APP壳工程模块依赖所有业务组件,最终打包成一个完整的Apk。可以通过一个参数配置来表示各个模块是集成打包,还是独立运行。

组件化带来的好处

  • 通过模块拆分降低项目耦合度。每个业务组件互不依赖,开发时也不会相互影响,测试时也只需要关注改动的组件,避免全盘回归。因此可以极大的提升开发效率。
  • 多团队共用组件,节省开发和维护成本。拆分出来的功能组件具有独立的功能,提交到自己内部的maven私服后可以像依赖第三方库一样方便。如果公司项目有多个项目,那么功能组件可以被多个项目依赖,避免每个项目都单独维护。
  • 各组件单独开发,节省编译时间,提高工作效率。每个业务组件都可以单独的作为一个APP运行,开发时只需要编译运行单个组件即可,编译速度更快。

2.组件化架构要解决的问题

  • 如何让业务组件既能独立运行又能集成运行?

  • 各个没有依赖关系的业务组件之间如何实现页面跳转?

  • 各个没有依赖关系的业务组件之间如何获取Fragment的实例?

  • 各个没有依赖关系的业务组件之间如何实现数据通信?

二、组件化架构配置

1. 集成打包参数配置

在项目跟目录的build.gradle下添加一个isBundleMode的boolean类型的参数,true表示各个模块集成打包,false表示各个模块可以独立运行。

ext {
    isBundleMode = true
}

2. APP壳工程模块的依赖配置

APP壳工程模块只有在所有模块集成打包时才有用,因此,只有当isBundleMode为true时才会依赖所有子模块。相关配置如下:

dependencies {
    if (rootProject.ext.isBundleMode) { // 集成模式依赖所有子模块
        implementation project(':home')
        implementation project(':publish')
        implementation project(':find')
        implementation project(':user')
        implementation project(':main')
    } else { // 子模块独立运行模式,无需依赖子模块
        implementation project(':common')
    }
}

3. 子模块的gradle配置

子模块即可以作为library集成打包到APP模块,也可以作为一个单独的APP独立运行。即可以通过isBundleMode参数来决定子模块是library还是application。在子模块的build.gradle中添加如下配置:

if (Boolean.valueOf(rootProject.ext.isBundleMode)) {
    apply plugin: 'com.android.library'
} else {
    apply plugin: 'com.android.application'
}

除此之外,如果是作为一个APP单独运行,还需要添加applicationId,因此可以添加如下配置。

android {
    defaultConfig {
        if (!rootProject.ext.isBundleMode) {
            applicationId "com.zhpan.cypoem.home"
        }
		// ...
    }
}

另外,由于application与library模式下AndroidManifest文件配置也是不同的,因此每个模块都需要两个AndroidManifest文件,一个作为单独运行的配置文件,一个作为集成运行的模块。

可以在src/main下边新建一个module目录,然后添加一个AndroidManifest文件作为独立运行的配置,如下:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.zhpan.module_me" >

    <!--android:name属性——是用来设置所有activity属于哪个application的,默认是android.app.Application。-->
    <application
        android:name="com.zhpan.library.base.BaseApp"
        android:allowBackup="true"
        android:label="Me"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name="com.zhpan.module_me.MeActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

    </application>
</manifest>

原本AndroidManifest文件作为集成环境下的配置,如下:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.zhpan.module_me">

    <application>
        <activity android:name=".MeActivity"/>
    </application>

</manifest>

接下来在build.gradle中根据isBundleMode参数加载不同的AndroidManifest清单文件,如下:

android {
    sourceSets {
        main {
            if (rootProject.ext.isBundleMode) {
                manifest.srcFile 'src/main/AndroidManifest.xml'
                java {
                    //排除java/debug文件夹下的所有文件
                    exclude '*module'
                }
            } else {
                manifest.srcFile 'src/main/module/AndroidManifest.xml'
            }
        }
    }

}

这样便完成了组件化模块独立运行与集成运行的配置,通过修改isBundleMode参数即可实现。

三、组件间通信问题

不同模块间Activity的跳转、加载其他模块的Fragment以及模块间的通信都可以通过ARouter来实现。关于ARouter的配置这里不再赘述,详情参考ARouter主页。

1. 模块间Activity的跳转

为“发现”模块中的Activity设置Route路径,如下:

@Route(path = ACTIVITY_FIND)
public class FindActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_find);
    }
}

在其他模块通过ARouter以及对应的Path即可跳转到FindActivity页面。如下:

ARouter.getInstance()
  .build(RoutingTable.ACTIVITY_FIND)
  .navigation();

2.加载其他模块的Fragment

在“发现”模块为Fragment添加Route路径,如下:

@Route(path = RoutingTable.FRAGMENT_FIND)
public class FindFragment extends BaseFragment {
    @Override
    protected int getLayout() {
        return R.layout.fragment_find;
    }

    @Override
    protected void initTitle() {

    }

    @Override
    protected void initView(Bundle savedInstanceState) {

    }

    public static FindFragment getInstance() {
        return new FindFragment();
    }
}

接着在其他模块可以通过ARouter与对应的path获取这个Fragment的实例,代码如下:

BaseFragment findFragment = (BaseFragment) ARouter.getInstance().build(RoutingTable.FRAGMENT_FIND).navigation();

3. 组件间交换数据

子模块之间避免不了的需要进行数据交换,比如用户模块提供用户信息,其他模块需要调用用户模块来获取用户信息。子模块之间没有相互依赖,因此需要通过其他方法来实现模块间的数据交换。

(1) 使用ARouter实现组件间数据交换

ARouter也提供了组件间数据交换的能力,实现方式如下。

首先在common模块定义一个提供用户信息的接口,并继承IProvider。如下:

import com.alibaba.android.arouter.facade.template.IProvider;

public interface IARouterUserService extends IProvider {

  String getUserId();

  String getUserName();

  int getUserAge();
}

然后在用户模块添加上述接口的实现,如下:

@Route(path = RoutingTable.USER_DATA)
public class IARouterUserServiceImpl implements IARouterUserService {
  @Override public String getUserId() {
    return UserHolder.getUserId();
  }

  @Override public String getUserName() {
    return UserHolder.getUserName();
  }

  @Override public int getUserAge() {
    return UserHolder.getUserAge();
  }

  @Override public void init(Context context) {

  }
}

注意这个实现类需要使用Route注解,并设置path。在这个类的各个方法中返回用户的信息。

接着,在其他模块的Activity或者Fragment中获取IARouterUserServiceImpl的实例。需要注意的是在使用时一定要通过 ARouter.getInstance().inject(this); 进行注入。

public class HomeFragment extends BaseFragment {

  IARouterUserService userInfo;
  // 可以通过Autowired进行自动初始化
  @Autowired(name = RoutingTable.USER_DATA)
  IARouterUserService userInfo2;

  @Override
  protected void initView(Bundle savedInstanceState) {
    ARouter.getInstance().inject(this);
    // 手动初始化
    userInfo = (IARouterUserService) ARouter.getInstance()
    .build(RoutingTable.USER_DATA)
    .navigation();;
    userInfo.getUserName();
		userInfo2.getUserAge();
  }

}

(2) 使用SPI实现组件间通信

首先在common组件中定义一个获取用户信息的接口,如下:

public interface ISPIUserService {

  String getUserId();

  String getUserName();

  int getUserAge();
}

然后在user模块实现这个接口,并返回用户信息。如下:

public class ISPIUserServiceImpl implements ISPIUserService {

  @Override public String getUserId() {
    return UserHolder.getUserId();
  }

  @Override public String getUserName() {
    return UserHolder.getUserName();
  }

  @Override public int getUserAge() {
    return UserHolder.getUserAge();
  }
}

接着需要在APP模块下注册服务。在src/main目录下添加resources/META-INF/services目录,并且添加一个文件,这个文件的名字必须为com.zhpan.library.ISPIUserService,即ISPIUserService的全路径名,文件中添加 com.zhpan.module_me.ISPIUserServiceImpl 一行配置,即ISPIUserServiceImpl这个类的全路径名。

完成配置后便可以通过JDK提供的API ServiceLoader 来获取ISPIUserServiceImpl的实例了,对ServiceLoader进行如下封装:

public class ServiceLoaderManager {
    public static  <T> T provide(Class<T> tClass) {
        // 返回一个ISPIUserService实例的集合
        Iterator<T> iterator = ServiceLoader.load(tClass).iterator();
        if(iterator.hasNext()) {
            // 这里只取第一个实例
            return iterator.next();
        }
        throw new IllegalStateException(
                "Can not find the implement class of " + tClass.getCanonicalName());
    }
}

最后,便可以在common模块通过ServiceLoaderManager获取到ISPIUserServiceImpl的实例了。添加一个UserInfoTools的工具类,如下:

public class UserInfoTools {
	// 获取用户名称
  public static String getUserName() {
    return getUserService().getUserName();
  }
	// 获取用户ID
  public static String getUserId() {
    return getUserService().getUserId();
  }
  // 获取用户年龄
  public static int getUserAge() {
    return getUserService().getUserAge();
  }
  // 获取ISPIUserServiceImpl实例
  private static ISPIUserService getUserService() {
    return ServiceLoaderManager.provide(ISPIUserService.class);
  }
}

由于UserInfoTools是位于common模块中,所有依赖了common模块的子模块都可以通过UserInfoTools获取到用户信息。

(3) 使用其他第三方库实现

例如APPJoint等第三方库都可以实现类似的功能。

组件化架构demo