Building your own Android Library - meswapnilwagh/android_guides GitHub Wiki
Building your own Android library enables other developers to take advantage of code that you've written. You can share existing activities, services, images, drawables, resource strings, and layout files that enable other people to leverage your work such as those documented in the must have libraries guide. Also, if your code base begins to take longer times to compile and/or run, creating a library also enables you to iterate faster by working on a more smaller component.
If you plan to share only standard Java code, you can distribute them packaged as Java Archive Resources (.jar
) files. However, if you intend to include resources such as layouts, drawables, or string resources, or even an additional AndroidManifest.xml
file, you must create an Android Archive Resource .aar
file file instead. An .aar
file can include the following types of files:
- /AndroidManifest.xml (mandatory)
- /classes.jar (mandatory)
- /res/ (mandatory)
- /R.txt (mandatory)
- /assets/ (optional)
- /libs/*.jar (optional)
- /jni//*.so (optional)
- /proguard.txt (optional)
- /lint.jar (optional)
When you create a new Android project, a new application is always created. You can use this application to test your library. After creating the project, go to New
-> New Module
:
Select Android Library
. There is the option to choose Java library
, but there is a major difference in that an Android library will include not only the Java classes but the resource files, image files, and Android manifest file normally associated with Android.
You will prompted next to provide a name and the module name. The name will simply be used to label the application in the Android Manifest file, while the module name will correspond to the directory to be created:
When you click Next, a directory with the module name will be generated along with other files including a resource and Java folder:
In addition, a build.gradle
file will be created. One major difference is that Android applications use the com.android.application
plugin. Android libraries will use the com.android.library
plugin. This statement at the top signals to the Android Gradle plug-in to generate an .aar
file instead of an .apk
file normally installed on Android devices.
// Android library
apply plugin: 'com.android.library'
Android applications usually have a build and debug variation. The buildTypes
parameter designates the settings for each type of configuration.
android {
buildTypes {
release {
}
debug {
}
}
You can compile the library with Android Studio, or type ./gradlew build
at the command line. The output will be stored under the library's subdirectory under build/outputs/aar
. Unlike Android applications in which debug
or release
versions can be generated, only release versions by default are published as documented here.
If you wish to build multiple variations, you will need to add this statement to your library build.gradle
file:
android {
publishNonDefault true
}
When using this statement, different .aar
packages are generated for each build type specified. To reference them once they are published, see this section.
If you wish to reference the library from your demo application within the same Android project, you will need to explicitly specify which library to use with the configuration
parameter. You need to add this statement to your app/build.gradle
:
dependencies {
// colon needs to prefixed with the library path
debugCompile project(path: ':mylibrary', configuration: 'debug')
releaseCompile project(path: ':mylibrary', configuration: 'release')
}
If you intend use the library with ButterKnife, you will find that it does not work with Android libraries. There are specific reasons for not supporting it as stated here. For any existing code, you will have to convert your @InjectView
annotations back to findViewById()
calls.
To publish your library, you can either make it available to a public or private repository. jCenter and Maven Central are the most popular ones, though jCenter has become the default one used in Android Studio. For understanding the differences between jCenter or Maven Central, see this blog link.
First, signup for a BinTray account. You will want to create a GPG signing key: and go to Edit Profile to add this private/public key pair.
gpg --gen-key
Find the public key ID generated by finding the 8-digit hex after "pub 2048/XXXXXXXX":
gpg --list-keys
gpg --keyserver hkp://pool.sks-keyservers.net --send-keys [PUBLIC_KEY_ID]
Export your keys. You will want to copy/paste these sections into the GPG Signing
section:
gpg -a --export [email protected] > public_key_sender.asc
gpg -a --export-secret-key [email protected] > private_key_sender.asc
Click on the API Key
section when editing your profile. You will need to provide your username and API Key by setting it locally in your gradle.properties
file:
bintrayUser=user
bintrayApiKey=key
Take a look at the examples provided by BinTray here. In particular, you should follow the android-maven-example
.
Next, edit your root build.gradle
file. Add the android-maven-gradle-plugin
, which will be used to generate the Maven-compatible archive to be shared, as well as the JFrog plugin:
buildscript {
repositories {
jcenter()
}
dependencies {
// used to generate a POM file
classpath 'com.github.dcendents:android-maven-gradle-plugin:1.3'
}
}
// Plugin used to upload authenticated files to BinTray through Gradle
plugins {
id "com.jfrog.bintray" version "1.5"
}
Inside your library/build.gradle
file, you will want to apply the Android Maven Gradle and JFrog plugin:
apply plugin: 'com.android.library'
apply plugin: 'com.github.dcendents.android-maven'
apply plugin: 'com.jfrog.bintray'
Next, you will need to define constants that will be used to generate the XML files used by Maven to understand information about the package. Gradle compile statements are usually follow the form of GROUP_ID:ARTIFACT_ID:VERSION
, such as 'com.squareup.picasso:picasso:2.5.2', so we should always to make sure these values are set.
// If your directory matches the name, you do not need to set archivesBaseName.
archivesBaseName = "android-oauth-handler"
install {
repositories.mavenInstaller {
pom.project {
group "com.codepath.libraries"
artifactId "android-oauth-handler"
version "1.0.0"
}
}
}
The remaining section should be added for authenticating uploads to BinTray. Note that the configurations
option alerts the plugin to upload the final packages generated.
bintray {
user = project.hasProperty('bintrayUser') ? project.property('bintrayUser') : System.getenv('BINTRAY_USER')
key = project.hasProperty('bintrayApiKey') ? project.property('bintrayApiKey') : System.getenv('BINTRAY_API_KEY')
// jFrog plugin must be declared for this line to work
configurations = ['archives']
// Package info for BinTray
pkg {
repo = 'maven'
name = 'android-oauth-handler'
userOrg = user
licenses = ['Apache-2.0']
vcsUrl = 'https://github.com/bintray/gradle-bintray-plugin.git'
version {
name = '0.1'
desc = 'Gradle Bintray Plugin 1.0 final'
vcsTag = '0.1'
}
}
}
Another approach is to setup a private Maven repository, which also be done through Amazon's Web Services (AWS) and the Simple Storage Service (S3). Gradle supports the ability to access private S3 repositories with a secret access key and ID used to authenticate with Amazon:
To add the S3 repository to the list, you will need to add the credentials to access the S3 bucket to your root build.gradle
file:
allprojects {
repositories {
jcenter()
maven {
url "s3://yourmavenrepo-bucket/android/snapshots"
credentials(AwsCredentials) {
accessKey AWS_ACCESS_KEY
secretKey AWS_SECRET_KEY
}
}
}
Instead of adding the keys directly, it is recommended that you add it to your local.properties
to your local machine:
AWS_ACCESS_KEY=<my_aws_access_key>
AWS_SECRET_KEY=<my_super_secret_key>
In order to publish the plugin, we need to create a separate Gradle file that can be use in our library configuration. Create a file called gradle/gradle-mvn-push.gradle
, which will apply the Gradle-Maven plugin and specify the location of the S3 bucket when using the ./gradlew uploadArchives
command:
apply plugin: 'maven'
def isReleaseBuild() {
return VERSION_NAME.contains("SNAPSHOT") == false
}
def getOutputDir() {
if (isReleaseBuild()) {
return "${project.buildDir}/releases"
} else {
return "${project.buildDir}/snapshots"
}
}
def getDestUrl() {
if (isReleaseBuild()) {
return "s3://yourmavenrepo-bucket/android/releases"
} else {
return "s3://yourmavenrepo-bucket/android/snapshots"
}
}
uploadArchives {
repositories {
mavenDeployer {
// repository(url: "file:///" + getOutputDir()) can also be used to copy to local file
repository(url: getDestUrl()) // for copying directly to S3
pom.groupId = GROUP
pom.artifactId = POM_ARTIFACT_ID
pom.version = VERSION_NAME
pom.project {
name POM_NAME
packaging POM_PACKAGING
description POM_DESCRIPTION
}
}
}
}
We will then apply the statements in this file by applying it within the library's Gradle file (i.e. mylibrary/build.gradle
):
apply from: rootProject.file('gradle/gradle-mvn-push.gradle')
Edit the root project's gradle.properties
too:
VERSION_NAME=0.4-SNAPSHOT
VERSION_CODE=0.4
GROUP=com.codepath
Finally, we need to setup the metadata necessary for publishing. Edit the library's gradle.properties
and set the values:
POM_NAME=My Library
POM_ARTIFACT_ID=library
POM_PACKAGING=aar
Currently Gradle's Amazon S3 integration only supports access keys and does not support Identity Access Management (IAM) roles. To take advantage of a client that does, you can output the repository to a local file and use the AWS command-line client to copy the snapshot dirs:
uploadArchives {
repositories {
mavenDeployer {
repository(url: "file:///" + getOutputDir())
}
}
}
task copyToS3(type: Exec) {
commandLine 'aws', 's3', 'cp', '--recursive', getOutputDir(), getDestUrl()
}
copyToS3.dependsOn uploadArchives
To publish and execute the task to copy the build to S3, the command to enter is ./gradlew copyToS3
.
Once the private S3 repository has been added to the list, you can simply add this line to the Gradle dependency list. The Android Gradle plug-in will search through all possible repositories searching for a match. Add this line to your app/build.gradle
file:
dependencies {
compile 'com.codepath:mylibrary:0.4-SNAPSHOT'
}
If you published multiple versions of your package as described here, you will need to specify the build type (i.e. release
or debug
). Gradle may assume that if you specify the build type to search for a .jar
file, so you must also specify the @aar
. Using this syntax doesn't follow the dependencies included in the package, so transitive=true
must also be included as described in this Google discussion.
dependencies {
releaseCompile('com.codepath:mylibrary:0.4:release@aar') {
transitive=true
}
debugCompile('com.codepath:mylibrary:0.4-SNAPSHOT:debug@aar') {
transitive=true
}
}
If you are making constant changes to your snapshot versions and wish to have the latest updates pulled each time, you can mark the dependency as a changing module in Gradle:
dependencies {
debugCompile('com.codepath:mylibrary:0.4-SNAPSHOT:debug@aar') {
transitive=true
changing=true
}
}
Gradle will normally cache the module for 24 hours for those marked as changing, but you can lower this setting:
configurations.all {
// check for updates every build
resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
}
If you are trying to access a private Amazon S3 repository, you may see an AWS authentication requires a valid Date or x-amz-date header
error. It is a known issue with Gradle and Java versions.
To fix this issue, you will need to upgrade to Gradle v2.8 by editing your gradle/wrapper.properties
:
distributionUrl=https\://services.gradle.org/distributions/gradle-2.8-all.zip
Even though the default Gradle version used in Android projects is 2.4, the build should compile without needing to make any changes.
If you intend to export your release, you should also include any configurations in case ProGuard is applied to your library. If you specify consumerProguardFiles
in your library config, the ProGuard rules will be added during the compilation.
android {
defaultConfig {
minifyEnabled true
consumerProguardFiles 'consumer-proguard-rules.pro'
}
}
Make sure to create a consumer-proguard-rules.pro
file. See Configuring ProGuard for more details.
If you use the default configuration, ProGuard will obfuscate and alter the name of your library classes, making it impossible for Android projects to reference them. The most basic example of ensuring your library classes are exported is shown below:
-dontobfuscate
# See https://speakerdeck.com/chalup/proguard
-optimizations !code/allocation/variable
-keep public class * {
public protected *;
}
See the ProGuard documentation for more information about the syntax of this file. See this example of an Android library definition.
If your Android library defines an AndroidManifest.xml
or any other resource files (i.e. strings.xml
, colors.xml
), these resource will be automatically merged with your application. In this way, you do not have to redeclare permissions that are needed in your library in your main application. However, if your library declares color styles that may conflict with the appearance in your main application, you may need to rename these styles.
If you do wish to understand how the final AndroidManifest.xml
is generated, you can decode the final .apk
file using a third-party tool called apktool. Instructions to install are located here. If you are upgrading the apktool
version, you may need to delete the $HOME/apktool/framework/1.apk
file.
Once you have the tool installed, you simply need to type this line:
apktool decode <.apk file>
The tool should decode your .apk
file and allow you to better understand how the final resource files are generated.