最近领导给安排了个任务,让我把我们现有的一个 SDK 上传到 Maven 上去,方便客户直接用 gradle 依赖,不再需要拷贝 jar 和 so 了,此前我也看过一些相关的文章我想问题也不大,觉得工作量也就一两天的事情,主要的难点在于如何隐藏源码上传 maven(因为是商业 SDK),万万没想到问题这么多,网上有用的文章也很少,加上 gradle 的版本捣捣乱让我整整一周焦头烂额,一言难尽,略过,直接进入正题!
前期准备
AndroidStudio 版本(不同版本默认的 gradle 版本不同)
Gradle 版本
groovy
classpath 'com.android.tools.build:gradle:4.1.3'
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip
总体流程
- 注册 sonatype 账号并申请 groupid
- 压缩 jar、so、其它资源到 aar
- 编写 gradle 脚本
- 解决源码混淆问题(非商用 SDK 可跳过)
- 解决文档 javadoc 问题(非商用 SDK 可跳过)
- GPG 签名
- 上传
注册 sonatype 并申请 group id
groovy
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 然后告诉你本次的密码哪里不合格,错了好多遍之后我知道了所有的密码要求:
- 必须大于等于 8 位
- 必须有英文和数字和特殊符号三种,缺一不可
- 必须同时包含大写和小写
登录之后你想申请一个 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
groovy
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.github.kezong:fat-aar:1.3.8'
}
}
Add plugin
第二步,在项目主 library 中的 build.gradle 添加此插件
groovy
apply plugin: 'com.kezong.fat-aar'
Embed dependencies
第三步,embed 你所需要的工程, 用法类似 implementation。
embed 的工程会被压缩到 aar 中,implementation 的项目只参与编译的过程,不会参与打包的过程。
groovy
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 默认勾画的,取消它!!!
如果你习惯了命令行的话也可以直接在项目根目录下敲这些命令:
shell
# 产物:library-debug.aar 和 library-release.aar
./gradlew assemble
# 产物:library-debug.aar
./gradlew assembleDebug
# 产物:library-release.aar
./gradlew assembleRelease
编写 gradle 脚本
先上代码,解释都在注释里
groovy
plugins {
id 'signing'
id 'maven-publish'
}
task sourcesJar(type: Jar) {
from android.sourceSets.main.java.srcDirs
classifier = 'sources'
}
task javadoc(type: Javadoc) {
source = android.sourceSets.main.java.srcDirs
classpath += 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 sourcesJar
artifact javadocJar
groupId = '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 声明的是要上传的东西,可以声明多行,每行一个,它自己会把它们集合到一起,也可以用大括号的形式声明一个列表
groovy
artifact{
...
...
}
sourcesJar 和 javadocJar 这两个 task 可以直接照搬,后面会再讲。
artifactId 是构件ID,这个名称一般都为小写字母,没有其他的特殊字符,我这里写的有问题,大写不会报错,也不会影响打包、推送,就是不太符合规范,推荐使用"实际项目名称-模块名称"的方式定义,例如:spirng-mvn、spring-core等。
如果 gradle 报错出现 main 找不到的情况,可以加入下列代码,显示的声明路径:
groovy
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 ,所以上传时不能上传源码,我就想,那上传的时候选择性的混淆一下吧,外层接口保存原装,内部代码混淆一下,于是就开始研究怎么过滤这个代码。
groovy
task sourcesJar(type: Jar) {
from android.sourceSets.main.java.srcDirs
classifier = 'sources'
}
思路一:写一个 gradle plugin
通过一个 gradle plugin 添加一个任务,在打 sourceJar 的时候通过一些规则混淆源码。
在项目下新建一个 moudle ,他通过使用 gradle 和 javaparser 来处理代码。加入如下依赖:
groovy
dependencies {
// gradle sdk
implementation gradleApi()
// groovy sdk
implementation 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()
}
}
}
最简略的发布逻辑,接下来处理代码。
groovy
import org.gradle.api.Plugin
import org.gradle.api.Project
class HideSourcePlugin implements Plugin<Project> {
@Override
void apply(Project project) {
final android = project.extensions.android
HideSourceTask sourcesTask = project.tasks.findByName("hideSourceJar") as HideSourceTask
if (sourcesTask == null) {
sourcesTask = project.tasks.create("hideSourceJar", HideSourceTask)
}
sourcesTask.from(android.sourceSets.main.java.srcDirs)
}
}
首先它要实现 plugin 的接口,重写 applay 的方法,当这个插件被 apply 的时候生效。
检查当前任务列表里有没有一个 hideSourceJar 的任务,没有的话就创建添加一个,然后让它来处理我们源码路径下的所有文件。
groovy
import com.github.javaparser.JavaParser
import com.github.javaparser.ast.CompilationUnit
import com.github.javaparser.ast.body.ConstructorDeclaration
import com.github.javaparser.ast.body.MethodDeclaration
import com.github.javaparser.ast.expr.FieldAccessExpr
import com.github.javaparser.ast.expr.MethodCallExpr
import com.github.javaparser.ast.expr.NameExpr
import com.github.javaparser.ast.expr.StringLiteralExpr
import com.github.javaparser.ast.stmt.BlockStmt
import com.github.javaparser.ast.visitor.VoidVisitorAdapter
import 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> {
@Override
void 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)
}
@Override
void visit(ConstructorDeclaration n, Void arg) {
if (n.body != null) {
n.body.statements.clear()
}
// 修改
BlockStmt block = new BlockStmt()
n.body = 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)
}
}
}
}
这是一个替换方法中的代码为 System.out.println("Some Unspoken Thing~~")
的逻辑,想要替换哪些方法可以自己写一个逻辑来控制。
好,现在代码写好了,publishing 到本地检查本地,确实有此插件,jar 也正确。
到了应用的时候了,先在项目的 build.gradle 中加入 mavenLocal() 和 classpath
groovy
buildscript {
repositories {
mavenLocal()
}
dependencies {
classpath 'com.liuyue.plugin:HideSourcePlugin:0.0.2'
}
}
**注意:**如果你使用的 gradle 是 7.x 的,那么你项目的 build.gradle 会变成这个样子
groovy
plugins {
id 'com.android.application' version '7.2.0-alpha07' apply false
id 'com.android.library' version '7.2.0-alpha07' apply false
}
task clean(type: Delete) {
delete rootProject.buildDir
}
看到这个让我感觉有点不知所措,之前这东西不在这的呀,然后我发现 setting.gradle 也变了,以前只有 include 'xxx'
来着,现在多了一堆东西。
groovy
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,我能不能用这个过滤,做一些操作呢,我理想的写法:
groovy
task sourcesJar(type: Jar) {
from android.sourceSets.main.java.srcDirs
filter(HideSourceReader.class)
classifier = 'sources'
}
但这样会报错找不到 HideSourceReader.class ,那就解决它!
先 import 试试,因为 gradle 也是用 java 实现的,所以语法差不多,试试。
Emmm,不行!
把这个类打成 jar ,然后在 gradle 中依赖它,这样应该能找到了吧,试试。
Emmm,不行!
Game,over~
最后仔细想一想,source.jar 它并不是必传的东西,所以如果不想暴露源代码可以不传,这是我最后的解决方案了。
解决文档 javadoc 问题(非商用 SDK 可跳过)
事先声明,此部分我未解决,留下我的解决经验,以便后来者可以借鉴,减少重复劳动或者减少试错成本。
groovy
task javadocJar(type: Jar, dependsOn: Javadoc) {
source = android.sourceSets.main.java.srcDirs
classpath += 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 可以指定去除某些类。类似:
groovy
exclude('com/liuyue/library/haha/**')
但是,这样写之后会导致 javadoc 执行失败,因为依然存在的类可能引用着被去除掉的类,这样就会报错。
Game over~
查看 AndroidStudio 自带的打 javadoc 的程序执行的参数,试图在打包的时候带上一些参数阻止因为错误导致的打包停止,发现没什么特别的,暂时无法解决。
Game over~
在这里我还遇到一个坑,因为我为了测试打包上传这些功能自己写了一个 demo 工程,它写的比较简单,然后我写了 4 个类,有外部的类有内部的类,有混淆的有不混淆的,有引用其它类的有单独自己的,但是经过混淆后发现就剩两个类了,这就让我没法测试内部注释是否可以展示这个问题了,左思右想我意识到是混淆的问题,因为这里会对代码做优化,把它觉得不必要的类,没有用的类合并或者删掉,所以导致我最后缺了俩类。
一开始我觉得是混淆开启的压缩导致的,因为我配置的压缩等级是 5,这也是推荐的压缩等级。
properties
-optimizationpasses 5
我把它改为 0 ,发现并不是这样子的,然后我查如何关系混淆代码优化,发现是这句代码,写上这句话可以关闭混淆优化:
properties
-dontshrink
这个问题会出现在 AndroidStudio 3.4 版本以后,因为此版本后都默认开启了 R8 代码缩减。
最后仔细想一想,javadoc.jar 它并不是必传的东西,所以如果不想暴露源代码可以不传,这是我最后的解决方案了。
PGP 签名
在 gradle 的最后一段代码里有这么句
groovy
signing {
sign configurations.archives
}
这句就是签名用的。网上关于 PGP 签名的文章还是很多的,从下载到最后生成我都没遇到困难,但是到了打包那一步可是坑死我了,因为一个签名算法的问题我被迫从头再来。
第一步,下载 GPG
Mac 用户可以直接使用 homebrew 下载
shell
brew install gpg
Winodws 用户可以在 https://www.gpg4win.org/ 这里下载
第二步,生成密钥
gpg --full-gen-key
这里会让你选择密钥算法,密钥长度,密钥有效期
这里一定要选 4 RSA 仅用于签名(我也吃了这个的亏),不然操作到最后你会发现一个错误:
unknown public key algorithm encountered
gradle 会无法理解里的加密算法
随后它会让你写一个名字,电子邮件地址,信息,都确认无误后,它会让你输入一个密码。
这个密码一定要记住了,以后要用到!
操作完后,你的密钥就已经被生成了。你可以使用下列命令来查看你已经创建的密钥:
shell
gpg --list-keys --keyid-format short
--keyid-format shot 可以让你的密钥以短 ID 的形式展示,这个后面会用到。
红框内是你的密钥ID,它的左上角是你的短ID。这里注意一下左上角有两组数字,一个是 ed25519,如果这串数字是 ed 开头的,那么恭喜你,选错密钥加密算法了,这种加密算法为 EDDSA ,非 RSA ,趁早赶紧重来吧。
如果错了可以选择删除这个密钥,如果不删除的话后续再生成同样用户名、邮箱的密钥会比较乱,很容易分不清。
而且如果你已经进行了下一步,那你就再也无法从网络上删掉这个密钥了。
因为现在仍然在工作的绝大多数密钥服务器都是使用的sks密钥服务器(组),其有以下几个特性:
- 分布式,提交的密钥提交至任何一个在sks服务器池的服务都会很快与其他位于sks池的程序同步。通过分布式提高了sks池整体的可用性可靠性和稳定性。
- 不可删除,即使你控制着一个sks密钥服务器,删除了一个公钥,很快就会通过sks的公钥算法同步,而想要命令所有的sks池同时删除一个指定的公钥几乎是不可能的。这样可以阻止恶意第三方恶意删除公钥,但是也阻止了正常的公钥删除流程。
所以一旦上传至 sks 池,将不可能从sks公钥服务器删除公钥 。顶多只能在公钥上面加上一段"我从此以后不再信任/使用该证书"的声明(又称 吊销密钥) 。所以这一行为也可以作为攻击 sks 服务器的一种手段,讲远了。
如果你想吊销一对密钥需要先生成一个吊销证书,而如果你只是不想使用了之前的密钥对,你可以先将该密钥的信息修改成垃圾信息,然后清空所有有效 uid 和所有 subkey ,并将截止时间修改为第二天,然后上传到公钥服务器。第二天的时候额外上传吊销证书,这样可以既保证密钥服务器的信息不乱,也可以吊销这个密钥(更方便是从来也不上传到密钥服务器)。
shell
gpg --gen-revoke 你的密钥 > gpg-revoke.asc(吊销证书保存地址)
然后它会问你为什么要吊销的原因,你可以选 3 不再使用,然后一路确定就可以了。
然后将撤销证书导入本地 GPG 库中,撤销本地公钥
shell
gpg --import gpg-revoke.asc(吊销证书的地址)
最后把这个密钥上传到公网上去就好了
shell
gpg --send-keys 你的密钥ID
过一会你可以使用 search-keys 来搜索你刚才吊销的密钥,会发现它的状态已经改变了,会在最后有一个(revoked)
的标识。
shell
gpg --search-keys 你的密钥ID或者当时设置的name
在本地删除公钥和私钥,删除公钥和私钥分别有一条命令,不过我建议你全部删除,执行以下命令:
shell
gpg --delete-secret-and-public-key 你的密钥ID
这样就完成掉密钥的吊销和删除了
第三步 上传公钥
为了让大家都知道你的公钥以做验证,你需要上传的你的密钥,只需要下面的一行命令,它会自己上传到一个地方,然后这个地方的公钥会很快与其它各处地方进行同步,很快你的公钥大家就都知道了,这个就叫做钥匙环。
shell
gpg --send-keys 你的密钥ID
第四步 导出私钥
在 gradle 的最后一步会对上传的包进行签名,这里会用到私钥,需要配置一个私钥的地址,它需要配置在 gradle.properties 文件中(也许可以配置在 gradle 脚本中,但是我没找到具体的办法)。
signing.keyId=你的密钥ID
signing.password=密钥密码
signing.secretKeyRingFile=私钥所在地址
keyId 这里需要填写要签名密钥的ID,这里要填短ID,在第一步的时候讲过如何查看短ID(其实就是密钥ID的后8位)
shell
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 时一样。
因为网上很多都是老的文章,所以它的地址是老的地址,但你依然可以登录,界面和新的也完全一样,但是登录后它会告诉你账号密码不对或者你没有这个权限,就很让人摸不到头脑。
登录上去,网上的文章很多都说去 staging Repositories
选项下去找你刚才发布的包,它会为 staging 的状态,close 后会经过检查,最后点击 release 才能真正进行发布。但我发现并不是这样的,一开始我也很纳闷为什么我的找不到,到底是哪里出问题导致推送失败了,也没有报错,后来我发现在 nexus 的代码库中是可以搜索到我发布的包的,那说明就已经成功了,不用担心:
过一会你就可以在本地通过 gradle 引用你发布的包了~
mavenCentral 的同步通常是 30 分钟,最多可能需要 4 个小时。