Android合并两个应用项目
最近工作中有个需求:需要合并两个基本无关联的应用,暂且用A和B区分,A作为主应用,需要将B的功能全部合并到A中,并且尽量减少对A的侵入。
在经过一周多的尝试研究后基本完成了该需求,下面记录下我的实践过程。
1.因为要减少侵入,保证A的已有功能稳定,我考虑将B作为aar包,然后让A去依赖B实现。
2.合并成一个应用后,B的部分页面需要跳转至A的页面,由于合并后的工程逻辑是A依赖B,B并不依赖A,所以使用startIntent()方式跳转无法完成,
因为B不能调用A的类或方法。考虑使用路由方式Arouter,具体实现方式跳过,网上很多资料。
有一点需要注意,如果页面是kotlin写的,那么整个A的gradle文件需要添加
apply plugin: 'kotlin-kapt'
并将
annotationProcessor 'com.alibaba:arouter-compiler:1.5.2'
修改为
kapt 'com.alibaba:arouter-compiler:1.5.2'
否则会报错
ARouter::There is no route match the path
3.基本想法有了,开始操作。首先B如何制作成aar包呢?B本身的项目结构很复杂,B有十来个的module(其中部分module是单纯的java项目,这个给后来打包aar带来了不小麻烦)依赖,
而且这些module还存在链式依赖引用。
简单整理了下它的构成,这还不包括里面的so库依赖

看着就头疼,只有硬着头皮改造。
3.1首先选择打包aar的方法,因为要将所有module进行一次封装打包,考虑使用fataar,使用说明看这里https://github.com/kezong/fat-aar-android/blob/master/README_CN.md。
3.2既然是作为aar包导入其它应用,所以我们需要将B的gradle中的apply plugin: 'com.android.application'改成apply plugin: 'com.android.library',由应用改成library,否则没法打包aar。
3.3根据3.1中的使用说明,我们需要将所有需要打包进aar包中的项目用emed关键字在gradle中进行声明,包括这些module的远程依赖
embed project(':MHModules:mhapp')
embed project(':MHModules:mhdevice-manager')
embed project(':MHModules:mhlinphone')
embed project(':MHModules:mhsdk')
embed project(':MHModules:mhwatch')
embed project(':MHModules:mhconfig-android')
embed project(':MHModules:mhutil-android')
//所有module的远程依赖都需要emed声明,否则最后生成的aar包中不会有这些包或类
embed "io.reactivex.rxjava2:rxjava:2.2.6"
...
细心的人可能已经发现,我这里embed的本地module似乎并不完全。为什么呢?因为其它几个本地mudule一旦同样方法依赖,就会编译报错。查看对比了下它们的差别,发现它们在gradle中声明的是
apply plugin: 'java'而其它正常能embed的声明是apply plugin: 'com.android.library'。
最开始我想着要不也改成apply plugin: 'com.android.library',然后编译器会提示我缺少AndroidManifest.xml,我又给这些项目都增加了AndroidManifest.xml文件,但是编译的时候又会提示找不到JDK里的方法或类,
比如找不到javax.lang.model.element。
这里有个疑问,为啥我android项目找不到JDK里的类呢,但是换成apply plugin: 'java'就能正常找到并通过编译,他们配置的是同一个JDK呀?简单搜了下没找到原因,只能先想其它方式。
3.4后来我发现虽然这几个不能打包进aar,但是可以正常生成jar包(build/lib目录下),可以将这些生成的jar包放到A中进行依赖。
3.5在aar包的改造打包过程中,有些资源名称会冲突,比如上图中mhutil、mhlinphone中可能都存在名字为a.png的图片。几番搜索发现,可以在项目的gradle.properties中配置android.disableResourceValidation=true,
它的作用是可以忽略资源冲突的编译错误,程序会采用第一个找到的同名资源作为实际资源。另外比如颜色资源名重复(比如在各自的colors.xml中都定义了black的颜色值),可以通过给各自模块的资源名加前缀方式规避,比如**_black。
3.6另外B中如果存在ButterKnife的引用,需要全部去掉,改成findviewbyid的方式,查了下@BindView的属性必须是一个常数,也就是说library module编译的时候,R文件中所有的数据并没有被加上final,也就是R文件中的数据并非常量。
当然想保留ButterKnife也有其它解决方式,我选择不使用,避免其它bug。同时也不让使用switch/case,编译器提示用if/else替换,
这里我也很疑惑,我在另一个项目用了kotlin的when,打包aar为啥就是正常可用的?
打包问题至此暂时解决了(其实中间遇到很多问题也尝试了很多方法,此处只总结有参考学习价值的地方)
4.A中有自定义的application,B同样有,我们知道在项目的AndroidManifest中只能指定一个application,如何解决呢?如果把B的application实现都拷贝进A中实现,岂不是侵入很强,后期如果需要剥离这部分业务的话也是麻烦。
几经搜索,发现有一种静态代理的方式。
具体方式是定义一个接口类,比如我的实现
public interface IApplicationListener { public abstract fun onProxyAttachBaseContext(base: android.content.Context): kotlin.Unit
public abstract fun onProxyConfigurationChanged(newConfiguration: android.content.res.Configuration): kotlin.Unit
public abstract fun onProxyCreate(): kotlin.Unit
public abstract fun onProxyTerminate(): kotlin.Unit
}
B的application去实现该接口,然后在各接口回调中调用B自己的对应生命周期方法。在A的application中同样定义一个接口实现的对象指向B,
在A的各生命周期中调用IApplicationListener的回调方法
(因为此时接口实现类是B的application,所以实现了B的各生命周期同A一致)
5.如何解决so库重复依赖冲突(非必须配置,如果项目中存在so冲突)
android{
packagingOptions {
pickFirst '**/*.so'
}
}
6.AB合并后,部分页面执行findviewbyid得到为空,定位了很久,发现是AB两个项目中有同名xxx.xml文件,合并后,系统不能找到对应的布局文件,
然后去findview时找不到导致的。解决办法:将其命名改为不同。
浙公网安备 33010602011771号