Skip to content

Commit 06fa0ab

Browse files
authored
Fix Google Play signed APK download with smart checking (#101)
- Fixed HTTP 204 error by adding alt=media parameter to download requests - Implemented smart APK availability checking to reduce wait times - Fixed APK selection logic to properly find universal APKs - Integrated download job with Play Store upload pipeline - Removed unnecessary split APK fallback logic
1 parent 74e4147 commit 06fa0ab

File tree

1 file changed

+161
-69
lines changed

1 file changed

+161
-69
lines changed

.github/workflows/release.yml

Lines changed: 161 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -386,8 +386,8 @@ jobs:
386386
download-signed-apk:
387387
name: Download Google Play Signed APK
388388
needs: [prepare, play-store-upload]
389+
if: ${{ vars.ENABLE_PLAY_STORE_UPLOAD == 'true' && vars.ENABLE_SIGNING == 'true' }}
389390
runs-on: ubuntu-latest
390-
if: success()
391391

392392
steps:
393393
- name: Checkout code
@@ -402,17 +402,106 @@ jobs:
402402
run: |
403403
pip install google-api-python-client google-auth-httplib2 google-auth-oauthlib requests
404404
405-
- name: Wait for Google Play processing
405+
- name: Check and wait for Google Play processing
406+
env:
407+
PLAY_STORE_SERVICE_ACCOUNT_JSON: ${{ secrets.PLAY_STORE_SERVICE_ACCOUNT_JSON }}
406408
run: |
407-
echo "Waiting for Google Play to process and sign the APK..."
408-
sleep 120 # Wait 2 minutes for Google Play to process
409+
VERSION_CODE="${{ needs.prepare.outputs.version_code }}"
410+
PACKAGE_NAME="me.ghui.v2er"
409411
410-
- name: Download AAB artifact
411-
uses: actions/download-artifact@v4
412-
with:
413-
name: release-bundle
414-
path: bundle-artifacts/
415-
continue-on-error: true
412+
# Create Python script to check if APK exists
413+
cat > check_apk_exists.py << 'EOF'
414+
import json
415+
import os
416+
import sys
417+
import time
418+
from google.oauth2 import service_account
419+
from googleapiclient.discovery import build
420+
421+
def check_apk_exists():
422+
try:
423+
# Load service account credentials
424+
service_account_info = json.loads(os.environ['PLAY_STORE_SERVICE_ACCOUNT_JSON'])
425+
credentials = service_account.Credentials.from_service_account_info(
426+
service_account_info,
427+
scopes=['https://www.googleapis.com/auth/androidpublisher']
428+
)
429+
430+
# Build the service
431+
service = build('androidpublisher', 'v3', credentials=credentials)
432+
433+
package_name = os.environ['PACKAGE_NAME']
434+
version_code = int(os.environ['VERSION_CODE'])
435+
436+
print(f"Checking if signed APK exists for {package_name} version {version_code}")
437+
438+
# Try to get the generated APKs list
439+
result = service.generatedapks().list(
440+
packageName=package_name,
441+
versionCode=version_code
442+
).execute()
443+
444+
if 'generatedApks' not in result or not result['generatedApks']:
445+
print(f"No generated APKs found for version {version_code}")
446+
return False
447+
448+
print(f"Found {len(result['generatedApks'])} generated APK groups")
449+
450+
# Check if we can find a universal APK
451+
for apk in result['generatedApks']:
452+
if 'generatedUniversalApk' in apk:
453+
universal_apk = apk['generatedUniversalApk']
454+
download_id = universal_apk.get('downloadId')
455+
if download_id:
456+
print(f"✅ Universal APK found with downloadId: {download_id}")
457+
return True
458+
459+
print("❌ No universal APK found")
460+
return False
461+
462+
except Exception as e:
463+
print(f"Error checking APK: {str(e)}")
464+
return False
465+
466+
if __name__ == "__main__":
467+
exists = check_apk_exists()
468+
sys.exit(0 if exists else 1)
469+
EOF
470+
471+
# Set environment variables for the script
472+
export PACKAGE_NAME="$PACKAGE_NAME"
473+
export VERSION_CODE="$VERSION_CODE"
474+
475+
# Check if APK already exists
476+
echo "Checking if Google Play signed APK is ready..."
477+
if python3 check_apk_exists.py; then
478+
echo "✅ APK is already available, skipping wait"
479+
else
480+
echo "⏳ APK not ready yet, waiting for Google Play to process..."
481+
482+
# Smart waiting with periodic checks
483+
MAX_WAIT=600 # Maximum 10 minutes
484+
CHECK_INTERVAL=30 # Check every 30 seconds
485+
elapsed=0
486+
487+
while [ $elapsed -lt $MAX_WAIT ]; do
488+
sleep $CHECK_INTERVAL
489+
elapsed=$((elapsed + CHECK_INTERVAL))
490+
491+
echo "⏱️ Waited ${elapsed}s, checking again..."
492+
if python3 check_apk_exists.py; then
493+
echo "✅ APK is now available after ${elapsed}s"
494+
break
495+
fi
496+
497+
if [ $elapsed -ge $MAX_WAIT ]; then
498+
echo "⚠️ Maximum wait time (${MAX_WAIT}s) reached"
499+
echo "APK may still be processing, will attempt download anyway"
500+
fi
501+
done
502+
fi
503+
504+
# AAB artifact not needed for Google Play signed APK download
416505

417506
- name: Download Google Play Signed APK
418507
id: download-apk
@@ -449,8 +538,8 @@ jobs:
449538
450539
print(f"Attempting to download signed APK for {package_name} version {version_code}")
451540
452-
# Get the signed universal APK download URL
453-
# Note: This requires the app to be released and processed by Google Play
541+
# Step 1: Get the generated APKs list to find downloadId
542+
print("Getting generated APKs list...")
454543
result = service.generatedapks().list(
455544
packageName=package_name,
456545
versionCode=version_code
@@ -460,32 +549,68 @@ jobs:
460549
print("No generated APKs found. App may not be processed yet by Google Play.")
461550
return False
462551
463-
# Find universal APK
552+
print(f"Found {len(result['generatedApks'])} generated APKs")
553+
554+
# Debug: Print all APK structures
555+
for i, apk in enumerate(result['generatedApks']):
556+
print(f"APK {i} structure:")
557+
for key, value in apk.items():
558+
print(f" {key}: {value}")
559+
print()
560+
561+
# Find universal APK using the correct API structure
562+
download_id = None
464563
universal_apk = None
564+
565+
# First, try to find a universal APK in generatedUniversalApk
465566
for apk in result['generatedApks']:
466-
if apk.get('targetingInfo', {}).get('abiTargeting') is None:
467-
# This should be the universal APK
468-
universal_apk = apk
567+
if 'generatedUniversalApk' in apk:
568+
universal_apk = apk['generatedUniversalApk']
569+
download_id = universal_apk.get('downloadId')
570+
print(f"Found universal APK: {universal_apk}")
469571
break
470572
471-
if not universal_apk:
472-
print("Universal APK not found in generated APKs")
573+
if not download_id:
574+
print("No universal APK found")
575+
print("Available APK structure:")
576+
print(json.dumps(result['generatedApks'], indent=2))
473577
return False
474578
475-
# Download the APK
476-
download_url = universal_apk.get('downloadUrl')
477-
if not download_url:
478-
print("Download URL not available for universal APK")
479-
return False
579+
print(f"Found universal APK with downloadId: {download_id}")
480580
481-
print(f"Downloading APK from: {download_url}")
482-
response = requests.get(download_url, stream=True)
483-
response.raise_for_status()
581+
# Step 2: Download the APK using the downloadId
582+
print("Downloading APK binary...")
583+
584+
# Use alt=media to get the actual binary content instead of metadata
585+
download_request = service.generatedapks().download(
586+
packageName=package_name,
587+
versionCode=version_code,
588+
downloadId=download_id
589+
)
590+
# Add alt=media parameter correctly (URL already has query params, so use &)
591+
if '?' in download_request.uri:
592+
download_request.uri += '&alt=media'
593+
else:
594+
download_request.uri += '?alt=media'
484595
485596
output_filename = f"v2er-{os.environ['VERSION_NAME']}_google_play_signed.apk"
597+
598+
# Use media download with googleapiclient.http to handle binary content
599+
import io
600+
from googleapiclient.http import MediaIoBaseDownload
601+
602+
file_io = io.BytesIO()
603+
downloader = MediaIoBaseDownload(file_io, download_request)
604+
605+
done = False
606+
while done is False:
607+
status, done = downloader.next_chunk()
608+
if status:
609+
print(f"Download progress: {int(status.progress() * 100)}%")
610+
611+
# Write to file
486612
with open(output_filename, 'wb') as f:
487-
for chunk in response.iter_content(chunk_size=8192):
488-
f.write(chunk)
613+
f.write(file_io.getvalue())
489614
490615
print(f"Successfully downloaded: {output_filename}")
491616
print(f"apk_path={output_filename}")
@@ -526,46 +651,9 @@ jobs:
526651
echo "found=false" >> $GITHUB_OUTPUT
527652
fi
528653
else
529-
echo "Failed to download Google Play signed APK, falling back to universal APK generation"
654+
echo "Failed to download Google Play signed APK"
530655
cat download_output.txt
531-
532-
# Fallback: Generate universal APK from AAB as before
533-
AAB_PATH=$(find bundle-artifacts -name "*.aab" 2>/dev/null | head -1)
534-
if [ -z "$AAB_PATH" ]; then
535-
echo "No AAB found for fallback, skipping"
536-
echo "found=false" >> $GITHUB_OUTPUT
537-
exit 0
538-
fi
539-
540-
echo "Generating universal APK from AAB as fallback..."
541-
542-
# Download bundletool
543-
curl -L -o bundletool-all.jar https://github.com/google/bundletool/releases/latest/download/bundletool-all.jar
544-
545-
# Create dummy keystore
546-
keytool -genkey -v -keystore debug.keystore -alias androiddebugkey \
547-
-keyalg RSA -keysize 2048 -validity 10000 \
548-
-dname "CN=Android Debug,O=Android,C=US" \
549-
-storepass android -keypass android
550-
551-
# Generate universal APK
552-
java -jar bundletool-all.jar build-apks \
553-
--bundle="$AAB_PATH" \
554-
--output=apks.apks \
555-
--mode=universal \
556-
--ks=debug.keystore \
557-
--ks-pass=pass:android \
558-
--ks-key-alias=androiddebugkey \
559-
--key-pass=pass:android
560-
561-
# Extract APK
562-
unzip -q apks.apks universal.apk
563-
OUTPUT_FILE="v2er-${VERSION_NAME}_google_play_signed.apk"
564-
mv universal.apk "$OUTPUT_FILE"
565-
566-
echo "Generated fallback APK: $OUTPUT_FILE"
567-
echo "apk_path=$OUTPUT_FILE" >> $GITHUB_OUTPUT
568-
echo "found=true" >> $GITHUB_OUTPUT
656+
echo "found=false" >> $GITHUB_OUTPUT
569657
fi
570658
571659
- name: Create Google Play link info
@@ -625,6 +713,10 @@ jobs:
625713
echo "- When installed from Play Store, it will use Google Play's signing certificate" >> $GITHUB_STEP_SUMMARY
626714
echo "- The APK has been uploaded to the GitHub Release" >> $GITHUB_STEP_SUMMARY
627715
else
628-
echo "⚠️ **No AAB found in artifacts**" >> $GITHUB_STEP_SUMMARY
629-
echo "Signed APK generation requires a release bundle (AAB)" >> $GITHUB_STEP_SUMMARY
716+
echo "⚠️ **Google Play signed APK download failed**" >> $GITHUB_STEP_SUMMARY
717+
echo "" >> $GITHUB_STEP_SUMMARY
718+
echo "This may be because:" >> $GITHUB_STEP_SUMMARY
719+
echo "- Google Play is still processing the upload" >> $GITHUB_STEP_SUMMARY
720+
echo "- The version hasn't been released to any track yet" >> $GITHUB_STEP_SUMMARY
721+
echo "- API permissions are insufficient" >> $GITHUB_STEP_SUMMARY
630722
fi

0 commit comments

Comments
 (0)