关于什么是组件化、为何要进行组件化以及实施组件化的基本流程网上一搜一大把,这里不作过多说明,不了解的话能够Google一下。这里主要记录一下组件化开发的一些心得和踩的一些坑。vue
在gradle.properties文件里定义一个常量IsBuildApp = false,表示是否把组件module做为单独的app运行。定义好了这个常量后,在项目的任何一个gradle文件里均可以读取到这个值,那么就用这个值来做为module组件是否须要单独运行的开关。java
// 在module组件的gradle里配置以下,gradle.properties 中的数据类型都是String类型,这里须要作一下转换
if (IsBuildApp.toBoolean()){
apply plugin: 'com.android.application'
}else {
apply plugin: 'com.android.library'
}
复制代码
咱们知道android的四大组件、权限等都是须要注册的,当module单独运行的时候,确定须要一个清单文件注册组件和申请权限,可是当module做为app的一个子组件存在的时候,清单文件是要合并到app的壳工程中的,这个时候若是每一个module都有本身的启动页面和自定义application的话,就会引发冲突。android
为了解决这个问题,那就须要根据module是否须要单独运行来配置不一样的清单文件。在java同级目录新建independent目录,在此目录下建立项目module须要单独运行的清单文件和application。而后在module的gradle文件里指定清单文件路径,代码以下:git
// 在android领域里指定清单文件的路径
sourceSets {
main {
if (IsBuildApp.toBoolean()) {
// 单独做为app运行的清单文件,这里能够添加启动页面、自定义application等。
manifest.srcFile 'src/main/independent/AndroidManifest.xml'
} else {
// 做为组件的清单文件
manifest.srcFile 'src/main/AndroidManifest.xml'
//release模式下排除independent文件夹中的全部Java文件
java {
exclude 'independent/**'
}
}
}
}
复制代码
这样配置完成之后,做为组件的清单文件是不能有本身的启动页面、application、appname等属性的,下面看一下完整的配置:github
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.article.demos.vue">
<application android:theme="@style/AppTheme">
<activity android:name=".ui.VueActivity" />
</application>
</manifest>
复制代码
下面看一下独立运行模式下的清单文件:api
// 做为独立app运行的清单文件,注意这里我设置了主题,否则的话会报错。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.article.demos.main">
<application android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
复制代码
独立运行的话,就和正常的app清单文件同样,要有启动页面,application标签能够添加label、icon、自定义application等,就很少说啦。微信
在commonlibrary中建立自定义application,由于其余的module都依赖这个module,因此其余的module均可以获取到这个全局的application。另外,组件在独立运行模式下的application,继承咱们自定义这个BaseApplication就能够了。由于咱们在release模式下,排除了全部independent文件夹下的java文件,因此做为组件运行时,并不会产生application的冲突,配置以下:架构
sourceSets {
main {
if (IsBuildApp.toBoolean()) {
manifest.srcFile 'src/main/independent/AndroidManifest.xml'
} else {
manifest.srcFile 'src/main/AndroidManifest.xml'
//release模式下排除independent文件夹中的全部Java文件
java {
exclude 'independent/**'
}
}
}
}
复制代码
为了不重复依赖三方库的问题,咱们的三方库依赖统一放在commonlibrary的module中,这样既能够避免重复依赖,又方便管理。而后咱们在app的module里,以下引用便可:app
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
if (IsBuildApp.toBoolean()) {
implementation project(':commonlibrary')
} else {
implementation project(':androidmodule')
implementation project(':vuemodule')
implementation project(':kotlinmodule')
implementation project(':javamodule')
}
}
复制代码
资源冲突主要是指各个module里的资源文件名冲突的问题,若是命名同样,合并的时候便会产生冲突。ide
解决冲突主要有两个解决方案,一个是约定规则,好比资源名约定都以module名开头。
方案二是经过gradle脚原本设置,在各个组件的gradle文件里添加以下代码:
resourcePrefix "module名称_"
复制代码
可是这种配置有限制,好比只能限定xml里的资源,因此并不推荐这种方式。
由于组件是相互隔离的,咱们并不能显式跳转,这里咱们选用阿里巴巴的Arouter路由跳转,项目的地址github.com/alibaba/ARo…。
这里须要特别说明一下,须要跳转的目标module须要引入arouter的注解处理器,不然没法处理router注解会出现路径不匹配的问题:
annotationProcessor 'com.alibaba:arouter-compiler:1.1.4'
复制代码
同时,改module的defaultconfig里也别忘记配置moduleName
javaCompileOptions {
annotationProcessorOptions {
arguments = [ moduleName : project.getName() ]
}
}
复制代码
跨moduel交互通常是指module间通讯和module间的相互调用。module间通讯这里选用eventbus,很简单,就不过多说明了。
下面说一下同级module直接的通讯,好比我在任何一个页面要调用loginModule里的微信登陆方法,由于各个module是互相独立的,互不依赖,想要直接调用基本不可能。目前网上发现有两种解决方案,一个是写一个反射工具类,经过反射获取到要调用的类,而后调用相应的方法。另外一个是经过commonModule作一下桥接,了解更多能够参考这里。不过感受用Arouter能更优雅的实现,下面具体讲一下利用arouter来实现。
首先,在公共module里建立一个接口IService
public interface IService extends IProvider{
String wxLogin();
}
复制代码
接口里定义一个微信登陆的伪代码,而后在咱们的登陆组件里,实现该接口并添加route注解
@Route(path = Constant.WX_LOGIN)
public class WxTest implements IService{
@Override
public void init(Context context) {
}
@Override
public String wxLogin() {
return "wxlogin";
}
}
复制代码
其中 Constant.WX_LOGIN是我定义的一个字符串常量
public static final String WX_LOGIN = "/wx/login";
复制代码
以上两步就把工做作完了,下面只须要在须要调用的页面调用登陆就好了。首先,咱们获取到IService
/**
* 推荐使用方式二来获取IService
*/
// IService iService = (IService) ARouter.getInstance().build(Constant.WX_LOGIN).navigation();
IService iService = ARouter.getInstance().navigation(IService.class);
复制代码
拿到IService后,就能够放心大胆的调用登陆方法就好了。
mBinding.btLogin.setOnClickListener(v -> {
String s = iService.wxLogin();
Toast.makeText(getContext(), s, Toast.LENGTH_SHORT).show();
});
复制代码
通常的项目首页都是一个activity和多个fragment组成。因为组件间的隔离,咱们在首页里怎么获取到其余组件里的fragment呢?开篇的两个参考文章分别使用了两种不一样的方式,有兴趣的朋友能够看看。各有利弊吧,一个是查询全部,太耗时。一个是直接反射获取,可是好像有点违背组件隔离,须要知道fragment的全路径。
这里我参考了《Android组件化架构》一书,使用arouter来获取。其实三种方式获取的原理同样,都是经过反射。咱们看一下arouter的注解的源码就知道:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.CLASS)
public @interface Route {……}
复制代码
能够看到Route注解的retention是CLASS,也是经过反射来获取。
(1)使用dataBinding的话,每一个module的gradle文件里都要加上dataBinding的支持,不然没法生成相应的binding类
// 每一个module都加上dataBinding的支持,不然没法生成相应的binding类
dataBinding {
enabled = true
}
复制代码
(2)java8的支持同样要每一个module都要单独配置
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
复制代码
(3)升级到as 3.1.2后,出现没法访问TaskStackBuilder的问题
检查一下你的support包,将你的support包更新到27或以上便可。
(4)若是使用有自定义注解annotation的话,若是编译报错 Annotation processors must be explicitly declared now...,那么在commonlibrary的gradle文件的defaultConfig里添加以下代码:
// Annotation processors must be explicitly declared now
javaCompileOptions { annotationProcessorOptions { includeCompileClasspath = true } }
复制代码
(5)若是你组件化开发,子module中没法使用butterknife的话,网上自行搜解决方案吧(🤦♀️)
关于为什么出现这个问题,推荐一篇博文R.java、R2.java是时候懂了
(6)其余问题本篇博客会持续更新……
2018年6.15更新………………………………………………………… 编译报错
Multiple dex files define Lcom/alibaba/android/arouter/routes/ARouter$$Group$$module
复制代码
通常网上说是依赖版本冲突,其实这个问题是不一样module之间有相同分组致使的问题,好比a模块 path = "/message/a",b模块 path = "/message/b",有相同的message分组,修改为不同的就能够了。
最近在用kotlin和java混合开发,发现原有java页面跳转新写的kotlin页面 arouter 页面跳转的时候报异常提示 There is no route match the path……,此时参考官方文档便可解决,
// 在kotlin的module中添加插件
apply plugin: 'kotlin-kapt'
// 依赖里 使用kapt 引用
dependencies {
compile 'com.alibaba:arouter-api:x.x.x'
kapt 'com.alibaba:arouter-compiler:x.x.x'
...
}
复制代码
遇到了一个很蛋疼的问题,在纯java写的module里经过arouter跳转到另外一个module里的kotlin页面的时候,发现setcontentview方法无效,页面什么都不显示。调试了半天,发现是页面的xml布局文件和一个空的xml布局文件重名了,致使kotlin页面加载了空页面的布局,在此记录一下,好尴尬。
最后附上完整的demo地址,若是对你有帮助麻烦start鼓励一下,你的鼓励是我前进的动力。