最近领导给安排了个任务,让我把我们现有的一个 SDK 上传到 Maven 上去,方便客户直接用 gradle 依赖,不再需要拷贝 jar 和 so 了,此前我也看过一些相关的文章我想问题也不大,觉得工作量也就一两天的事情,主要的难点在于如何隐藏源码上传 maven(因为是商业 SDK),万万没想到问题这么多,网上有用的文章也很少,加上 gradle 的版本捣捣乱让我整整一周焦头烂额,一言难尽,略过,直接进入正题!

前期准备

AndroidStudio 版本(不同版本默认的 gradle 版本不同)

Gradle 版本

classpath 'com.android.tools.build:gradle:4.1.3'distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip

总体流程

  1. 注册 sonatype 账号并申请 groupid
  2. 压缩 jar、so、其它资源到 aar
  3. 编写 gradle 脚本
  4. 解决源码混淆问题(非商用 SDK 可跳过)
  5. 解决文档 javadoc 问题(非商用 SDK 可跳过)
  6. GPG 签名
  7. 上传

注册 sonatype 并申请 group id

buildscript {repositories {jcenter()google()mavenCentral()}dependencies {...}}

我们在项目的 gradle.build 中一般都有这么一段代码,其中 repositories 下的每一行代码代表一种远程代码仓库,其中 jcenter 已于 2021 年 3 月 31 日被设定为只读代码库,从 AndroidStudio 中也可以看到相关的提醒;

google 代表的是 google 提供的代码仓库,mavenCentral 则代表着我们想要上传的仓库,但这个 mavenCentral 它不能直接上传,它需要通过它所支持的第三方仓库来同步更新。也就是说你想要上传一个包,需要上传到它指定的一个第三方代码仓库中,第三方仓库会定时与 mavenCentral 同步,在同步后你就可以在 mavenCentral 中找到它了,其中 sonatype 是一个比较好的代码仓库,上传和同步都比较及时。

注册 sonatype 第一步,不要去 google 搜索 sonatype,那会让你不知所措。搜到的大概率是这个东西:

Sontype 管理代码库的申请用的是 jira,所以你要注册它的账号需要登录这个网站:

https://issues.sonatype.org/secure/Signup!default.jspa

这里的密码有点烦,它不会一下子告诉你该有什么要求,每次都是填完密码、验证码后点 sign up 然后告诉你本次的密码哪里不合格,错了好多遍之后我知道了所有的密码要求:

  1. 必须大于等于 8 位
  2. 必须有英文和数字和特殊符号三种,缺一不可
  3. 必须同时包含大写和小写

登录之后你想申请一个 group id 需要创建一个 issue,label 选 Community Support – Open Source Project Repository Hosting (OSSRH),问题类型选 new project。

Group id 这里要认真填写,它对应的是下图中红框圈出的部分,如果你拥有一个域名,可以填写 com.你的域名,不要填写你无法影响的域名,后面会让你在 DNS 解析上加记录来验证域名是你所有的。这里也可以填写你的 github 地址,例如我的为 io.github.shaolongfei。

Project url 这里填写你的项目地址,一般这里为 git 地址,例如我的 https://github.com/ShaoLongFei/AndroidOpenGL。

scm url 这里写你项目的下载方式,一般这里也可以填 git 地址,例如我的 https://github.com/ShaoLongFei/AndroidOpenGL.git。

username 是你希望发布包时的用户名,非必填项,没必要写,也不会审核这个,后面想写可以直接配置在 gradle 脚本上。

alread synced to central(是否已经同步到 mavenCentral 代码库),保持为 no 就可以,我觉得看这篇文章的人应该没有 yes 的吧。

提交之后这个 issue 会自动分配人员来对你提交的信息进行审核,由于他们的审核人员在国外所以会有一些时差,白天提交的话过一晚上就能收到回复了,晚上 10 点提交的话过一会就能收到回复。下面是我的回复:

我提交了一个 com.liuyue 的 group id ,它让我在 DNS 上加一条记录,但由于我并不拥有这个域名所以我选了下面的一个操作,更改 group id 为 io.github.shaolongfei ,在我的 github 上建一个他指定名称的库来验证 github 账号确实是我所有的。

