Image from pexels
To release an Android app to the public, every Android Developer needs to build and sign their Android build securely to publish it on Play Store or any other store to download. No store allows unsigned builds to be published.
Signing Android build gives developers ownership of the Android app so that no other developer can release the same app with a different sign.
Even if your project is open source and you’re not planning to publish on any store and distribute through the Github Release page or any other tool, you’ll still need to build and sign your Android build. There are several reasons why a release build is necessary:
- Performance: A release build is optimised for performance, with code and resources stripped of unnecessary debug information and libraries.
- Security: Signing the app helps ensure its integrity and authenticity, preventing security issues and harm to users.
- Updates: Signing enables efficient and secure updates to the app, maintaining user trust.
You can easily use the Android Studio’s built-in Generate Signed Bundle/APK function. Still, it is cumbersome to do manually for every release and it is also not secure to share your keystore file and password with other peers to create a release build. As the quote says
Anything that you do more than twice has to be automated.
In this blog, we will explore how you can use GitHub Actions to automate the process of signing Android build and creating release on GitHub. We will cover the basics of GitHub Actions, the steps involved in building and signing your Android app, and best practices to ensure your app is secure and ready for release. So let’s dive in and learn how to build and sign your Android app securely using GitHub Actions.
Setting up Gradle
Before we can start with the actual implementation, we need to set up signingConfigs in our app-level build.gradle
to use environment variables from our GitHub secrets, which we will later set up. The following code will use our decoded keystore file and use environment variables to sign the app.
android { ... signingConfigs { release { storeFile file("keystore.jks") storePassword System.getenv("SIGNING_STORE_PASSWORD") keyAlias System.getenv("SIGNING_KEY_ALIAS") keyPassword System.getenv("SIGNING_KEY_PASSWORD") } } ... }
Encoding KeyStore
To sign your android build, you need keystore(.jks)
a file. If you don’t already have your keystore file then you can follow the process from the official doc here.
Do not add your keystore file in your project and push along with your project file.
For encoding, we will use the popular Base64 encoding scheme. This scheme will allow converting binary data into a text representation
Text representation will allow us to store the file as text in our GitHub Secrets and later on in the GitHub workflow process decode it back to our original KeyStore file.
To encode the file use these commands based on ur machine.
Mac: base64 --i [Jks FilePath] --i [EncodeFilePath].txt Alpine & Ubuntu: base64 [Jks FilePath] > [EncodeFilePath].txt
e.g. `base64 — input ~/Document/Project/keystore.jks — output encoded_keystore.b64
You should see a new file named encoded_keystore.txt
GitHub Actions Workflow
GitHub Actions is Github’s tool to automate the software CI/CD workflows.
GitHub Secrets
GitHub Secrets is the feature that allows users to securely store and manage sensitive information that their workflows need to access, such as API keys, tokens, or passwords.
Using GitHub Secrets, you can avoid exposing sensitive information in your code or workflows. Secrets can be used to store encrypted values and only decrypted during runtime, and they are never shown in the logs or exposed to users.
We used in environment variables in out build.gradle
in signingConfigs. To access those variables, we need to create GitHub Secrets with the same name.
SIGNING_KEY_STORE_BASE64
To store base64 encoded text we created in previous step. Copy the content from the file and paste it as secret in the GitHub console
SIGNING_STORE_PASSWORD
To store the password which you used while creating the keystore (.jks) file
SIGNING_KEY_ALIAS
To store the key alias
SIGNING_KEY_PASSWORD
To store the key password you used.
SIGNING_KEY_STORE_PATH
In build.gradle you must have noticed storeFile
path or file name. This is secret is to store the path and name of the decoded keystore (.jks) file for easily accessible by the workflow for decoding.
Set the value to keytore.jks
Workflow
on: push: branches: master tags: - v* release_build: runs-on: ubuntu-latest if: startsWith(github.ref, 'refs/tags/') steps: - uses: actions/checkout@v2.6.0 - name: Setup JAVA 11 uses: actions/setup-java@v3 with: distribution: 'corretto' java-version: 11 - name: Cache Gradle and wrapper uses: actions/cache@v2 with: path: | ~/.gradle/caches ~/.gradle/wrapper key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }} #1 - name: Decode Keystore env: ENCODED_STRING: ${{ secrets.SIGNING_KEY_STORE_BASE64 }} SIGNING_KEY_STORE_PATH: ${{ secrets.SIGNING_KEY_STORE_PATH }} run: | echo $ENCODED_STRING > keystore-b64.txt base64 -d keystore-b64.txt > $SIGNING_KEY_STORE_PATH #2 - name: Build Release apk env: SIGNING_KEY_STORE_PATH: ${{ secrets.SIGNING_KEY_STORE_PATH }} SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS }} SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }} SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD }} run: ./gradlew assembleRelease - name: Build Release bundle env: SIGNING_KEY_STORE_PATH: ${{ secrets.SIGNING_KEY_STORE_PATH }} SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS }} SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }} SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD }} run: ./gradlew bundleRelease #3 - name: Upload Release Build to Artifacts uses: actions/upload-artifact@v3 with: name: release-artifacts paths: | app/build/outputs/apk/release/ app/build/outputs/bundle/release/ #4 - name: Create Github Release uses: softprops/action-gh-release@v1 with: generate_release_notes: true prerelease: true files: | app/build/outputs/apk/release/app-release.apk app/build/outputs/bundle/release/app-release.aab
Job Offers
This is the job to build a release apk and bundle, upload artifacts and finally create a release.
This is the job release_build
. This will run on ubuntu-latest
and this condition startsWith(github.ref, 'refs/tags/')
will make sure that this job will only be triggered when the tag value be pushed in the master branch.
I will directly jump to the Decode keystore step but if you’re unfamiliar with steps before this then you should check my previous article, I have explained this in that.
Streamline Your Android Builds using GitHub Actions
#1 Decode Keystore
First, we create the env variable to be used in the command. run:
will run 2 commands.
- First to store the encode keystore text data to
keystore-b64.txt
- Second to decode the encoded text to its original value.
#2 Build Release APK and Bundle
./gradlew assembleRelease
and ./gradlew bundleRelease
will build the release APK and bundle respectively in your project build folder which will access in the next step to upload.
#3 Upload Release Build to Artifacts
In this step, we will use the upload-artifact GitHub Action to upload our release builds. This action will take the multiple paths of artifacts which you want to upload.
By default, gradle generate release signed APK in this path app/build/outputs/apk/release/
and bundle in this path app/build/outputs/bundle/release/
So we have used both of these paths.
#4 Create Github Release
In this step, we will use action-gh-release to create a release for your project. By default release name will be the tag that is pushed to the main branch. This action accepts take inputs to make the GitHub release more customisable and informative.
generate_release_notes
It will create the release notes for free since your last release
prerelease
Indicator of whether or not
files
Newline-delimited globs of paths to assets to upload for release
You can find more inputs in the GitHub Repository. Below is the release it will generate something like that for you.
Workflow in Action
Push the tag in the main branch then go to the Actions tab in your project and see the release Workflow in Action. This shows all the steps taken to complete this job.
Conclusion
In this article, we have discussed how we can automate and simplify the process of building the release for the android project without compromising with our keystore file by uploading it to the project.
We used GitHub secrets to save encoded keystore file and later decode it to temporary file to use for building the release.
If you run a public repository, this strategy is extremely helpful. The trade-off is that throughout the workflow process, the KeyStore file is temporarily decoded to the original file.
This solution can be applied to any other CI pipeline that supports for the store the secrets, not just GitHub Workflows.
By setting up this workflow, you can automate the build procedure, sign the release build, and distribute it to your users. This can help you save time and effort and streamline and improve the release process. With the expertise of GitHub Actions at your fingertips, you can focus on building your app and offering customers with new features while GitHub handles the rest.
If you have come this far, I hope you have learned something new today.
Feel free to connect with me on Twitter
If you’re interested in Kotlin Multiplatform, then you should check out my previous article. How you can start with KMM
This article was previously published on proandroiddev.com