android组件化打包module遇到的问题总结(打包成aar)

本文中关于 api 和 implementation的说法应该是错的,正确的说明请看:

https://blog.csdn.net/tantion/article/details/86623969

-----------------------------------------------------------------------------------

原文:https://blog.csdn.net/u012345683/article/details/80118601 

在一开始接触APICLoud平台的时候我是拒绝的,因为对于一个有着熟练的java技能,掌握着老旧设计模式的人来说,这种平台简直就是对于程序员这个职业的侮辱。第一个原因是APICLoud平台剥离了原生开发和html+js开发的职责,使得原生开发的职业方向越来越窄,开发中的地位也逐渐下降;第二个原因是,这个平台完全是无脑式开发,所有的js框架都是封装好的,只需要像jQuery一样直接调用就好了,APICLoud框架的设计使得前端开发者开发起来特别简单,前端如果转apicloud开发成本会很低,完全用不着设计模式。
现在想想之前的想法过于偏激,社会就是越来越现代化,开发语言只是一种工具,人都说君欲善其事,必先利器。我觉得很有道理,这些平台的发展就是为了减少开发人员的代码编写工作,集中注意力在业务逻辑上,也可以使得企业能够快速迭代产品,从而减少运行成本,安卓/ios开发一直都饱受着混合开发和RN开发的折磨,现在这个APICLoud平台的出现我觉得是混合开发平台成熟的标志,因为该平台不仅仅是混合开发的框架,而且包含着混合开发的所有安卓/ios的第三方sdk的商店,也就是api服务商业化模式,这种模式对于开发人员的冲击是相当大的,当sdk商店中已经集齐了所有满足日常的sdk的时候,原生人员也就失业了,因为所有的基础功能商店已经有,前端只需要配置进apicloud框架就可以了,企业的开发人员根本不需要重复造轮子,可以把大量的时间放在业务逻辑上。
在学习安卓原生的过程中,让我明白了很多道理,那就是这个世界唯一不变的是改变,唯有不断的改变和学习才能跟的上时代的步伐,授人以鱼不如授人以渔,学习过程中也积累了很多学习经验,学习效率明显得到了提高,现在我马上要转到小程序和网页编写了,想想也有点小激动呢!经过一个礼拜的学习,基本上可以做一些页面了,路漫漫其修远兮,吾将上下而求索。

在自定义模块的时候出现的问题


APICLoud的平台自定义模块上传需要的是aar包,而aar包的特点就是内部会打包jar和jni里面的so文件,但是不会把gradle的远程依赖下载并且打包进aar文件中,这就需要手动下载complie所引用的远程库了。

什么是gradle?


 Gradle是一个基于Apache Ant和Apache Maven概念的项目自动化构建工具。它使用一种基于Groovy的特定领域语言(DSL)来声明项目设置,抛弃了基于XML的各种繁琐配置。面向Java应用为主。当前其支持的语言限于Java、Groovy、Kotlin和Scala。

他的功能是根据编写的gradle task来完成项目构建任务的,我们android studio中的gradle文件就是用来编译我们的安卓项目的,其中包含插件导入,利用插件辅助编译,多个编译文件合并操作,引入远程仓库等等,gradle的功能非常强大。
gradle官网可以查看用法
利用gradle来完成打包任务

1、下载远程库

电脑上安装一个gradle软件,配置好环境变量。
接下来就可以进行下载任务了。
参考:点击这里
使用此方法下载Jar包的前提是已经配置好了Gradle的环境了,配置好的标志是在终端输入gradle不提示command not found。
1. 编写build.gradle文件代码:

apply plugin: 'java'
repositories {
    mavenCentral()
    jcenter()
    google()
}
dependencies {
    compile 'io.reactivex.rxjava2:rxjava:2.1.3'
}

task copyJars(type: Copy) {
  from configurations.runtime
  into 'lib' // 目标位置
}


2. 执行下载的指令
gradle copyJars


如果上述代码运行之后,发现不能下载成功,那么更换仓库地址,使用国内镜像下载:

 maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' }  //阿里镜像
 maven{ url 'http://maven.aliyun.com/nexus/content/repositories/jcenter'}  //阿里镜像
 mavenCentral()
 jcenter()
 google()


2、合并依赖包

