安卓对外发布工程源码:怎么做到仅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 层。

相关推荐
千里马学框架2 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台2 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone2 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc2 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo2 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077002 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼2 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone2 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen2 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone2 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui