Bài viết giải thích cách tự động upload Android App Bundle (.aab) lên beta track của Play Store. Sử dụng Android Studio và AWS làm cloud infrastructure.
Sau khi upload app bundle, sẽ trigger Slack notification.
Đây là việc đáng giá vì tạo observability và ưu tiên quy trình.
Công Nghệ Sử Dụng
Tài nguyên cho tutorial:
- Android Studio
- AWS CodeBuild
- AWS Lambda
- S3
- Slack
Tổng Quan Mức Cao

Hình trên cho thấy cấu trúc tổng thể.
Cần Code Pipeline trên AWS cho Android repository. Pipeline có Code Build làm stage.
Push lên master branch trigger Code Build. Code Build project sign Android app từ command line và upload artifact lên S3 bucket.
Upload bundle lên S3 trigger Lambda, download bundle và upload lên Play Store dùng Google Publishing API. Khi nhận response 200, Lambda trigger Slack notification.
Lấy Google Play Service Account Key
Để dùng Google Play Publisher API, cần Google Play Service Account key.
Service account là tài khoản hành động thay mặt bạn khi server giao tiếp. Đọc thêm về OAuth2.0 server-to-server của Google.
Xem cách tạo service account và cấp quyền Google Play Publisher API.
Sau khi tạo service account và cấp quyền, tải service account key và giữ an toàn. Sẽ upload lên S3 bucket.
Ký Android Bundle
Chính là cách ký Android App Bundle. Google có tài liệu tốt.
Tóm tắt:
Tạo private key dùng keytool:
keytool -genkey -v -keystore my-release-key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias my-alias Đặt tên key tùy ý. Ở đây đặt my-release-key.jks. Alias cũng tùy ý. Dùng đúng tên và alias xuyên suốt tutorial.
Mở build.gradle trong thư mục app trong Android Studio, thêm block:
android { ... defaultConfig { ... } signingConfigs { release { // Cần chỉ định absolute path hoặc include keystore file // cùng thư mục với build.gradle file. storeFile file("my-release-key.jks") storePassword "password" keyAlias "my-alias" keyPassword "password" } } buildTypes { release { signingConfig signingConfigs.release ... } } } Nếu đổi tên release key, chỉ định tên mới. Tương tự alias.
Store password là password khi upload app lên Play Store lần đầu.
Chạy ./gradlew :app:bundleRelease từ command line Android Studio, sẽ generate signed App Bundle.
Loại Bỏ Thông Tin Ký
Commit code với signing info plain text trong build.gradle là rủi ro bảo mật.
Google có tài liệu về này.
Tạo file keystore.properties ở root project.
Nội dung:
storePassword=myStorePassword keyPassword=myKeyPassword keyAlias=myKeyAlias storeFile=myStoreFileLocation Store password và key password là password khi upload app bundle lần đầu.
keyAlias và storeFile là alias khi tạo private key và vị trí private key.
Load file vào build.gradle. Gradle hoạt động như DSL, dễ viết config.
// Load properties from keystore.properties def keystorePropertiesFile = rootProject.file("keystore.properties") // Creating a new Properties() object def keystoreProperties = new Properties() // If keystorePropertiesFile exists, read from that, else set from build environment if (keystorePropertiesFile.exists()) { // Loading the keystoreProperties file keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) } else { // Read all environment variables from the build environment keystoreProperties.setProperty("storeFile", "${System.getenv('STORE_FILE')}") keystoreProperties.setProperty("keyAlias", "${System.getenv('KEY_ALIAS')}") keystoreProperties.setProperty("keyPassword", "${System.getenv('KEY_PASSWORD')}") keystoreProperties.setProperty("storePassword", "${System.getenv('STORE_PASSWORD')}") } Điều kiện if dành cho Code Build sau này.
Đổi signingConfigs trong build.gradle:
signingConfigs { release { storeFile file(keystoreProperties['storeFile']) keyAlias keystoreProperties['keyAlias'] keyPassword keystoreProperties['keyPassword'] storePassword keystoreProperties['storePassword'] } } Setup AWS Code Pipeline
Không đi sâu vì đơn giản.
Setup AWS Code Pipeline 3 stage:
- Source stage kết nối GitHub repository branch
master - Build stage kết nối AWS Code Build
- Deploy stage deploy lên S3 bucket.
Tài liệu Code Pipeline.
Setup AWS S3
Đảm bảo Code Pipeline có Code Build stage. Tạo 2 S3 buckets:
- Bucket lưu release key. Gọi
release-key.jks - Bucket lưu Google Play Service Account private key. (Đã tải khi tạo service account.)
Cần cho phép Code Build service role truy cập buckets. Service role tạo khi setup Code Pipeline.
Vào IAM console, tìm Code Build service role, lấy ARN.
Dùng console vào Permissions tab cho bucket release-key.jks, thêm policy:
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "AWS": [ "arn:aws:iam::123456789:role/service-role/codebuild-service-role-dummy", ] }, "Action": "s3:GetObject", "Resource": "arn:aws:s3:::release-key-bucket/*" } ] } Policy cho phép truy cập S3 bucket từ máy chạy CodeBuild.
Thay ARN bằng ARN tài khoản bạn. Chỉ định đúng ARN Code Build service role khi update policy.
Bucket thứ 2 không cần đổi policy. Sẽ thêm permission cho Lambda role truy cập bucket.
Setup AWS CodeBuild
Tạo file buildspec.yml ở root project.
version: 0.2 phases: build: commands: - aws s3api get-object --bucket release-key.jks --key release-key.jks ./releaseKey.jks - cp ./releaseKey.jks ${CODEBUILD_SRC_DIR}/app/releaseKey.jks - export STORE_FILE=releaseKey.jks - export KEY_ALIAS=$keyAlias - export KEY_PASSWORD=$keyPassword - export STORE_PASSWORD=$storePassword - ./gradlew :app:bundleRelease artifacts: files: - app/build/outputs/bundle/release/app-release.aab File đơn giản: fetch release key từ bucket, lưu local trên Code Build server.
Export variables cho build.gradle. Cuối chạy Gradle release command.
Trước khi chạy script trong Code Build, thêm variables vào Code Build environment. Vào AWS Code Build console, chọn build project Android app.
Chọn Edit → Environment:

Chọn Additional Configuration dropdown. Thấy option thêm environment variables key-value pairs.
Khi Code Build chạy buildspec.yml, sẽ export variables.
Hiện tại, pipeline chạy, Code Build download private key, sign và build Android app, upload signed bundle lên S3.
Setup Slack App
Observability là dấu hiệu của automation. Muốn biết automation chạy khi nào, success/fail, fail thì lý do gì.
AWS thường dùng CloudWatch. Nhưng Slack integration cũng tốt.
Cách dễ nhất tích hợp Slack: setup Slack app, gửi notification từ automation workflow.
Xem docs setup Slack app. Quá trình dễ, vài phút xong.
Sau khi tạo app, nhận WebHook URL để gọi app post vào channel. Giữ URL này để dùng với AWS Lambda.
Setup AWS Lambda
Đã có Android App Bundle signed, built, upload S3. Tiếp upload bundle lên beta track Play Store.
Cách: setup AWS Lambda trigger khi bundle upload lên S3. Lambda chạy, download bundle, lấy service account key, upload bundle lên Play Store beta track.
Sau khi tạo Lambda và thêm trigger chạy khi file upload lên bucket, xem code:
"""This Python3 script is used to upload a new .aab bundle to the play store. The execution of this Python script occurs through an AWS Lambda which is invoked when a new file is uploaded to the relevant S3 buckets""" import json import boto3 import os from urllib import request, parse from google.oauth2 import service_account import googleapiclient.discovery # Defining the scope of the authorization request SCOPES = ['https://www.googleapis.com/auth/androidpublisher'] # Package name for app package_name = 'com.app.name' # Define the slack webhook url slack_webhook_url = os.environ['SLACK_WEBHOOK_URL'] def send_slack_message(message): data = json.dumps({ 'text': message }) post_data = data.encode('utf-8') req = request.Request(slack_webhook_url, data=post_data, headers={ 'Content-Type': 'application/json' }) request.urlopen(req) # This is the main handler function def lambda_handler(event, context): # Create a new client S3 client and download the correct file from the bucket s3 = boto3.client('s3') s3.download_file('service-account-bucket-key', 'service-account-bucket-key.json', '/tmp/service-account-key.json') SERVICE_ACCOUNT_FILE = '/tmp/service-account-key.json' # Download the app-release.aab file that triggered the Lambda bucket_name = event['Records'][0]['s3']['bucket']['name'] file_key = event['Records'][0]['s3']['object']['key'] s3.download_file(bucket_name, file_key, '/tmp/app-release.aab') APP_BUNDLE = '/tmp/app-release.aab' print(f"A bundle uploaded to {bucket_name} has triggered the Lambda") # Create a credentials object and create a service object using the credentials object credentials = service_account.Credentials.from_service_account_file( SERVICE_ACCOUNT_FILE, scopes=SCOPES ) service = googleapiclient.discovery.build('androidpublisher', 'v3', credentials=credentials, cache_discovery=False) # Create an edit request using the service object and get the editId edit_request = service.edits().insert(body={}, packageName=package_name) result = edit_request.execute() edit_id = result['id'] # Create a request to upload the app bundle try: bundle_response = service.edits().bundles().upload( editId=edit_id, packageName=package_name, media_body=APP_BUNDLE, media_mime_type="application/octet-stream" ).execute() except Exception as err: message = f"There was an error while uploading a new version of {package_name}" send_slack_message(message) raise err print(f"Version code {bundle_response['versionCode']} has been uploaded") # Create a track request to upload the bundle to the beta track track_response = service.edits().tracks().update( editId=edit_id, track='beta', packageName=package_name, body={u'releases': [{ u'versionCodes': [str(bundle_response['versionCode'])], u'status': u'completed', }]} ).execute() print("The bundle has been committed to the beta track") # Create a commit request to commit the edit to BETA track commit_request = service.edits().commit( editId=edit_id, packageName=package_name ).execute() print(f"Edit {commit_request['id']} has been committed") message = f"Version code {bundle_response['versionCode']} has been uploaded from the bucket {bucket_name}.\nEdit {commit_request['id']} has been committed" send_slack_message(message) return { 'statusCode': 200, 'body': json.dumps('Successfully executed the app bundle release to beta') } Lambda dùng thư viện googleapiclient và discovery module build URL cho Google Play Publishing API.
Lambda download service account key từ bucket setup trước. Cần chỉ định đúng bucket names.
Tùy upload thành công/thất bại, gửi Slack message. Thêm Slack WebHook URL vào environment variables Lambda. Function dùng module os Python truy cập env variable và post message Slack.
Nếu Lambda fail, có thể do Lambda không có permission truy cập S3 bucket chứa key Google Play service account. Sẽ thấy error message.
Fix: thêm permission cho Lambda role.
Policy cần thêm:
{ "Version": "2012-10-07", "Statement": [ { "Effect": "Allow", "Action": [ "s3:GetObjectVersion", "s3:GetBucketVersioning", "s3:GetBucketAcl", "s3:GetObject", "s3:GetBucketTagging", "s3:GetBucketLocation", "s3:GetObjectVersionAcl" ], "Resource": [ "arn:aws:s3:::arn:aws:s3:::your-bucket-name-with-service-account-key" ] } ] } Thay ARN bucket bằng ARN tài khoản bạn.
Kết Luận
Đã xong. Không dễ dàng, nhiều moving parts, nhưng automation này tiết kiệm thời gian và công sức lớn.
Nếu team thường xuyên release update, không muốn bị cản trở bởi vắng mặt người phụ trách release.
Xây automation này làm CI/CD workflow mượt và robust hơn.
Nếu thích blog này, đọc thêm tại https://redixhumayun.github.io hoặc follow Twitter.