1> libs下面的包合并
一般我都是用代码编写一个task,把所有dependencies{}括号包含的语句拆解成一句一句,因为可能引用里面版本不一致,就会导致下载下来的重名的jar或者aar会相互覆盖,这个需要自己手动去排查版本,比如说support包需要版本一致,而okhttp和io-socket都引用了oki.jar包,就是用代码让每一个依赖下载下来的文件都放在单独的一个文件夹,然后自己手动去合并这些包到libs中,这里一定要细心和耐心,也要做到版本就低不就高。
如果出现什么mutilclass , duplicate resource等等都是重复引用的导致的问题。

2> so文件的合并问题
so文件也需要手动去合并,因为你自定义的module可能会和其他的module的so文件冲突,这个就需要在android{}标签内加入

packagingOptions {
        //jni包重复编译,那么在这里配置包包含 或者不包含
        exclude 'lib/mips/librsjni.so'
        exclude 'lib/armeabi-v7a/libRSSupport.so'
        exclude 'lib/arm64-v8a/librsjni.so'
        exclude 'lib/arm64-v8a/libRSSupport.so'
        exclude 'lib/x86_64/libRSSupport.so'
        exclude 'lib/mips/libRSSupport.so'
        exclude 'lib/x86/librsjni.so'
        exclude 'lib/x86_64/librsjni.so'
        exclude 'lib/armeabi-v7a/librsjni.so'
        exclude 'lib/x86/libRSSupport.so'
    }


还要一个就是其他module有的兼容版本,你没有,那么就需要手动复制兼容版本文件夹,把对应的文件导入,比如,APICLOUD平台只有
而你的module有x86 armeabi64 armeabi和armeabi-v7a cpu版本兼容,那么在编译的时候就会包含四个文件夹,而其他module只有两个,那么就会运行时就会缺包,如果是这种情况,那么就把自己的包x86 armeabi64 删掉,或者在 defaultConfig{}标签内加入

ndk {
            abiFilters  "armeabi-v7a", "armeabi"
        }


这样编译的时候就只会保留 “armeabi-v7a”, "armeabi"文件夹。
关于so文件兼容请看:
链接内容

3> 插件导入
插件是gradle运行的环境,前提条件,比如说

apply plugin: 'com.android.library'
apply plugin: 'com.jakewharton.butterknife'



这里的意思就是说当前gradle的任务是把这个module当成是library来编译,在运行gradle之前加载butterknife插件,至于如何配置butterknife的话,这里不细说,但是要把module打包成aar的话,加载butterknife插件必须本地加载,因为aar不会自动下载远程依赖的,同理使用第一步所说的下载butterknife所需要的jar包将它们放入lib(注意是新建的)文件夹。然后在gradle的根部添加如下代码:

//展开libs文件夹里面的文件,aar文件依赖必须的配置
repositories{
    flatDir{
        dirs 'libs'
    }
}
/* buildscript这是gradle环境本身所依赖的库,一般是链接
maven和git,Jcenter的仓库加载插件的,但是这里必须本地加载,
下面是把所有的lib文件中的文件加载进去*/
buildscript {
    dependencies {
        classpath fileTree(include: ['*.jar'], dir: 'lib')
    }
}


2、项目合并上面只是仅仅把module中所有的jar和aar都合并进去了,接下来还要考虑,不同的module直接依赖冲突的问题,先来了解下gradle依赖的几个关键字:

Android Studio引用第三方库很方便,只需要一句代码就可以搞定,几种引用第三方库的方式,总结一下:

方式:1:它就会自动把这个包下载下来,并且引用它。节省git空间,而且修改版本也很方便。
compile 'com.android.support:support-v4:23.3.0'

方式2:引用libs下所有jar包
compile fileTree(dir: 'libs', include: ['*.jar'])

方式3:引用一个jar
compile files('libs/fastjson-1.1.53.android.jar')

方式4:引用一个aar文件,注意并不能像 方式2 那样自动引用全部的aar,而需要对每个aar分别进行引用。
compile(name: 'aar_file_name', ext: 'aar')

方式5:引用库类型的项目
compile project(':xxxsdk')

方式6:仅仅在编译时使用,但最终不会被编译到apk或aar里
provided files('libs/glide-3.7.0.jar')


