安卓对外发布工程源码:怎么做到仅UI层公布

安卓对外发布工程源码:怎么做到仅UI层公布 - Wesley's Blog

最近新客户要求将软件的代码公开给他们,允许他们自己修改 UI,进行定制。由于我们工程包含其他核心模块,不能全部开放,所以需要将非 UI 模块进行整合并混淆再发布。

由于工程具有良好的模块化,所以一开始想到的是参考安卓官方版fat-aar:使用Fused Library将多个Android库发布为一个库 - Wesley's Blog将非 UI 模块合并成一个 aar。

但有挑战:

  • 依赖传递很难处理,就算处理了也不方便后续维护
  • 存在部分内网 SDK 依赖

为了不改变 maven 的管理方式,决定使用本地 maven 的方式。

可以先用gradlew build --scan生成一棵依赖树,网页版方便查看 app 有哪些依赖,还可以定位内网的依赖来自于哪个地址。

实施

在gradle.properties 定义本地 maven 路径

ini 复制代码
LOCAL_MAVEN_URL=maven_repo
GROUP_ID=com.wesley.test
SDK_VERSION=1.0.0 #统一控制所有模块版本

settings.gradle引入本地依赖

bash 复制代码
maven {
            url = uri("file://${rootProject.projectDir}/${LOCAL_MAVEN_URL}")
​
        }

各自模块接入maven-publish

scss 复制代码
plugins {
    alias(libs.plugins.android.library)
    alias(libs.plugins.kotlin.android)
    id 'maven-publish'
}
def ARTIFACT_ID='base-base'
android {
    //不能发布源码给第三方
    publishing {
        singleVariant('release') // 不生成 sources JAR
    }
​
    ...............
​
     //如果有模块重名,比如 feature:base, base:base, 需要定义
    compileOptions {
        kotlinOptions.freeCompilerArgs += ['-module-name', "$GROUP_ID.$ARTIFACT_ID"]
    }
    
}
​
​
publishing {
    publications {
        maven(MavenPublication) {
            groupId GROUP_ID
            artifactId ARTIFACT_ID
            version SDK_VERSION
​
            afterEvaluate {
                from components.release
            }
        }
    }
​
    repositories {
        maven {
            url rootProject.file(LOCAL_MAVEN_URL).toURI()
        }
    }
}
​

如果模块存在多个 flavor:

scss 复制代码
plugins {
    alias(libs.plugins.android.library)
    alias(libs.plugins.kotlin.android)
    id 'maven-publish'
}
android {
    publishing {
        multipleVariants {
            allVariants()
            //withJavadocJar()
        }
    }
}
​
publishing {
    publications {
        maven(MavenPublication) {
            groupId GROUP_ID
            artifactId "feature-xxx"
            version SDK_VERSION
​
            afterEvaluate {
                from components.default
            }
        }
    }
​
    repositories {
        maven {
            url rootProject.file(LOCAL_MAVEN_URL).toURI()
        }
    }
}

如果要在发布时控制某些代码不混淆或者依赖方式变更:

arduino 复制代码
// 在根项目的 build.gradle 中
ext {
    // 定义判断是否是发布任务的方法
    isPublishTask = { ->
        gradle.startParameter.taskNames.any {
            it.startsWith("publish")
        }
    }
}
less 复制代码
if (isPublishTask()) {
        // 发布时将 aar 打包进 sdk
        println("wesley publish")
        //embed(project( ":feature:testAAR" ))
        // id("com.kezong.fat-aar")
        embed project(path: ":feature:testAAR", configuration: 'default')
    } else {
        implementation(project( ":feature:testAAR" ))
    }

其他

对于不携带 aar 包的模块,比如某 B 模块implementation(project( ":base:base" )),如果:base:base 也配置了 publish 任务,依赖它的模块不需要修改成远程依赖方式。为什么呢?问了一下 AI:

