9.1 KiB
9.1 KiB
Android Release Process
Keystore Generation
Create Release Keystore
# Generate a new keystore
keytool -genkeypair -v -storetype PKCS12 -keystore myapp-release-key.keystore -alias myapp-key-alias -keyalg RSA -keysize 2048 -validity 10000
# Verify keystore
keytool -list -v -keystore myapp-release-key.keystore
Keystore Security
# Store keystore securely
mkdir -p ~/.android/keystores
mv myapp-release-key.keystore ~/.android/keystores/
# Set proper permissions
chmod 600 ~/.android/keystores/myapp-release-key.keystore
Gradle Configuration
Configure Signing
android/gradle.properties:
# Keystore configuration
MYAPP_UPLOAD_STORE_FILE=myapp-release-key.keystore
MYAPP_UPLOAD_KEY_ALIAS=myapp-key-alias
MYAPP_UPLOAD_STORE_PASSWORD=your_keystore_password
MYAPP_UPLOAD_KEY_PASSWORD=your_key_password
# Build optimization
org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
org.gradle.parallel=true
org.gradle.configureondemand=true
org.gradle.daemon=true
# Android configuration
android.useAndroidX=true
android.enableJetifier=true
android.enableR8.fullMode=true
android/app/build.gradle:
android {
compileSdkVersion rootProject.ext.compileSdkVersion
defaultConfig {
applicationId "com.myapp.app"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "1.0.0"
multiDexEnabled true
}
signingConfigs {
debug {
storeFile file('debug.keystore')
storePassword 'android'
keyAlias 'androiddebugkey'
keyPassword 'android'
}
release {
if (project.hasProperty('MYAPP_UPLOAD_STORE_FILE')) {
storeFile file(MYAPP_UPLOAD_STORE_FILE)
storePassword MYAPP_UPLOAD_STORE_PASSWORD
keyAlias MYAPP_UPLOAD_KEY_ALIAS
keyPassword MYAPP_UPLOAD_KEY_PASSWORD
}
}
}
buildTypes {
debug {
signingConfig signingConfigs.debug
applicationIdSuffix ".debug"
debuggable true
minifyEnabled false
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
}
release {
signingConfig signingConfigs.release
minifyEnabled true
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
debuggable false
zipAlignEnabled true
}
}
splits {
abi {
reset()
enable true
universalApk false
include "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
}
}
}
ProGuard Configuration
android/app/proguard-rules.pro:
# React Native
-keep class com.facebook.react.** { *; }
-keep class com.facebook.hermes.** { *; }
-keep class com.facebook.jni.** { *; }
# Keep our application class
-keep class com.myapp.** { *; };
# Keep native methods
-keepclassmembers class * {
native <methods>;
}
# Keep enums
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
# Keep Parcelable implementations
-keep class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}
# Keep serializable classes
-keepnames class * implements java.io.Serializable
-keepclassmembers class * implements java.io.Serializable {
static final long serialVersionUID;
private static final java.io.ObjectStreamField[] serialPersistentFields;
!static !transient <fields>;
private void writeObject(java.io.ObjectOutputStream);
private void readObject(java.io.ObjectInputStream);
java.lang.Object writeReplace();
java.lang.Object readResolve();
}
# Firebase
-keep class com.google.firebase.** { *; }
-keep class com.google.android.gms.** { *; }
# OkHttp
-dontwarn okhttp3.**
-dontwarn okio.**
-dontwarn javax.annotation.**
-keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase
# Retrofit
-dontwarn retrofit2.**
-keep class retrofit2.** { *; }
-keepattributes Signature
-keepattributes Exceptions
# Gson
-keepattributes Signature
-keepattributes *Annotation*
-dontwarn sun.misc.**
-keep class com.google.gson.** { *; }
-keep class * implements com.google.gson.TypeAdapterFactory
-keep class * implements com.google.gson.JsonSerializer
-keep class * implements com.google.gson.JsonDeserializer
Build Process
Build Commands
# Clean build
cd android && ./gradlew clean
# Build debug APK
./gradlew assembleDebug
# Build release APK
./gradlew assembleRelease
# Build release AAB (recommended for Play Store)
./gradlew bundleRelease
# Install debug APK
./gradlew installDebug
# Install release APK
./gradlew installRelease
Build Variants
android {
flavorDimensions "version"
productFlavors {
development {
dimension "version"
applicationIdSuffix ".dev"
versionNameSuffix "-dev"
buildConfigField "String", "API_BASE_URL", '"https://dev-api.myapp.com"'
resValue "string", "app_name", "MyApp Dev"
}
staging {
dimension "version"
applicationIdSuffix ".staging"
versionNameSuffix "-staging"
buildConfigField "String", "API_BASE_URL", '"https://staging-api.myapp.com"'
resValue "string", "app_name", "MyApp Staging"
}
production {
dimension "version"
buildConfigField "String", "API_BASE_URL", '"https://api.myapp.com"'
resValue "string", "app_name", "MyApp"
}
}
}
Google Play Console Setup
Create Application
-
Go to Google Play Console
-
Create Application
- App name: MyApp
- Default language: English
- App or game: App
- Free or paid: Free
-
App Content
- Privacy Policy URL
- App category
- Content rating questionnaire
- Target audience
- Data safety form
Upload Release
# Build release AAB
cd android && ./gradlew bundleRelease
# AAB file location
# android/app/build/outputs/bundle/release/app-release.aab
Release Tracks
-
Internal Testing
- Upload AAB
- Add internal testers
- Release to internal testing
-
Closed Testing
- Create closed testing track
- Add test users via email lists
- Release to closed testing
-
Open Testing
- Create open testing track
- Set country availability
- Release to open testing
-
Production
- Review release
- Set rollout percentage
- Release to production
Version Management
Automated Version Bumping
scripts/bump-android-version.sh:
#!/bin/bash
VERSION_TYPE=${1:-patch} # major, minor, patch
BUILD_GRADLE="android/app/build.gradle"
# Get current version
CURRENT_VERSION=$(grep "versionName" $BUILD_GRADLE | sed 's/.*versionName "\(.*\)"/\1/')
CURRENT_CODE=$(grep "versionCode" $BUILD_GRADLE | sed 's/.*versionCode \(.*\)/\1/')
echo "Current version: $CURRENT_VERSION ($CURRENT_CODE)"
# Calculate new version
IFS='.' read -ra VERSION_PARTS <<< "$CURRENT_VERSION"
MAJOR=${VERSION_PARTS[0]}
MINOR=${VERSION_PARTS[1]}
PATCH=${VERSION_PARTS[2]}
case $VERSION_TYPE in
major)
MAJOR=$((MAJOR + 1))
MINOR=0
PATCH=0
;;
minor)
MINOR=$((MINOR + 1))
PATCH=0
;;
patch)
PATCH=$((PATCH + 1))
;;
esac
NEW_VERSION="$MAJOR.$MINOR.$PATCH"
NEW_CODE=$((CURRENT_CODE + 1))
echo "New version: $NEW_VERSION ($NEW_CODE)"
# Update build.gradle
sed -i "s/versionCode $CURRENT_CODE/versionCode $NEW_CODE/" $BUILD_GRADLE
sed -i "s/versionName \"$CURRENT_VERSION\"/versionName \"$NEW_VERSION\"/" $BUILD_GRADLE
echo "Version updated successfully!"
Git Tagging
# Create and push tag
git tag -a v1.0.0 -m "Release version 1.0.0"
git push origin v1.0.0
# List tags
git tag -l
# Delete tag
git tag -d v1.0.0
git push origin --delete v1.0.0
Release Checklist
Pre-Release Checklist
- Update version number
- Update changelog
- Run all tests
- Test on multiple devices
- Check ProGuard configuration
- Verify signing configuration
- Test release build locally
- Update app store metadata
- Prepare release notes
Post-Release Checklist
- Monitor crash reports
- Check app store reviews
- Monitor performance metrics
- Update documentation
- Create git tag
- Notify team of release
- Plan next release
Troubleshooting
Common Build Issues
# Clean and rebuild
cd android
./gradlew clean
./gradlew assembleRelease
# Clear React Native cache
npx react-native start --reset-cache
# Clear Metro cache
npx react-native start --reset-cache
# Reset Android build
cd android
rm -rf build/
rm -rf app/build/
./gradlew clean
Keystore Issues
# Verify keystore
keytool -list -v -keystore your-keystore.keystore
# Check keystore alias
keytool -list -keystore your-keystore.keystore
# Export certificate
keytool -export -alias your-alias -keystore your-keystore.keystore -file certificate.crt