Android SDK 上传 Maven 喂奶级教程

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

前期准备

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

./upload-maven/image-20220423102431037

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 中也可以看到相关的提醒;

./upload-maven/image-20220423113702766

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

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

./upload-maven/image-20220423121058261

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

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

./upload-maven/image-20220423121843328

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

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

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

./upload-maven/image-20220423125736170 ./upload-maven/image-20220423125925379

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

./upload-maven/image-20220423130136150

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 点提交的话过一会就能收到回复。下面是我的回复:

./upload-maven/image-20220423132142968

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

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

./upload-maven/image-20220423133224293

收到这样的回复就代表你已经通过审核了。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 任务。

./upload-maven/image-20220423141427692./upload-maven/image-20220423141511496

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

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

./upload-maven/image-20220423142105170

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

./upload-maven/image-20220423142413216

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

# 产物: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.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 声明的是要上传的东西,可以声明多行,每行一个,它自己会把它们集合到一起,也可以用大括号的形式声明一个列表

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.srcDirs
    classifier = 'sources'
}

思路一:写一个 gradle plugin

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

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

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()
        }
    }
}

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

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 的任务,没有的话就创建添加一个,然后让它来处理我们源码路径下的所有文件。

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 也正确。

./upload-maven/image-20220423162713383

到了应用的时候了,先在项目的 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 false
    id '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.srcDirs
    filter(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.srcDirs
    classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
    classifier = 'javadoc'
    from javadoc.destinationDir
}

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

./upload-maven/image-20220423204932980

如果没有特殊要求的话那么直接用这个就可以了,但我们是商业的 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

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

./upload-maven/image-20220423215320258

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

unknown public key algorithm encountered

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

./upload-maven/image-20220423215622396

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

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

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

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

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

./upload-maven/image-20220424114114355

红框内是你的密钥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
./upload-maven/image-20220424113249727

第四步 导出私钥

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

signing.keyId=你的密钥ID
signing.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 文件夹,一个正常的发布文件是这样的:

./upload-maven/image-20220424151220226

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

./upload-maven/image-20220424151456098

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

./gradlew publish

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

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

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

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

./upload-maven/image-20220424153532744

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/379647.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

深度学习在知识图谱问答中的革新与挑战

目录 前言1 背景知识2 基于深度学习改进问句解析模型2.1 谓词匹配2.2 问句解析2.3 逐步生成查询图 3 基于深度学习的端到端模型3.1 端到端框架3.2 简单嵌入技术 4 优势4.1 深入的问题表示4.2 实体关系表示深挖4.3 候选答案排序效果好 5 挑战5.1 依赖大量训练语料5.2 推理类问句…

【原创 附源码】Flutter海外登录--Tiktok登录最详细流程

最近接触了几个海外登录的平台&#xff0c;踩了很多坑&#xff0c;也总结了很多东西&#xff0c;决定记录下来给路过的兄弟坐个参考&#xff0c;也留着以后留着回顾。更新时间为2024年2月7日&#xff0c;后续集成方式可能会有变动&#xff0c;所以目前的集成流程仅供参考&#…

uniapp的配置和使用

①安装环境和编辑器 注册小程序账号 微信开发者工具下载 uniapp 官网 HbuilderX 下载 首先先下载Hbuilder和微信开发者工具 &#xff08;都是傻瓜式安装&#xff09;&#xff0c;然后注册小程序账号&#xff1a; 拿到appid&#xff1a; ②简单通过demo使用微信开发者工具和…

Ribbon全方位解析:构建弹性的Java微服务

第1章 引言 大家好,我是小黑,咱们今天聊聊Ribbon,这货是个客户端负载均衡工具,用在Spring Cloud里面能让咱们的服务调用更加灵活和健壮。负载均衡,听起来挺高大上的,其实就是把外界的请求平摊到多个服务器上,避免某个服务器压力太大,其他的却在那儿闲着。 Ribbon的牛…

npm install express -g报错或一直卡着,亲测可解决

问题描述&#xff1a; 最近学习vue3前端框架&#xff0c;安装Node.js之后&#xff0c;在测试是否可行时&#xff0c;cmd窗口执行了&#xff1a;npm install express -g&#xff0c;发现如下图所示一直卡着不动&#xff0c;最后还报错了&#xff0c;网上找了好久&#xff0c;各…

Springboot根据环境读取application配置文件

目录 1. 首先创建两个不同配置文件 2. pom.xml 配置文件 3. 指定环境 4. 最后启动测试 1. 首先创建两个不同配置文件 分别为开发环境和生产环境 application-dev.properties 和 application-prod.properties application-dev.properties 配置为 1931 端口 application-pro…

12个最常用的matplotlib图例 !!

文章目录 1、折线图 2、散点图 3、直方图 4、柱状图 5、箱线图 6、热力图 7、饼图 8、面积图 9、等高线图 10、3D图 11、时间序列图 12、树状图 总结 1、折线图 折线图&#xff08;Line Plot&#xff09;&#xff1a;用于显示数据随时间或其他连续变量的变化趋势。在实际项目中…

奇异果投屏的进化之路