经过本人实验,貌似是资源文件在打包编译的时候如果重名就会覆盖,而class文件重名就是直接编译报错,所以资源文件冲突是隐形的,class冲突比较明显。
jar冲突的话,让主module依赖这个jar,其他次module使用provided 关键字依赖这个jar包,这样编译的时候就只有一份。
aar冲突比较麻烦了,因为aar中可能会包含jar文件,这样的话就需要查看官方文档或者自己解压缩文件去查看这个aar里面究竟包含着那些jar文件,利用 exclude group:"", module:"" 语句来排除jar被编译进aar包中。例如:


最后如果是资源文件冲突,最常见的的错误就是:

no resource id found


或者是

java.lang.NoSuchFieldError: No static field xxx of 
type I in class Lcom/XX/R$id; or its superclasses


要么就是多个module的资源文件重名,导致相互覆盖,那么就找不到指定的资源,要么就是指定的控件ID重名,多个重名控件导致找不到控件,解决办法也很简单就是改文件或者控件名字。

比如说我在APICLOUD云平台编译的时候出现以下错误,

 What went wrong: 
 Execution failed for task ':app:transformClassesWithJarMergingForRelease'
 .> com.android.build.api.transform.TransformException: java.util.zip.ZipException: duplicate entry: android/support/v4/app/BaseFragmentActivityDonut.class


意思就是class文件重复,原因就是apicloud的框架本身自带v4包,而我的module也有v4包,在引入我的自定义模块后,打包的时候就会出现相同的两个class文件,这样就出现了以上错误,解决的办法,上面已经说了使用exclude语法,将相应的部分排除,这个方法只能排除网络库,本地的还没发现能够有效起到作用,因此,我在想,既然apicloud框架本身自带v4的class文件,而我自定义模块打包成aar文件又不会把v4.aar打包进去,还是要手动将v4.aar放到和module.aar同目录下,那么,我可以不将v4.aar放入,而是使用apicloud框架自带的v4.aar,这样就巧妙的将重复问题解决了。记录下这个问题。

新版gradle的新语法:
Gradle 3.4 引入了新的依赖配置,

########################### 这种说法应该是错的  begin  ###########

新增
api 和 implementation 来代替 compile 依赖配置。
其中 api 和以前的 compile 依赖配置是一样的。
使用 implementation 依赖配置,会显著提升构建时间,因为implementation只会依赖直接引用的资源,而资源本身所依赖的资源是不会去深度依赖,这样就减少了,依赖编译所消耗的时间。
比如A依赖B ,B依赖C 。A implementation B,只会将B加入编译。而 A api B或者A compile B就不仅仅只编译B也会将C加入编译。
所以在使用implementation 依赖的时候,不要惊讶找不到类,找不到资源之类的事情,因为他的原理就是不深度依赖。要么使用api(在高版本建议使用这个)和complie(低版本)。要么自己手动在A中将所需要的资源都依赖过来。
implementation和api的区别还有就是在,moduleB中如果使用implementation来引用网络或者本地资源,moduleA 依赖 moduleB这样写的话,还是找不到,如果moduleB api来依赖网络或者本地资源,那么这些资源就可以被moduleA找到,并使用。

########################### 这种说法应该是错的  end  ###########

正确的说明请看:

https://blog.csdn.net/tantion/article/details/86623969

新增
compileOnly    provided    gradle 添加依赖到编译路径,编译时使用。(不会打包到APK)
runtimeOnly    apk    gradle 添加依赖只打包到 APK,运行时使用。(不会添加到编译路径)

现在外国服务器相当不稳定,经常性的连接不上他们的中央仓库,所以自己收藏了几个国内的镜像,速度非常快。
maven { url ‘http://maven.aliyun.com/nexus/content/groups/public/’ }
maven{ url ‘http://maven.aliyun.com/nexus/content/repositories/jcenter’}

经常会出现,merge task失败的问题 ,Task [App] not found in root project [project_name] 这种问题,一般可以去查找aar资源包内是否有重复的jar依赖,导致合并编译出来的jar失败,但是aar重复并不会导致这个问题,不知道谷歌是哪根经出了问题。
 

猜你喜欢

转载自blog.csdn.net/tantion/article/details/86621808