我经常被 android 开发社区中如此多而且好用的第三方库所震惊。有很长的一段时间,我想贡献一些东西,但我不知道如何去做。在浏览了其他很多关于如何发布一个 android 开发库的文章后,我仍然发现缺失了一些细节,而且,所有的信息都是在不同的地方。所以,我将完整的走过这个过程,向大家展示我的做法。
对新手来说,我推荐使用 Android Studio 来创建所有的 Android 项目,Android Studio官方使用 Gradle 构建系统。请确保你下载了 Android Studio 的最新版。
![]()
相关术语介绍
在我们开始之前,还有一些术语,需要熟悉下。
项目(Project) — 在 Android Studio 中,一个 项目 就是一个完整的 Android app。Android Studio 项目包含了一个或多个模块。 在 Android Studio 中,一个 项目 类似于在 Eclipse 的一个工作区间( workspace )。
模块( Module) – 一个 模块 是 app 中的一个组件,它可以单独的进行构建、测试和调试。模块包含了 app 的源代码和资源文件。在 Android Studio 中,一个 模块 类似于在 Eclipse 的一个项目。
AAR – ‘aar’ 套件是 Android 开发库项目的二进制的分发形式。(AAR 格式)开发库项目的主要产出就是 .aar 包(意思是 Android 压缩包)。它是由编译后的代码(如 jar 文件或者 .so 文件)和资源文件(如 manifest 文件、res 文件、asset 文件)组合而成的。
Maven 中央仓库 – 由 Maven 社区提供的仓库。它包含了很多我们常用的开发库。 Search Maven 网站可用来浏览 maven 中央仓库的内容。Gradle, Please 网站是另一个可用来搜索中央仓库的工具。如果你在项目配置文件的仓库配置部分添加了 jCenter() ,那么 Gradle 将使用 jCenter 仓库( jCenter 的说明)。Maven 中央仓库也经常被称作 Maven 中心或者中央仓库。
Sonatype — Sonatype的开源软件仓库托管(OSSRH)服务是项目作者和贡献者们发布他们的组件到中央仓库的主要途径。它是 Sonatype Nexus Professional 组织利用 Nexus Staging Suite 工具,对开源项目提供部署托管服务,该服务主要用来处理部署和验证操作,也提供同步操作将内容通过网络投递到中央仓库。
GPG – GNU 隐私保护组织 (也称为 GPG 或者 GnuPG),这个 GNU 项目是一个加密软件,遵循 OpenPGP (RFC4880)标准,是 PGP 的免费替代品。使用 GPG 你可以加密(解密)包含敏感数据的文件,比如那些由健康保险携带和责任法案 (HIPAA) 制定的受保护的隐私和安全方面的电子健康信息。想了解 GPG 的更多信息,请访问 GNU Privacy Guard website。
准备好你的 Android 开发库
我将使用我的 Trestle 开发库作例子来讲解。在你的项目中,需要修改一些地方,来准备作为一个开发库发布到 Maven 中央仓库中。
- 将开发库的核心代码和示例代码区分开来。在我的项目中,我将他们分成 library 和 sample 两个模块。请仔细阅读关于创建一个开发库模块的技巧。你也可能需要重命名你的模块。
- 在 sample 模块的 build.gradle 文件中,请确保包含以下内容:
- apply plugin: 'com.android.application'
-
- dependencies {
-
- compile project(':library')
- }
- 在 library 模块的 build.gradle 文件中,请确保包含以下内容:
- apply plugin: 'com.android.library'
-
- apply from: 'maven-push.gradle'
- 在 library 模块中,增加 gradle.properties 文件,请确保在该文件中包含以下内容:
- POM_NAME=ProjectName
-
- POM_ARTIFACT_ID=projectname
-
- POM_PACKAGING=aar
- 在 library 模块中,增加 maven-push.gradle 文件,请确保在该文件中包含以下内容:
- /*
-
- * Copyright 2013 Chris Banes
-
- *
-
- * Licensed under the Apache License, Version 2.0 (the "License");
-
- * you may not use this file except in compliance with the License.
-
- * You may obtain a copy of the License at
-
- *
-
- * http://www.apache.org/licenses/LICENSE-2.0
-
- *
-
- * Unless required by applicable law or agreed to in writing, software
-
- * distributed under the License is distributed on an "AS IS" BASIS,
-
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-
- * See the License for the specific language governing permissions and
-
- * limitations under the License.
-
- */
-
-
-
- apply plugin: 'maven'
-
- apply plugin: 'signing'
-
-
-
- def isReleaseBuild() {
-
- return VERSION_NAME.contains("SNAPSHOT") == false
-
- }
-
-
-
- def getReleaseRepositoryUrl() {
-
- return hasProperty('RELEASE_REPOSITORY_URL') ? RELEASE_REPOSITORY_URL
-
- : "https://oss.sonatype.org/service/local/staging/deploy/maven2/"
-
- }
-
-
-
- def getSnapshotRepositoryUrl() {
-
- return hasProperty('SNAPSHOT_REPOSITORY_URL') ? SNAPSHOT_REPOSITORY_URL
-
- : "https://oss.sonatype.org/content/repositories/snapshots/"
-
- }
-
-
-
- def getRepositoryUsername() {
-
- return hasProperty('NEXUS_USERNAME') ? NEXUS_USERNAME : ""
-
- }
-
-
-
- def getRepositoryPassword() {
-
- return hasProperty('NEXUS_PASSWORD') ? NEXUS_PASSWORD : ""
-
- }
-
-
-
- afterEvaluate { project ->
-
- uploadArchives {
-
- repositories {
-
- mavenDeployer {
-
- beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
-
-
-
- pom.groupId = GROUP
-
- pom.artifactId = POM_ARTIFACT_ID
-
- pom.version = VERSION_NAME
-
-
-
- repository(url: getReleaseRepositoryUrl()) {
-
- authentication(userName: getRepositoryUsername(), password: getRepositoryPassword())
-
- }
-
- snapshotRepository(url: getSnapshotRepositoryUrl()) {
-
- authentication(userName: getRepositoryUsername(), password: getRepositoryPassword())
-
- }
-
-
-
- pom.project {
-
- name POM_NAME
-
- packaging POM_PACKAGING
-
- description POM_DESCRIPTION
-
- url POM_URL
-
-
-
- scm {
-
- url POM_SCM_URL
-
- connection POM_SCM_CONNECTION
-
- developerConnection POM_SCM_DEV_CONNECTION
-
- }
-
-
-
- licenses {
-
- license {
-
- name POM_LICENCE_NAME
-
- url POM_LICENCE_URL
-
- distribution POM_LICENCE_DIST
-
- }
-
- }
-
-
-
- developers {
-
- developer {
-
- id POM_DEVELOPER_ID
-
- name POM_DEVELOPER_NAME
-
- }
-
- }
-
- }
-
- }
-
- }
-
- }
-
-
-
- signing {
-
- required { isReleaseBuild() && gradle.taskGraph.hasTask("uploadArchives") }
-
- sign configurations.archives
-
- }
-
-
-
- //task androidJavadocs(type: Javadoc) {
-
- //source = android.sourceSets.main.allJava
-
- //}
-
-
-
- //task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) {
-
- //classifier = 'javadoc'
-
- //from androidJavadocs.destinationDir
-
- //}
-
-
-
- task androidSourcesJar(type: Jar) {
-
- classifier = 'sources'
-
- from android.sourceSets.main.java.sourceFiles
-
- }
-
-
-
- artifacts {
-
- archives androidSourcesJar
-
- }
-
- }
- # [Android] ========================
-
- # Built application files
-
- *.apk
-
- *.ap_
-
-
-
- # Files for the Dalvik VM
-
- *.dex
-
-
-
- # Java class files
-
- *.class
-
-
-
- # Generated files
-
- bin/
-
- gen/
-
-
-
- # Gradle files
-
- .gradle/
-
- build/
-
-
-
- # Local configuration file (sdk path, etc)
-
- local.properties
-
-
-
- # Proguard folder generated by Eclipse
-
- proguard/
-
-
-
- # Log Files
-
- *.log
-
-
-
- ## Directory-based project format:
-
- .idea/
-
-
-
- ## File-based project format:
-
- *.ipr
-
- *.iws
-
-
-
- ## Plugin-specific files:
-
-
-
- # IntelliJ
-
- out/
-
-
-
- # mpeltonen/sbt-idea plugin
-
- .idea_modules/
-
-
-
- # JIRA plugin
-
- atlassian-ide-plugin.xml
-
-
-
- # Crashlytics plugin (for Android Studio and IntelliJ)
-
- com_crashlytics_export_strings.xml
-
-
-
- # [Maven] ========================
-
- target/
-
- pom.xml.tag
-
- pom.xml.releaseBackup
-
- pom.xml.versionsBackup
-
- pom.xml.next
-
- release.properties
-
-
-
- # [Gradle-Android] ========================
-
-
-
- # Ignore Gradle GUI config
-
- gradle-app.setting
-
-
-
- # Gradle Signing
-
- signing.properties
-
- trestle.keystore
-
-
-
- # Mobile Tools for Java (J2ME)
-
- .mtj.tmp/
-
-
-
- # Package Files #
-
- *.jar
-
- *.war
-
- *.ear
-
-
-
- # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
-
- hs_err_pid*
-
-
-
- # Misc
-
- /.idea/workspace.xml
-
- .DS_Store
-
- /captures
-
- **/*.iml
-
- *.class
- 修改在项目根目录的 settings.gradle 文件
- include ':sample', ':library'
- 修改在项目根目录的 gradle.properties 文件
- # Project-wide Gradle settings.
-
-
-
- # IDE (e.g. Android Studio) users:
-
- # Gradle settings configured through the IDE *will override*
-
- # any settings specified in this file.
-
-
-
- # For more details on how to configure your build environment visit
-
- # http://www.gradle.org/docs/current/userguide/build_environment.html
-
-
-
- # Specifies the JVM arguments used for the daemon process.
-
- # The setting is particularly useful for tweaking memory settings.
-
- # Default value: -Xmx10248m -XX:MaxPermSize=256m
-
- # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
-
-
-
- # When configured, Gradle will run in incubating parallel mode.
-
- # This option should only be used with decoupled projects. More details, visit
-
- # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
-
- # org.gradle.parallel=true
-
-
-
- VERSION_NAME=0.0.1
-
- VERSION_CODE=1
-
- GROUP=com.github.github_username
-
-
-
- POM_DESCRIPTION=A library that does X, Y, and Z
-
- POM_URL=https://github.com/github_username/ProjectName
-
- POM_SCM_URL=https://github.com/github_username/ProjectName
-
- POM_SCM_CONNECTION=scm:git@github.com:github_username/ProjectName.git
-
- POM_SCM_DEV_CONNECTION=scm:git@github.com:github_username/ProjectName.git
-
- 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=github_username
-
- POM_DEVELOPER_NAME=GitHub FullName
- 增加 README.md 文件,向其他开发者介绍你的开发库以及如何使用它。如果你想在你的 README.md 文件中增加些截图,我极力推荐一款叫做Screenr的app。
安装 GPG
如果你的机器上还没安装 GPG,你需要下载安装它。如果你是 MacOSX 系统,安装手册在这里。
如果你从未使用过 GPG – 首先,请创建 GPG 密钥:
- $ gpg
在你创建 GPG 密钥的时候,如果你不确定该如何回答问题, 这篇指南(Creating an encryption key)可以帮上忙。
接下来,找到你的密钥 ID:
- $ gpg
第一行像是 pub XXXXX/YYYYYYYY <日期>的。切记,’YYYYYYYY’ 部分,就是你的密钥 ID。
现在,发布你的密钥:
- $ gpg
-
- $ gpg
你当然也可以使用其他密钥服务器,你也可以通过如下命令确认你的密钥是否已经发布:
- $ gpg
为了使你的开发库在 Gradle, Please网站上列出(也为了其他人方便的引用你的开发库),请上传你的项目到 Maven Central。 最简单的上传项目的方法是使用 Sonatype。
Sonatype
- 在Sonatype 创建一个 JIRA 帐号。
- 登录成功后,创建一个 new issue。
![]()
我为我的 Trestle 项目创建了一个 GitHub 仓库。所以我在 new issue 上填写的字段大概如此:
Group Id : com.github.<github_username>
Project URL : https://github.com/<github_username>/<project_name>
SCM url : https://github.com/<github_username>/<project_name>.git
Username : <sonatype_username>
Already Synced to Central : No
注意:我在你需要填写的字段上增加了括号作为占位符。你需要将它们替换成合适的值。
当你准备提交 issue 的时候,issue 的细节应该要和上面的截图差不多。当你提交完成后,Sonatype 将用 2 个工作日来处理你的 issue。接着,你将收到一份确认邮件,告知你的配置已经准备好了,你可以发布你的开源库了。
不要部署你的开源库,直到你接收到一封表明你的票据已经 OK 了的邮件。 对新项目来说,一个通病就是过早的部署。这将会误使你的构件(artifacts)变成一个人人都能获得的仓库。
最后,如果你的组件已经在中央仓库中了,请在你的票据中添加以下信息,并参考这篇文章,如何迁移到 OSSRH。
修改你本机上的 ~/.gradle/gradle.properties 文件,包含以下内容:
- NEXUS_USERNAME=sonatype_username
-
- NEXUS_PASSWORD=sonatype_password
-
- signing.keyId=gpg_key_id
-
- signing.password=gpg_password
-
- signing.secretKeyRingFile=/Users/username/.gnupg/secring.gpg
-
- org.gradle.daemon=true
当你发布开发库的时候,身份认证信息已经在 gradle.properties 文件中提供了。请确保提供了正确的 nexus 用户名和密码(也就是 Sonatype 的用户名和密码),否则你将得到未授权的 401 错误。
注意:如果你之前已经发布过一个开发库,那么你不需要在 JIRA(Sonatype) 上创建一个新的 issue。每个顶级的 groupId 对应一个 JIRA issue。你应该已经有了部署任何新构件到你的 groupId 或者下属 group 应需要的所有权限。中央仓库的同步操作遵循从上到下的过程,所以任何下属组的发布版本都将会自动同步。当你发布新组件,你不需要告诉 Sonatype,因为当你进行仓库同步工作的时候,没有什么需要 Sonatype 去配置或者检查的, Sonatype 仅仅会在第一次进行同步操作的时候,发布一条 twitter。
发布
一旦你准备发布你的开发库,在 Android Studio 中, 打开右侧的 Gradle 视图,在 Tasks > upload 下,点击 uploadArchives,将会上传你的开发库到 Sonatype Staging Repositories。
![]()
在 Sonatype Staging Repositories 网页上,登陆你的 Sonatype 账号,查找你的 “staging” 开发库,它应该在列表的最后,选中它,并按下 “关闭” 按钮。关闭一个开发库实际上意味着你准备发布它。如果关闭操作一切顺利 – 你应该会看到一个激活了的 ‘发布’ 按钮。你可能需要刷新下页面。请按下发布按钮。请阅读关于使用 Nexus 管理仓库的帮助文档。如果这是你发布的第一个开发库,返回到 JIRA,在 JIRA 上发表一条你已经改进了你的开发库的评论,如果不是第一个,就没必要告诉 Sonatype 你改进了你的开发库。然后,你应该会收到来自 Sonatype 的一条回复信息,信息上说你的开发库在 10 分钟内能准备好,将会在接下来的几个小时同步到 Maven 中央仓库。几个小时之后,它将展示在 Gradle, Please 网站上。
使用你的开发库
对其他开发者来说,想要使用你的开发库,他们需要在 Android 项目的 build.gradle 文件中添加一条依赖, 如下所示:
- apply plugin: 'android'
-
- dependencies {
-
- compile 'com.github.lawloretienne:trestle:0.0.3'
-
- }
特别感谢
非常感谢 Chris Banes,Jonathan Le,Serge Zaitsev 以及其他发表博客的人们,你们的文章帮助我得以走过这个复杂的过程。
我的开发库
QuickReturn — https://github.com/lawloretienne/QuickReturn
Trestle — https://github.com/lawloretienne/Trestle
ImageGallery — https://github.com/lawloretienne/ImageGallery