笔者按&#xff1a;奇异果投屏伴随奇异果TV一路发展至2022年&#xff0c;日活用户已达300多万&#xff0c;用户和我们都对投屏的功能和性能提出了更多的诉求和更高要求&#xff0c;因此2022开始系统地对投屏功能和性能做了扩展和优化。本文立足于TV端&#xff0c;为大家介绍爱奇…

百家cms代审

环境搭建 源码链接如下所示 https://gitee.com/openbaijia/baijiacms 安装至本地后 直接解压到phpstudy的www目录下即可 接下来去创建一个数据库用于存储CMS信息。&#xff08;在Mysql命令行中执行&#xff09; 接下来访问CMS&#xff0c;会默认跳转至安装界面 数据库名称和…

spring boot(2.4.x 开始)和spring cloud项目中配置文件application和bootstrap加载顺序

在前面的文章基础上 https://blog.csdn.net/zlpzlpzyd/article/details/136060312 spring boot 2.4.x 版本之前通过 ConfigFileApplicationListener 加载配置 https://github.com/spring-projects/spring-boot/blob/v2.3.12.RELEASE/spring-boot-project/spring-boot/src/mai…

ElasticSearch之search API

写在前面 本文看下查询相关内容&#xff0c;这也是我们在实际工作中接触的最多的&#xff0c;所以有必要好好学习下&#xff01; 1&#xff1a;查询的分类 主要分为如下2类&#xff1a; 1:基于get查询参数的URI search 2&#xff1a;基于post body的request body search&am…

8868体育助力法甲巴黎圣日耳曼俱乐部 运作球员转会

法甲的巴黎圣日耳曼足球俱乐部是8868的体育助力球队之一&#xff0c;根据法国媒体RMC的消息&#xff0c;巴黎圣日尔曼仍然希望在一月份增强球队的后防实力。虽然之前球队已经从圣保罗引进了20岁的巴西中后卫卢卡斯-贝拉尔多&#xff0c;而这名小将也将会是巴黎圣日耳曼冬窗的一…

nodejs+vue高校实验室耗材管理系统_m20vy

用户功能&#xff1a; 登录后要有一个首页 比如:可以看见目前的耗材消耗记录&#xff0c;可做成图表菜单栏在左侧显示 1.个人信息管理 可以对基本信息进行修改&#xff0c;(修改密码时需要验证) 2.耗材管理&#xff08;耗材信息&#xff09; 普通用户可以查询当前相关耗材信息[…

Easy Excel动态表头的实现

步骤&#xff1a; 1.查找官方API文档理解实现 2.实现融入到代码里面 一&#xff1a;Easy Excel动态头实时生成头写入 动态头实时生成头写入 二&#xff1a;实现 目的&#xff1a;实现表头为&#xff0c;第一列是固定列&#xff0c;第二列为动态生成的时间段的每一天的日期…

【JAVA WEB】CSS

目录 CSS是什么&#xff1f; 基本语法规范 引入方式 内部样式表 行内样式表 外部样式表 常用选择器的种类 基础选择器 标签选择器 类选择器 id选择器 通配符选择器 复合选择器 后代选择器 伪类选择器 常用元素属性&#xff1a; 字体属性&#xff1a; 文本属性…

初始web服务器(并基于idea来实现无需下载的tomcat)

前言 前面学习了对应的http协议&#xff0c;我们知道了他是在网络层进行数据传输的协议&#xff0c;负责相应数据以及接收数据的规则&#xff0c;但是在人员开发后端的时候不仅仅需要你写io流进行数据传输&#xff0c;还需要你进行对应的tcp协议来进行数据打包发送http协议-CSD…

Elasticsearch: 非结构化的数据搜索

很多大数据组件在快速原型时期都是Java实现&#xff0c;后来因为GC不可控、内存或者向量化等等各种各样的问题换到了C&#xff0c;比如zookeeper->nuraft(https://www.yuque.com/treblez/qksu6c/hu1fuu71hgwanq8o?singleDoc# 《olap/clickhouse keeper 一致性协调服务》)&a…

安卓服务的常见问题,性能优化以及应用场景剖析

一、引言 在安卓开发中&#xff0c;服务&#xff08;Service&#xff09;扮演着至关重要的角色&#xff0c;它们在没有用户界面的情况下&#xff0c;为用户提供了长时间的后台任务执行能力。本文将探讨服务常见问题、优化策略、应用场景以及开发过程中应注意的事项。 二、应用场…

按键扫描16Hz-单片机通用模板

按键扫描16Hz-单片机通用模板 一、按键扫描的原理1、直接检测高低电平类型2、矩阵扫描类型3、ADC检测类型二、key.c的实现1、void keyScan(void) 按键扫描函数①void FHiKey(void) 按键按下功能②void FSameKey(void) 按键长按功能③void FLowKey(void) 按键释放功能三、key.h的…

Qt PCL学习(二):点云读取与保存

注意事项 版本一览&#xff1a;Qt 5.15.2 PCL 1.12.1 VTK 9.1.0前置内容&#xff1a;Qt PCL学习&#xff08;一&#xff09;&#xff1a;环境搭建 0. 效果演示 1. pcl_open_save.pro QT core guigreaterThan(QT_MAJOR_VERSION, 4): QT widgets// 添加下行代码&#…
最新文章