在我做好这个操作后,更改这个 issue 的状态为 open(可以 comment 一下状态就会改变了) ,这样审核人员就会再次处理了。

收到这样的回复就代表你已经通过审核了。group id 的申请也就完成了。

压缩 jar、so、其它资源到 aar

以前客户使用我们的 sdk 都是分别拷贝 jar、so、资源文件到客户的项目中进行依赖,这样的优势是灵活,由于我们的 so 是分模块的,如果客户不想用某些功能可以根据自己的需求进行裁剪,也可以根据自己想要的 so 的架构进行依赖,因为几种架构的 so 体积加起来还挺大的;而上 maven 就不能散着了,需要打成一个 aar 把 jar 、so 、资源文件都放进去,这样的优势是客户使用起来很方便。

我们的项目是有两个 moudle 的,每个 moudle 会打出来一个 jar ,平时打 jar 包的流程是各自打 aar 包,分别解压出来 jar,将两个 jar 合并到一起。那打 aar 我原先的思路是在此前打 jar 的基础上往里面放入各个架构的 so 和指定路径的资源文件,最后搞了搞发现完全不需要这么麻烦,还得自己写脚本。

我找到了一个神器 https://github.com/kezong/fat-aar-android

这个用起来非常方便,非常简单,几行代码就可以解决打 aar 的各种烦恼,什么依赖库冲突,什么多 moudle ,混淆规则合并,AndroidManifest 合并,R.class 合并等等问题它都能搞定。工欲善其事,必先利其器啊。

Apply classpath

第一步,在项目的 build.gradle 中加入 mavenCentral 和 classpath

buildscript {repositories {mavenCentral()}dependencies {classpath 'com.github.kezong:fat-aar:1.3.8'}}

Add plugin

第二步,在项目主 library 中的 build.gradle 添加此插件

apply plugin: 'com.kezong.fat-aar'

Embed dependencies

第三步,embed 你所需要的工程, 用法类似 implementation。

embed 的工程会被压缩到 aar 中,implementation 的项目只参与编译的过程,不会参与打包的过程。

dependencies {implementation 'com.qiniu:qiniu-android-sdk:8.3.2'embed project(':MediaLibrary')}

执行 assemble 命令

此时配置已经完成了,在 AndroidStudio 的右侧 Gradle 任务栏里可以找到 assemble 任务、assembleDebug 任务和 assembleRelease 任务。

执行 assemble 任务可以打出来 library-debug.aar 和 library-release.aar;执行 assembleDebug 任务可以打出来 library-debug.aar;执行 assembleRelease 任务可以打出来 library-release.aar。

打出来的 aar 都在 build/outputs/aar 路径下。

**注意:**如果你在 AndroidStudio 右侧的 gradle 任务列表里找不到这些任务,那你需要在 AndroidStudio 的设置中取消下图勾画的这一项设置,这个设置是新版的 AndroidStudio 默认勾画的,取消它!!!

如果你习惯了命令行的话也可以直接在项目根目录下敲这些命令:

# 产物:library-debug.aar 和 library-release.aar./gradlew assemble# 产物:library-debug.aar./gradlew assembleDebug# 产物:library-release.aar./gradlew assembleRelease

编写 gradle 脚本

先上代码,解释都在注释里