核心原因:项目内依赖(Project Dependency) vs. Maven 依赖(Maven Dependency)的转换

  1. 在模块 A 的构建期间: (implementation project(":base:base"))

    • 当 Gradle 构建你的 A 模块时,它看到了 implementation project(":base:base")
    • 这里的 project(...) 告诉 Gradle:"这是一个项目内依赖 。你不需要去远程仓库下载任何东西,直接在本次构建中找到 :base:base 模块,编译它,然后让 A 模块使用它的代码即可。"
    • 在这个阶段,一切都在你的本地项目中进行,与 Maven 仓库和版本 1.1 是否已发布完全无关
  2. 在发布模块 A 时:(生成 pom.xml

    • 当你运行 publish 任务时,maven-publish 插件的职责是为模块 A 创建一个元数据文件,也就是 pom.xml
    • 这个 POM 文件是给外部用户看的。外部用户(比如你的同事或者一个CI服务器)没有你项目的完整源码,他们需要通过 Maven 坐标(groupId:artifactId:version)来下载依赖。
    • 因此,maven-publish 插件必须将项目内依赖 project(":base:base") "翻译" 成一个外部用户可以理解的 Maven 依赖。

"翻译"的过程是怎样的?

maven-publish 插件非常智能,它会执行以下操作:

  1. 它发现 A 依赖于 project(":base:base")

  2. 它会去检查 :base:base 模块的 build.gradle.kts 文件。

  3. 它在文件中找到了

    复制代码
    publishing

    配置块,并读取了里面定义的 Maven 坐标:

    • groupId: "com.wesley.test"
    • artifactId: "base-base"
    • version: "1.1"
  4. 然后,它将这些信息忠实地写入 A 模块的 pom.xml 文件中,生成了你看到的 <dependency> 块。

一个形象的比喻: 这就像你在写一份菜谱(发布模块 A)。你在菜谱里写到需要"我祖母的秘制酱料"(项目内依赖 :base:base)。为了让别人也能做出这道菜,你必须在菜谱的配料表上写清楚这种酱料在超市里的商品名和品牌(Maven坐标 com.wesley.test:base-base:1.1),而不是只写"我祖母的秘制酱料"。

maven-publish 就是那个负责把"我祖母的秘制酱料"翻译成商品名的角色。它并不关心此刻超市里到底有没有货(1.1 版本是否已发布),它只负责正确地翻译菜谱。


为什么它不关心 1.1 是否已发布?

publish 任务的职责是生成并上传当前模块的构件和元数据。它假设你(开发者)会确保所有声明的依赖项最终都是可用的。

  • 后果的承担者是消费者: 如果你发布了 A,但没有发布 :base:base1.1 版本,那么问题不会在你发布时出现,而是在别人使用你的 A 模块时 出现。当他们的项目尝试根据 Apom.xml 去下载 com.wesley.test:base-base:1.1` 时,Maven 会因为找不到这个依赖而构建失败。
  • 责任在你: Gradle 将保证依赖关系链正确的责任交给了开发者。你需要在发布 A 之前,先将它的所有依赖(如 :base:base)发布到 Maven 仓库。

最后执行 publish任务,所有模块都发布到maven_repo 里面了,以后向客户更新这个目录就行。

而对于内网 SDK,可以在网页版依赖树找到他们,然后通过类似

wget -r -np -nH --cut-dirs=8 -R "index.html*" http://172.20.135.19:8080/nexus/content/repositories/releases/com/wesley/sdk/protocol/2.1.8/的命令下载,把文件丢进maven_repo(注意目录结构),这样依赖也可以正确传递了。(如果包含源码,可以考虑删除)

注意

删掉工程签名文件。

如果有些模块不能公开,混淆后不能发布源码。

不能携带 .git 目录。

注意处理 UI 模块依赖其他模块的方法或者类,不能混淆。

注意处理一些配置信息或者关键代码,可以考虑转移到 native 层。

相关推荐
BoomHe8 分钟前
Android 源码两种执行脚本的区别
android·源码
Kapaseker15 分钟前
Jetpack Compose的副作用一览
android·kotlin
szhangbiao15 分钟前
RxJava炒冷饭之实用案例
android·rxjava
Wgllss1 小时前
6种Kotlin中单例模式写法,特点及应用场景指南
android·架构·android jetpack
prinTao2 小时前
【代码解析】opencv 安卓 SDK sample - 1 - HDR image
android·人工智能·opencv
_一条咸鱼_4 小时前
Android Runtime内存分配与对象生命周期深度解析(57)
android·面试·android jetpack
法的空间4 小时前
JsonToDart,你已经是一个成熟的工具了,接下来就靠你自己继续进化了!
android·flutter·ios
玲小珑4 小时前
Auto.js 入门指南(十八)常见问题与解决方案
android·前端
三少爷的鞋4 小时前
Kotlin 协程合理管理协程作用域:从 CoroutineScope 到 suspend 函数的重构实践
android