赞
踩
更新注明:此文章在编写的时候使用的Gradle版本为5.6.4,如果是比较高版本的Gradle,那么可能会出现一些错误,所以在这边放上最新版本的可以正常编译通过的Demo,并对之前的代码进行更新,使得在高版本的Gradle上也可以正常编译,主要修改点在Java项目生成Doc方式这一节里面,大家可以去查看一下
Gradle5.6.4 Demo:https://github.com/xiaozeiqwe8/MavenJavaDemo5.4.6
本文主要以Gradle Maven-Publish插件的方式进行发布到Maven Central的流程进行说明,附带了手动发布的流程、so库的发布流程、不上传源码和doc的发布流程,并提及说明了发布Jar包和发布aar包的不同方式、Java与Kotlin版本的不同打包方式。
Sonatype公司是负责维护Maven Central的,他们使用Sonatype Nexus Repository Manager 这个产品来管理Maven中央仓库,所以我们要使用Maven Central的话,首先需要注册Sonatype账号。
注册地址:https://issues.sonatype.org/secure/Dashboard.jspa
,点击Sign up进行注册
注册完成后,就可以进行登录了,登录进去后,可以发现,他其实是一个JIRA系统,在这个系统中,我们需要创建一个Ticket,用来申请我们自己的Group Id,这个Group Id熟悉maven的应该都了解,可以看成我们自己库的顶级域名,可以用它来和artifactId以及version组合,简称GAV,来定位到我们的某个版本的资源。
点击Create来创建一个Ticket
https://central.sonatype.org/changelog/#2021-04-01-comgithub-is-not-supported-anymore-as-a-valid-coordinate
填写完成这些信息后,就可以create了,create完成后,你的单子会自动assign给工作人员
工作人员会根据你填写的GroupId的方式,来要求你配合完成一些验证,会在comments里面回复你,在这边,我只说明github的验证方式,域名方式则可自行百度查看验证方式。
然后要求我们在自己的github账号里创建一个public的名称为你这个Ticket的repository,很简单,直接创建一个,注意 Repository名称以及权限是public即可
这样就完成了,然后我们就可以立马去刚刚的Ticket的comments里面告诉工作人员,我们已经完成了这一步
接下来的步骤,我们就是等待了,等待工作人员回复完成即可进行下一步了,我在这一步等待的时间很快,应该不到半小时,至于为什么要等待,可以查看官网的解释https://central.sonatype.org/faq/a-human/
等待到结果会像这样,这时候,我们也不忘谢谢工作人员。
到这一步,我们的Maven仓库就已经可以使用了,使用这个地址进行登录https://s01.oss.sonatype.org
,用户名密码就是你上面注册的工单系统的账密,接下来我们就可以准备我们需要上传的库了。
GPG签名主要是为了给需要发布到maven central的包进行签名,每个发布上去的包都需要进行这个操作,为了接下来我们可以直接使用,我们所以我们先创建一个自己的GPG签名。关于GPG的概念,大家可以自行学习。
可以从以下地址进行下载:http://www.gnupg.org/download/
下载完成后,进行安装,默认都下一步即可完成。
安装完成后,在命令行中,可以查看一下版本,来确认环境以及安装是否成功
这一步我们就可以开始创建GPG密钥了,有两种方式可以生成密钥,一种是通过kleopatra桌面管理程序来生成,另一种是通过CMD来生成,这边我推荐通过命令行来生成密钥,因为我的kleopatra始终都无法打开,如下图:
所以我这边就使用命令行的方式生成,流程也不复杂
使用命令
gpg --gen-key
來進行,
选择default即可,直接回车或输入1
默认2048即可
这边我们设置沒有过期时间,直接回車
输入y
要求输入一个Real name,要大于五个字符
在输入一个邮箱,comment可以不填,输入O來确认
最后需要给密钥设置一个密码
创建成功,我们这边的密钥指纹是77D1 40D6 A015 4401 32C8 AC53 B440 6F47 CCA2 0382,它的后8位CCA20382,我们接下来就可以通过他来进行一些查询操作。
另外需要说明的是,如果是使用linux进行操作,如果使用gpg --gen-key命令没有要求输入很多信息,这样出来的证书默认是2年的有效期限的,所以我们可以使用
gpg --full-generate-key
来生成,这样会要求我们输入很多信息了。
创建成功后,我们可以使用gpg --list-keys来查询所有本地生成的key列表
接下来我们还需要将我们生成的密钥上传到GPG服务器,这样做是为了Maven仓库到时候可以从GPG服务器上拿到我们的密钥对我们上传的包进行验证。
注意:下面的hkp://pool.sks-keyservers.net已经被弃用了(具体详情可以查看https://sks-keyservers.net/overview-of-pools.php),所以请替换成其他的GPG服务器仓库来使用,推荐使用hkp://keyserver.ubuntu.com
另外也可以查看官方说明的仓库来使用https://central.sonatype.org/publish/requirements/gpg/#distributing-your-public-key
上传的命令
gpg --keyserver hkp://pool.sks-keyservers.net --send-keys CCA20382
keyserver固定填写该地址即可,当然也可以选择其他的keyserver,我们这边只用主池就可以,具体也可以参考这个列表来查看:https://sks-keyservers.net/overview-of-pools.php
已废弃
send-keys使用我们上面生成的密钥指纹的后8位
然后我们可以查询一下是否可以查询到我们上传的key,只有能查询到才代表上传成功
查询命令:
gpg --keyserver hkp://pool.sks-keyservers.net --search-keys CCA20382
key上传到主池后,会同步到其他key池,不过这个同步的时间就说不准了,可能会很久,不过不影响我们的使用,因为验证key的时候,maven会去很多key池里面查找,找到一个就可以进行验证,另外,如果是有上传不了主key的同学也可以试一下使用这个地址进行上传hkp://keys.gnupg.net
,我在尝试的时候,感觉这个池生效要来得快一些,并且这个池可以直接去网页上进行查询,地址:http://keys.gnupg.net/
输入你的key id或者是username都可以很快的进行查询。
接下来我们还需要导出我们生成的key的私钥,以便在maven插件里面使用
使用命令:
gpg -o /e/secring.gpg --export-secret-keys CCA20382
这边指定输出到e盘,并指定我们要导出的Key Id,导出后,上传的时候会用到。
上传的方式有很多种,具体可以查看官网上提供的5种方式:
https://central.sonatype.org/publish/publish-guide/#deployment
这边我们使用Gradle上传的方式,因为对于android开发来说,Gradle相对比较熟悉一些。
Gradle的方式主要是使用Gradle提供的Maven-Publish插件进行上传,手动上传方式会在最后补充
我们可以先把根目录下的jcenter()注释掉,改成使用mavenCentral(), 原因大家都知道吧,Google也已经明确指出了“JCenter is at end of life“,
不过这个步骤其实和我们的上传没关系,只是想把JCenter给注释掉
首先,我们打开要上传的lib module里的build.gradle,增加apply maven插件
apply plugin: 'maven-publish'
apply plugin: 'signing'
然后我们在build.gradle里添加maven publish脚本
其脚本为:
publishing { publications { maven(MavenPublication) { groupId 'io.github.xiaozeiqwe8' //开通maven central时候定义的 artifactId 'dialog-lib' //资源名称 version '0.9.4' //版本名称 pom { name = 'dialog-lib' description = 'dialog lib' url = 'https://github.com/xiaozeiqwe8/DialogLib' inceptionYear = '2021' scm { url = 'https://github.com/xiaozeiqwe8/DialogLib' connection = 'scm:git@https://github.com/xiaozeiqwe8/DialogLib.git' developerConnection = 'scm:git@https://github.com/xiaozeiqwe8/DialogLib.git' } licenses { license { name = 'The Apache Software License, Version 2.0' url = 'http://www.apache.org/licenses/LICENSE-2.0.txt' distribution = 'repo' comments = 'A business-friendly OSS license' } } developers { developer { id = 'xiaozei' name = 'karl' email = 'xiaozeiqwe8@163.com' url = 'https://github.com/xiaozeiqwe8/DialogLib' } } issueManagement { system = 'Github' url = 'https://github.com/xiaozeiqwe8/DialogLib' } } artifact generateSourcesJar //需要上传的source jar artifact generateJavadoc //需要上传的java doc artifact makeJar //需要上传的资源jar路径或者是aar路径,这边可以填写一个生成jar的task,如makeJar方法就是一个生成jar的task } } repositories { maven { // 指定要上传的maven仓库 url = 'https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/' //Maven仓库用户名和密码 credentials { username 'xiaozeiqwe8' password password } } } } signing { sign publishing.publications.maven //签名配置 }
上面的配置中,有几项需要单独说明
maven { url 'https://s01.oss.sonatype.org/content/repositories/snapshots/'}
接下来,建议可以将上面的这些属性值,把它抽离出来使用,这样就可以避免每次都要去修改build.properties,我们可以将这些值定义在gradle.properties中,这样全局都可以使用,如下:
在gradle.properties里面定义:
GROUP_ID=io.github.xiaozeiqwe8
ARTIFACT_ID=vox-sdk
VERSION_NAME=0.9.2-SNAPSHOT
.....
.....
然后在build.gradle中的maven配置中就可以使用下面的写法:
maven(MavenPublication) {
groupId GROUP_ID
artifactId ARTIFACT_ID
version VERSION_NAME
}
这样,就可以将配置参数和脚本分离开来。
GPG签名需要我们在gradle.properties里面增加三个属性值:
signing.keyId=AC6197AE
signing.password=11111111
signing.secretKeyRingFile=../secring.gpg
这边的keyId和password就是我们之前GPG生成的时候自己输入的,这边的secretKeyRingFile就是我们导出的gpg私钥,我们可以将其放在工程下,同时可以在ignore文件中忽略它的Git提交,并且gradle.properties文件也建议忽略掉,这样我们的密码只在本地就降低了泄漏的风险。
补充:我们上一步中,如果已经从maven脚本中,将配置参数分离到gradle.properties里面的话,其实我们还可以将敏感信息和maven配置参数分离,不要都放在gradle.properties里面,这样做的好处是我们可以提交maven的properties文件配置参数,但是maven账号密码和gpg的签名配置我们可以不用提交。
我们可以新建一个maven.properties文件,然后将gradle.properties里面抽离maven的基本配置(即除了敏感参数以外的所有配置)到maven.properties里面,然后在build.gradle里面引入:
Properties properties = new Properties()
properties.load(project.rootProject.file('maven.properties').newDataInputStream())
//定义properties
def GROUP_ID = properties.getProperty('GROUP_ID')
def ARTIFACT_ID = properties.getProperty('ARTIFACT_ID')
def VERSION_NAME = properties.getProperty('VERSION_NAME')
def .............
def .............
这样,我们的maven中的脚本就可以不需要修改,从而实现了配置参数的分离。
maven.properties内容:
GROUP_ID=io.github.xiaozeiqwe8 ARTIFACT_ID=dialog-lib POM_PACKAGING=jar #ARTIFACT_ID=dialog-lib #CUSTOM_ARTIFACT=./build/outputs/aar/dialogLibs-release.aar #POM_PACKAGING=aar VERSION_NAME=0.9.6+1.0.2 POM_NAME=dialog-lib POM_URL=https://github.com/xiaozeiqwe8/DialogLib POM_DESCRIPTION=dialog lib POM_SCM_URL=https://github.com/xiaozeiqwe8/DialogLib POM_SCM_CONNECTION=scm:git@https://github.com/xiaozeiqwe8/DialogLib.git POM_SCM_DEV_CONNECTION=scm:git@https://github.com/xiaozeiqwe8/DialogLib.git POM_LICENCE_COMMENTS=A business-friendly OSS license POM_LICENCE_NAME=The Apache Software License, Version 2.0 POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt POM_LICENCE_DIST=repo POM_DEVELOPER_ID= xiaozei #自行填写开发者id POM_DEVELOPER_NAME= karl #自行填写开发者姓名 POM_DEVELOPER_EMAIL= xiaozeiqwetw@gmail.com #自行填写开发者邮箱 POM_DEVELOPER_URL=https://github.com/xiaozeiqwe8/DialogLib POM_ISSUE_MANAGEMENT_SYSTEM=Github POM_ISSUE_MANAGEMENT_URL=https://github.com/xiaozeiqwe8/DialogLib POM_INCEPTION_YEAR=2021 #将以下信息拷贝到gradle.properties中填写,不提交gradle.properties文件,保证敏感数据的安全 #MAVEN_USERNAME= #MAVEN_PASSWORD= #signing.keyId= #signing.password= #signing.secretKeyRingFile=
Jar包的上传需要带以下内容
这三组是我们需要最终生成出来的,分别是
dialog-lib.jar, dialog-lib.jar.asc
dialog-lib-javadoc.jar, dialog-lib-javadoc.jar.asc
dialog-lib-sources.jar, dialog-lib-sources.jar.asc
asc后缀的包我们会通过sign插件自动生成出来,而除了这三组以外,其实还需要一组pom后缀的文件和asc,这个文件我们使用Gradle Maven-Publish插件其内部会给我们自动生成,所以我们不需要关心它。
接下来的步骤中,我们就会来生成我们需要关心的dialog-lib.jar,dialog-lib-javadoc.jar,dialog-lib-sources.jar
AAR包的上传资源只需要准备aar包即可,其他部分插件内部都会帮我们生成,并且不需要单独生成javasource和javadoc。
如果上传的资源是AAR,这一步可以跳过
生成JavaSource的话,可以使用下面的脚本:
task generateSorceJar(type: Jar) {
archiveClassifier.set( 'sources')
from sourceSets.main.java.srcDirs
}
其中,archiveClassifier.set( ‘sources’) 可以不指定,但是如果不指定,默认打出来的jar包的名称默认是模块名称,所以我们指定Classifier后,相当于是打了一个分包,jar包名称会变成[moduleName]-sources这样,正好符合我们上传的包名称,并且这个task像上面说的是可以直接作为artifact的参数来用的。
执行该task,打出来的包会在build/libs下
如果上传的资源是AAR,这一步可以跳过
生成JavaDoc的方式会有点麻烦,因为Kotlin需要借助到一个插件dokka才能实现生成出来的像JavaDoc一样的帮助文档,而Java在使用上会有一个坑,下面会分别说一下Java和Kotlin的生成方式:
module目录下的build.gradle下,在android作用域下添加task:
task javadoc(type: Javadoc) {
source = android.sourceSets.main.java.srcDirs
classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) + configurations.compile
// 高版本下可能要添加:doNotTrackState("Javadoc task does not impact the outcome of the build.")
}
配置编码为UTF-8以支持中文
javadoc {
options {
encoding "UTF-8"
charSet 'UTF-8'
author true
version true
links "http://docs.oracle.com/javase/8/docs/api"
}
}
在dependencies下添加:
dependencies {
compile 'com.android.support:support-annotations:28.0.0'
}
Update: 这边推荐使用高版本的写法,这样可以保证在Gradle7下也能正常编译
主要修改是在build.gradle开头加入configurations { javadocDeps } 将configurations.compile修改成configurations.javadocDeps dependencies中加入
javadocDeps ‘com.android.support:support-annotations:28.0.0’`
`
添加这个是为了能够让java doc可以认识androidx.annotation包下的class,如果不加这个,并且你的代码中有使用到了一些annotation下的内容,如@NonNull注解,则会报错,生成失败。
添加完成同步后,就可以执行javadoc task,会在.build文件夹下生成docs
然后添加我们的generateJavadoc的task
task generateJavadoc(type: Jar, dependsOn:['javadoc']) {
group = 'jar'
archiveClassifier.set('javadoc')
from 'build/docs/javadoc'
}
module下的build.gradle内,增加引用dokka的配置:
plugins {
id 'com.android.library'
id 'kotlin-android'
id("org.jetbrains.dokka") version ("1.4.32")
}
这边直接指定了版本为1.4.32,官方文档可以参考这个:https://kotlin.github.io/dokka/1.4.32/user_guide/gradle/usage/
在dependencies 增加:
dependencies {
dokkaPlugin("org.jetbrains.dokka:kotlin-as-java-plugin:1.4.32")
}
然后我们可以再配置一个生成路径,默认如果不配置则是在
build/dokka/javadoc下:
tasks.named("dokkaJavadoc") {
outputDirectory.set(new File("$rootDir/javadoc"))
}
到这一步,就可以生成了,可以选择task执行:
在生成的过程中,默认程序会去连android获取package-list,这一步如果没有VPN的话,需要等待它全部重试完才行,像这样
虽然最后能编译通过,但是太耗费时间,为了解决这个问题,我们可以增加配置,让其不去获取该package-list
dokkaJavadoc {
dokkaSourceSets {
configureEach { // Or source set name, for single-platform the default source sets are `main` and `test`
// Disable linking to online kotlin-stdlib documentation
noStdlibLink.set(true)
// Disable linking to online JDK documentation
noJdkLink.set(true)
// Disable linking to online Android documentation (only applicable for Android projects)
noAndroidSdkLink.set(true)
}
}
}
最后,我们需要将javadoc打成jar包,以供maven脚本上传使用,我们可以写个task来做这个事情,这样打出来的jar包名称也是符合我们上传要求的:
task generateJavadoc(type: Jar, dependsOn: dokkaJavadoc) {
group = 'jar'
archiveClassifier.set('javadoc')
from "$rootDir/javadoc"
}
需要上传的包如果是AAR包,则直接在maven的publish脚本中配置artifact 参数的路径为aar的路径即可,如配置
artifact './build/outputs/aar/dialogLibs-release.aar'
Maven的配置里面,我们可以直接指定需要上传的Jar的路径即可,我们可以在 build/intermediates/aar_main_jar/release/classes.jar 找到我们的编译后产生的jar包。
另外我们也可以像上面配置的一样,使用一个task直接指定到artifact参数上,就和javadoc和javasource一样,这也是推荐的做法,我们的task可以这样写:
task generateMergeJar(type: Jar, dependsOn:['assembleRelease']){
group = 'jar'
from(project.zipTree('build/intermediates/aar_main_jar/release/classes.jar'))
}
这边需要注意的是,这边我们是需要打一个主包,所以这边生成出来的jar名称,未指定classifier,这种情况下的jar默认会是和你的module名称一致的,这样在上传到maven后,在真正使用的时候Android Studio才能够正确的识别到这个jar包,如果你的artifactId和你的Module名称不一致,需要你自己去将这个jar包的名称修改成一致的才行,否则可能会造成你上传的资源最终Android Studio即使下载依赖了也认不到。
我在测试下来发现,如果你带的是真实的javadoc和javasource,并且javadoc和javasource的包名和artifactId对得上,那么上传上去的主名称即使不和artifactId一致,也会自动为你修改,但是如果是虚拟的javadoc和javasource或者这2个的名称也是随意的,那就不会自动帮你修改。
另外,这样做有一个好处是你可以将其他的jar也合并进来,组成一个jar包上传上去,只需要再加一个from即可,这可以将你的一些资源比如so库一起打进去上传到Maven Central上。
这一步是为了如果有些库因为特殊原因不能上传JavaSource或JavaDoc的,可以使用虚拟的source包和doc包,也是可以通过maven的验证的,不过官方上当然是不推荐这样做。
我们可以在根目录下新建一个javasource的目录,然后在里面新建一个空的README文件,像这样:
然后我们就可以使用下面的task来打这个虚拟jar包:
task generateSorceJar(type: Jar) {
//dummy doc
group = 'jar'
archiveClassifier.set( 'sources')
from "$rootDir/mavenwork/javasource"
}
同样,我们直接将这个task作为artifact的value即可,同理JavaDoc也是一样处理,最终我们会有3个artifact的配置在Maven脚本中:
这一步做完后我们就可以使用publish命令进行上传了
等待task执行成功后,去Maven Central的https://s01.oss.sonatype.org/#stagingRepositories
仓库中查看
在这一步,可以下载jar包或者是doc之类的文件进行验证正确性,如果有问题,可以点击Drop删除掉这个版本的信息后,再重新上传。
最终都没问题后,就可以进行release了,release的步骤是直接点击Close按钮,点击Confirm
这时候,maven仓库会进行验证,等待验证完成后,Refresh一下,即可出现Release按钮:
点击Release,Confirm后,即完成发布流程。
发布完成后,可以到https://s01.oss.sonatype.org/#welcome
页面输入你的GroupId进行查看:
注意:这边查看到了不代表立马就能在Android Studio中引用到,一般需要等待一会儿时间,这个时间我这边基本上10分钟以内可以完成。
我们可以通过这个网站来查看你发布的库有没有同步到maven中央仓库:https://repo1.maven.org/maven2/
查到发布的版本后,接下来的步骤,就是在Android Studio中引用啦,使用GAV进行资源定位:
dependencies {
implementation 'io.github.xiaozeiqwe8:dialog-lib:0.9.4'
}
如果需要将So库也引入依赖,我们可以采用将so库打成一个jar包,然后上传到maven的方式来实现,这个jar包需要有固定的包目录结构,才能被最终打入apk的正确路径下,查看apk的so存放路径,我们可以发现
其最终会被打入lib目录下,所以我们的so打的jar包,只要是这个目录格式,最后在打包成apk的时候,也会将so打入该目录下,并且可以被引用到,利用这一点,我们来打so库
在项目下新建一个soLibs目录,并在其下层新建lib目录,然后将so根据架构名称分文件夹全部放入其中
编写task:
task generateSoJar(type: Jar) {
group = 'jar'
archiveClassifier.set( 'so')
from('../soLibs/')
}
然后修改generateMergeJar的task
task generateMergeJar(type: Jar, dependsOn:['assembleRelease','generateSoJar']){
group = 'jar'
from(project.zipTree('build/libs/aiengine-so.jar'))
from(project.zipTree('build/intermediates/aar_main_jar/release/classes.jar'))
}
这样,我们就完成了将so和我们的jar合并在一起,并可以直接通过maven脚本上传的功能。
新建一个后缀名为pom的文件,其内容如下:
<?xml version="1.0" encoding="UTF-8"?> <project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <modelVersion>4.0.0</modelVersion> <groupId>io.github.xiaozeiqwe8</groupId> <artifactId>dialog-lib</artifactId> <version>0.9.6+1.0.2</version> <name>dialog-lib</name> <description>dialog lib</description> <url>https://github.com/xiaozeiqwe8/DialogLib</url> <inceptionYear>2021</inceptionYear> <licenses> <license> <name>The Apache Software License, Version 2.0</name> <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url> <distribution>repo</distribution> <comments>A business-friendly OSS license</comments> </license> </licenses> <developers> <developer> <id>xiaozei</id> <name>karl</name> <email>xiaozeiqwe8@163.com</email> <url>https://github.com/xiaozeiqwe8/DialogLib</url> </developer> </developers> <scm> <connection>scm:git@https://github.com/xiaozeiqwe8/DialogLib.git</connection> <developerConnection>scm:git@https://github.com/xiaozeiqwe8/DialogLib.git</developerConnection> <url>https://github.com/xiaozeiqwe8/DialogLib</url> </scm> <issueManagement> <system>Github</system> <url>https://github.com/xiaozeiqwe8/DialogLib</url> </issueManagement> </project>
这个Pom文件中的内容基本上与在Gradle插件中配置的一样,这边不再描述
资源包一样分为主包,javadoc,sources这三个jar包,其命名方式为[artifactId]-[version]-[分包名称].jar,如下图:
提示:这边的javadoc和sources一样可以用虚拟创建的
这样我们就有了4个文件,分别对这4个文件进行签名,使用gpg命令:
gpg -abu CCA20382 dialog-lib-0.9.6+1.0.2.jar
签名完成后,我们得到了所有的8个文件
使用命令:
jar -cvf bundle.jar dialog-lib-0.9.6+1.0.2.jar dialog-lib-0.9.6+1.0.2.jar.asc dialog-lib-0.9.6+1.0.2-javadoc.jar dialog-lib-0.9.6+1.0.2-javadoc.jar.asc dialog-lib-0.9.6+1.0.2-sources.jar dialog-lib-0.9.6+1.0.2-sources.jar.asc dialog-lib-0.9.6+1.0.2.pom dialog-lib-0.9.6+1.0.2.pom.asc
生成出来bundle.jar后我们就可以进行上传了
进入https://s01.oss.sonatype.org/#staging-upload
,选择UploadMode为Artifact Bundle,点击Select Bundle to Upload选择你本地需要上传的包
然后点击Upload Bundle,等待进度条完成上传
上传完成的包会自动进行Close的操作,即自动验证这个包,所以可以直接进行Release的步骤,Release的步骤和第7步一致。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。