plugins {id 'signing'id 'maven-publish'}task sourcesJar(type: Jar) {from android.sourceSets.main.java.srcDirsclassifier = 'sources'}task javadoc(type: Javadoc) {source = android.sourceSets.main.java.srcDirsclasspath += project.files(android.getBootClasspath().join(File.pathSeparator))}task javadocJar(type: Jar, dependsOn: javadoc) {classifier = 'javadoc'from javadoc.destinationDir}// 发布任务publishing {publications {maven(MavenPublication) {artifact "build/outputs/aar/library-release.aar" // 产物artifact sourcesJarartifact javadocJargroupId = 'io.github.shaolongfei' // 此前在 sonatype 上申请的artifactId = 'OpenGLESUtils' // 项目的名称,依赖的时候要用到的version = '0.0.18' // 项目版本pom {name = 'OpenGLESUtils' /// 项目名称packaging = 'aar' // 发布的形式url = 'https://github.com/ShaoLongFei/AndroidOpenGL' // 项目地址description = 'OpenGLES 常用的工具类' // 项目描述scm {connection = 'scm:git:git://ShaoLongFei/AndroidOpenGL.git'developerConnection = 'scm:git:ssh://ShaoLongFei/AndroidOpenGL.git'url = 'https://github.com/ShaoLongFei/AndroidOpenGL'}//开源协议licenses {license {name = 'The Apache License, Version 2.0'url = 'http://www.apache.org/licenses/LICENSE-2.0.txt'}}// sdk 发布者信息developers {developer {name = 'liuyue' // 发布者名称email = 'liuyueshaolongfei@foxmail.com'// 联系邮箱}}}}}repositories {mavenLocal() // 发布到本地,目录在 username/.m2/repository/ 下maven {name 'sonatypeRepository'url 'https://s01.oss.sonatype.org/content/repositories/releases/'credentials {username = ''// 之前在 sonatype 注册的账户名password = '' // 对应的密码}}}}// 不可打乱顺序signing {sign configurations.archives}

这是我查了好多文章总结出来的写法,网上很多文章讲的都是老版本的 maven 插件,现在它已经被弃用了,需要使用新版本的 maven-publish 插件才可以,新老版本差异还是挺大的,比如老版本的上传任务是 uploadArchives ,新版本的是 publishing。

publishing 下面套 publications 再套 maven(MavenPublication),这个配置可以满足绝大多数需求,非必要别改动。

artifact 声明的是要上传的东西,可以声明多行,每行一个,它自己会把它们集合到一起,也可以用大括号的形式声明一个列表

artifact{......}

sourcesJar 和 javadocJar 这两个 task 可以直接照搬,后面会再讲。

artifactId 是构件ID,这个名称一般都为小写字母,没有其他的特殊字符,我这里写的有问题,大写不会报错,也不会影响打包、推送,就是不太符合规范,推荐使用“实际项目名称-模块名称”的方式定义,例如:spirng-mvn、spring-core等。

如果 gradle 报错出现 main 找不到的情况,可以加入下列代码,显示的声明路径:

sourceSets {main {java { srcDirs = ["src/java"] }resources { srcDir "src/resources" }}}

licenses 开源协议不必要写,如果你的项目不开源的话可以不写。

repositories 下写的是要发布到哪里,建议先添加一个 mavenLocal() 发布到本地试试,它会发布到 username/.m2/repository/ 目录下,确定好没问题再上传 sonatype 上。

https://s01.oss.sonatype.org/content/repositories/releases/

这个地址是发布 release 的地址,如果你还要发布其它版本的话可以看看下列的地址:

https://s01.oss.sonatype.org/content/groups/public/

https://s01.oss.sonatype.org/content/groups/staging/

https://s01.oss.sonatype.org/content/repositories/snapshots/

这个地址千万不能填错,因为 sonatype 更换了新的服务器,但是网上的文章都是旧的,用的都是旧地址,会导致上传有问题。

最后的 signing 一定要写在 publishing 之后,不然会出现语法错误。

解决源码混淆问题(非商用 SDK 可跳过)

事先声明,此部分我未解决,留下我的解决经验,以便后来者可以借鉴,减少重复劳动或者减少试错成本。

上传 maven 一般来说需要三样产物,release.jar 是打包好的 sdk,source.jar 是便于使用者查看源码的 jar,javadoc.jar 是方便使用者看文档和代码上的注释的 jar。

由于我们是商用的 SDK ,所以上传时不能上传源码,我就想,那上传的时候选择性的混淆一下吧,外层接口保存原装,内部代码混淆一下,于是就开始研究怎么过滤这个代码。

task sourcesJar(type: Jar) {from android.sourceSets.main.java.srcDirsclassifier = 'sources'}

思路一:写一个 gradle plugin

通过一个 gradle plugin 添加一个任务,在打 sourceJar 的时候通过一些规则混淆源码。

在项目下新建一个 moudle ,他通过使用 gradle 和 javaparser 来处理代码。加入如下依赖:

dependencies {// gradle sdkimplementation gradleApi()// groovy sdkimplementation localGroovy()implementation 'com.android.tools.build:gradle:7.1.3'implementation 'com.github.javaparser:javaparser-core:3.24.2'}

这个插件完成之后还要发布到本地,然后另一个项目来引用并 apply。

plugins{id 'groovy'id 'maven-publish'}afterEvaluate {publishing {publications {maven(MavenPublication) {artifact "build/libs/hidesourceplugin.jar"groupId = 'com.liuyue.plugin'artifactId = 'HideSourcePlugin'version = '0.0.2'}}repositories{mavenLocal()}}}

最简略的发布逻辑,接下来处理代码。

import org.gradle.api.Pluginimport org.gradle.api.Projectclass HideSourcePlugin implements Plugin<Project> {@Overridevoid apply(Project project) {final android = project.extensions.androidHideSourceTask sourcesTask = project.tasks.findByName("hideSourceJar") as HideSourceTaskif (sourcesTask == null) {sourcesTask = project.tasks.create("hideSourceJar", HideSourceTask)}sourcesTask.from(android.sourceSets.main.java.srcDirs)}}

首先它要实现 plugin 的接口,重写 applay 的方法,当这个插件被 apply 的时候生效。

检查当前任务列表里有没有一个 hideSourceJar 的任务,没有的话就创建添加一个,然后让它来处理我们源码路径下的所有文件。

import com.github.javaparser.JavaParserimport com.github.javaparser.ast.CompilationUnitimport com.github.javaparser.ast.body.ConstructorDeclarationimport com.github.javaparser.ast.body.MethodDeclarationimport com.github.javaparser.ast.expr.FieldAccessExprimport com.github.javaparser.ast.expr.MethodCallExprimport com.github.javaparser.ast.expr.NameExprimport com.github.javaparser.ast.expr.StringLiteralExprimport com.github.javaparser.ast.stmt.BlockStmtimport com.github.javaparser.ast.visitor.VoidVisitorAdapterimport org.gradle.jvm.tasks.Jar/** * @author moonshoter */public class HideSourceTask extends Jar {HideSourceTask() {super()group = 'artifacts'classifier = "sources-hide"filter(CodeFilterReader.class)}static class CodeFilterReader extends FilterReader {CodeFilterReader(Reader reader) {super(reader)CompilationUnit compilationUnit = JavaParser.parse(reader)compilationUnit.accept(new MethodVisitor(), null)String codeAfterHide = compilationUnit.toString()this.in = new StringReader(codeAfterHide)reader.close()}private static class MethodVisitor extends VoidVisitorAdapter<Void> {@Overridevoid visit(MethodDeclaration n, Void arg) {// 清除原数据n.removeBody()// 修改BlockStmt block = new BlockStmt()n.setBody(block)NameExpr clazz = new NameExpr("System")FieldAccessExpr field = new FieldAccessExpr(clazz, "out")MethodCallExpr call = new MethodCallExpr(field, "println")call.addArgument(new StringLiteralExpr("Some Unspoken Thing~~"))block.addStatement(call)}@Overridevoid visit(ConstructorDeclaration n, Void arg) {if (n.body != null) {n.body.statements.clear()}// 修改BlockStmt block = new BlockStmt()n.body = blockNameExpr clazz = new NameExpr("System")FieldAccessExpr field = new FieldAccessExpr(clazz, "out")MethodCallExpr call = new MethodCallExpr(field, "println")call.addArgument(new StringLiteralExpr("Some Unspoken Thing~~"))block.addStatement(call)}}}}

这是一个替换方法中的代码为 System.out.println("Some Unspoken Thing~~") 的逻辑,想要替换哪些方法可以自己写一个逻辑来控制。

好,现在代码写好了,publishing 到本地检查本地,确实有此插件,jar 也正确。

到了应用的时候了,先在项目的 build.gradle 中加入 mavenLocal() 和 classpath

buildscript {repositories {mavenLocal()}dependencies {classpath 'com.liuyue.plugin:HideSourcePlugin:0.0.2'}}

**注意:**如果你使用的 gradle 是 7.x 的,那么你项目的 build.gradle 会变成这个样子

plugins {id 'com.android.application' version '7.2.0-alpha07' apply falseid 'com.android.library' version '7.2.0-alpha07' apply false}task clean(type: Delete) {delete rootProject.buildDir}

看到这个让我感觉有点不知所措,之前这东西不在这的呀,然后我发现 setting.gradle 也变了,以前只有 include ‘xxx’ 来着,现在多了一堆东西。

pluginManagement {repositories {gradlePluginPortal()google()mavenCentral()}}dependencyResolutionManagement {repositories {google()mavenCentral()}}rootProject.name = "OpenGLESUtils"include ':app'include ':library'include ':hidesourceplugin'

仔细对比可以发现,原来是原来项目的一些配置移动到了 setting.gradle 中去了,那我就在 setting.gradle 中写吧,咔咔咔写上去,发现它居然报错了!!!

Cannot resolve external dependency com.liuyue.plugin:HideSourcePlugin:0.0.2 because no repositories are defined.

我只好再 build.gradle 中写了,emmm,成功了。

好,现在,applay 它,运行,叮~,出错了。

Build was configured to prefer settings repositories over project repositories but repository 'Gradle Libs' was added by unknown code

它觉得你添加的 gradle libs 是一个未知的来源,它不敢用。

真怂,它不敢,我敢,删除 setting.gradle 中的 repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) ,报错解决,搞定。

现在问题拮据了,下一步,publishing,emmm,没有用。

Game over~

那我换一种思路吧,生成 sourceJar 的时候 from 指定了要生成 jar 包的代码的路径,这块是可以写多个的,它会自己合并,这里还有一个方法叫 filter,我能不能用这个过滤,做一些操作呢,我理想的写法:

task sourcesJar(type: Jar) {from android.sourceSets.main.java.srcDirsfilter(HideSourceReader.class)classifier = 'sources'}

但这样会报错找不到 HideSourceReader.class ,那就解决它!

先 import 试试,因为 gradle 也是用 java 实现的,所以语法差不多,试试。

Emmm,不行!

把这个类打成 jar ,然后在 gradle 中依赖它,这样应该能找到了吧,试试。

Emmm,不行!

Game,over~

最后仔细想一想,source.jar 它并不是必传的东西,所以如果不想暴露源代码可以不传,这是我最后的解决方案了。

解决文档 javadoc 问题(非商用 SDK 可跳过)

事先声明,此部分我未解决,留下我的解决经验,以便后来者可以借鉴,减少重复劳动或者减少试错成本。

task javadocJar(type: Jar, dependsOn: Javadoc) {source = android.sourceSets.main.java.srcDirsclasspath += project.files(android.getBootClasspath().join(File.pathSeparator))classifier = 'javadoc'from javadoc.destinationDir}

这个 task 打出来的 javadoc.jar 正常情况下是这样的:

如果没有特殊要求的话那么直接用这个就可以了,但我们是商业的 SDK ,这种打 jar 的方式会把内部的代码暴露出来(已测试)。那我们想要的是一个什么样式的呢?外部 API 的代码注释使用者可以正常查看,内部的代码隐藏,或者内部的代码混淆,注释清除。

第一点,我先注意到了 android.jar 中的 @hide 注解,只要是被这个修饰的类或者代码段我们平常开发者就无法查看(另外提一嘴,如果想查看 android 源码的话可以去网上找已经去除 @hide 的 android.jar,替换本地的就可以了,不过某些版本的 AndroidStudio 会检测 jar 的签名,会报错 mock jar,这个怎么解决自己去找一下吧),那我们能不能用 @hide 来修饰我们内部的代码呢?

怀着激动的心情,在我的 demo 工程上试了一下,不行!

这个东西不是你在注解包里找不到的问题,它是普通开发者就无法使用!

Game over~

那有没有办法去除掉一些类再打包 javadoc.jar 呢?

有!exclude 可以指定去除某些类。类似:

exclude('com/liuyue/library/haha/**')

但是,这样写之后会导致 javadoc 执行失败,因为依然存在的类可能引用着被去除掉的类,这样就会报错。

Game over~

查看 AndroidStudio 自带的打 javadoc 的程序执行的参数,试图在打包的时候带上一些参数阻止因为错误导致的打包停止,发现没什么特别的,暂时无法解决。

Game over~

在这里我还遇到一个坑,因为我为了测试打包上传这些功能自己写了一个 demo 工程,它写的比较简单,然后我写了 4 个类,有外部的类有内部的类,有混淆的有不混淆的,有引用其它类的有单独自己的,但是经过混淆后发现就剩两个类了,这就让我没法测试内部注释是否可以展示这个问题了,左思右想我意识到是混淆的问题,因为这里会对代码做优化,把它觉得不必要的类,没有用的类合并或者删掉,所以导致我最后缺了俩类。

一开始我觉得是混淆开启的压缩导致的,因为我配置的压缩等级是 5,这也是推荐的压缩等级。

-optimizationpasses 5

我把它改为 0 ,发现并不是这样子的,然后我查如何关系混淆代码优化,发现是这句代码,写上这句话可以关闭混淆优化:

-dontshrink

这个问题会出现在 AndroidStudio 3.4 版本以后,因为此版本后都默认开启了 R8 代码缩减。

最后仔细想一想,javadoc.jar 它并不是必传的东西,所以如果不想暴露源代码可以不传,这是我最后的解决方案了。

PGP 签名

在 gradle 的最后一段代码里有这么句

signing {sign configurations.archives}

这句就是签名用的。网上关于 PGP 签名的文章还是很多的,从下载到最后生成我都没遇到困难,但是到了打包那一步可是坑死我了,因为一个签名算法的问题我被迫从头再来。

第一步,下载 GPG

Mac 用户可以直接使用 homebrew 下载

brew install gpg

Winodws 用户可以在 https://www.gpg4win.org/ 这里下载

第二步,生成密钥

gpg --full-gen-key

这里会让你选择密钥算法,密钥长度,密钥有效期

这里一定要选 4 RSA 仅用于签名(我也吃了这个的亏),不然操作到最后你会发现一个错误:

unknown public key algorithm encountered

gradle 会无法理解里的加密算法

随后它会让你写一个名字,电子邮件地址,信息,都确认无误后,它会让你输入一个密码。

这个密码一定要记住了,以后要用到!

操作完后,你的密钥就已经被生成了。你可以使用下列命令来查看你已经创建的密钥:

gpg --list-keys --keyid-format short

–keyid-format shot 可以让你的密钥以短 ID 的形式展示,这个后面会用到。

红框内是你的密钥ID,它的左上角是你的短ID。这里注意一下左上角有两组数字,一个是 ed25519,如果这串数字是 ed 开头的,那么恭喜你,选错密钥加密算法了,这种加密算法为 EDDSA ,非 RSA ,趁早赶紧重来吧。

如果错了可以选择删除这个密钥,如果不删除的话后续再生成同样用户名、邮箱的密钥会比较乱,很容易分不清。

而且如果你已经进行了下一步,那你就再也无法从网络上删掉这个密钥了。

因为现在仍然在工作的绝大多数密钥服务器都是使用的sks密钥服务器(组),其有以下几个特性:

  1. 分布式,提交的密钥提交至任何一个在sks服务器池的服务都会很快与其他位于sks池的程序同步。通过分布式提高了sks池整体的可用性可靠性和稳定性。
  2. 不可删除,即使你控制着一个sks密钥服务器,删除了一个公钥,很快就会通过sks的公钥算法同步,而想要命令所有的sks池同时删除一个指定的公钥几乎是不可能的。这样可以阻止恶意第三方恶意删除公钥,但是也阻止了正常的公钥删除流程。

所以一旦上传至 sks 池,将不可能从sks公钥服务器删除公钥 。顶多只能在公钥上面加上一段”我从此以后不再信任/使用该证书”的声明(又称 吊销密钥) 。所以这一行为也可以作为攻击 sks 服务器的一种手段,讲远了。

如果你想吊销一对密钥需要先生成一个吊销证书,而如果你只是不想使用了之前的密钥对,你可以先将该密钥的信息修改成垃圾信息,然后清空所有有效 uid 和所有 subkey ,并将截止时间修改为第二天,然后上传到公钥服务器。第二天的时候额外上传吊销证书,这样可以既保证密钥服务器的信息不乱,也可以吊销这个密钥(更方便是从来也不上传到密钥服务器)。

gpg --gen-revoke 你的密钥 > gpg-revoke.asc(吊销证书保存地址)

然后它会问你为什么要吊销的原因,你可以选 3 不再使用,然后一路确定就可以了。

然后将撤销证书导入本地 GPG 库中,撤销本地公钥

gpg --import gpg-revoke.asc(吊销证书的地址)

最后把这个密钥上传到公网上去就好了

gpg --send-keys 你的密钥ID

过一会你可以使用 search-keys 来搜索你刚才吊销的密钥,会发现它的状态已经改变了,会在最后有一个(revoked)的标识。

gpg --search-keys 你的密钥ID或者当时设置的name

在本地删除公钥和私钥,删除公钥和私钥分别有一条命令,不过我建议你全部删除,执行以下命令:

gpg --delete-secret-and-public-key 你的密钥ID

这样就完成掉密钥的吊销和删除了

第三步 上传公钥

为了让大家都知道你的公钥以做验证,你需要上传的你的密钥,只需要下面的一行命令,它会自己上传到一个地方,然后这个地方的公钥会很快与其它各处地方进行同步,很快你的公钥大家就都知道了,这个就叫做钥匙环。

gpg --send-keys 你的密钥ID

第四步 导出私钥

在 gradle 的最后一步会对上传的包进行签名,这里会用到私钥,需要配置一个私钥的地址,它需要配置在 gradle.properties 文件中(也许可以配置在 gradle 脚本中,但是我没找到具体的办法)。

signing.keyId=你的密钥IDsigning.password=密钥密码signing.secretKeyRingFile=私钥所在地址

keyId 这里需要填写要签名密钥的ID,这里要填短ID,在第一步的时候讲过如何查看短ID(其实就是密钥ID的后8位)

gpg --list-keys --keyid-format short

password 要填你密钥的密码,之前提醒过你要记住,记不住的话我也没办法,重来吧。

secretKeyRingFile 这里要填私钥的地址,一定要是私钥(小坑),一定要是二进制格式的(巨坑)。

可以使用下面的命令导出私钥到文件

gpg -o /Users/shaolongfei/Downloads/secring.gpg(这个替换成你的地址) --export-secret-key 你的密钥ID

这里一定不要加 -a 或者 -armor 等等的,那会使导出的私钥文件是 ascii 编码,gradle 不认,它会报错(使用公钥也会报这个错):

It may not be a PGP secret key ring

配置完后就可以进行最后一步上传了~

上传

上传最简单了,但我建议你先 publish 到本地看一下,添加 mavenLocal() 后它会发布到 /username/.m2/repository 文件夹,一个正常的发布文件是这样的:

写好 gradle 脚本后在 AndroidStudio 的右侧 gradle task 列表里你可以找到 publish 任务:

双击执行就可以了,或者命令行:

./gradlew publish

执行过后它就会推送到 sonatype 的 nexus 上去了,你可以登录 nexus 查看一下,账号密码和你注册 sonatype jira 时一样。

https://s01.oss.sonatype.org/

因为网上很多都是老的文章,所以它的地址是老的地址,但你依然可以登录,界面和新的也完全一样,但是登录后它会告诉你账号密码不对或者你没有这个权限,就很让人摸不到头脑。

登录上去,网上的文章很多都说去 staging Repositories 选项下去找你刚才发布的包,它会为 staging 的状态,close 后会经过检查,最后点击 release 才能真正进行发布。但我发现并不是这样的,一开始我也很纳闷为什么我的找不到,到底是哪里出问题导致推送失败了,也没有报错,后来我发现在 nexus 的代码库中是可以搜索到我发布的包的,那说明就已经成功了,不用担心:

过一会你就可以在本地通过 gradle 引用你发布的包了~

mavenCentral 的同步通常是 30 分钟,最多可能需要 4 个小时。