플러터 앱을 만들었고, 이제 플레이 콘솔에서 수동으로 해보니 별다른 문제없이 배포되었다.
내부테스트 까지 해봤고, 조금 정리후에는 실제 플레이 스토어에 출시 할 수 있을 듯 하다.

그전에 수동 업로드 과정을 자동화 하고자 한다.

관련 API 를 제공하는 듯 하다.

Google Play Android Developer API  가 세팅되어 있어야 한다.
관련 부분은 필요하면 따로 정리.

CI/CD 로 주로 fastlane 과 연동하는 방법을 많이 쓰는 것 같다.

난 좀 단순화(?) 시켜서 반자동 으로 처리하고자 한다.
업로드 부분은 python 코드로 다음과 같다.

{
"package_name": "{your-package-name}",
"service_account_file": "../{your-google-play-developer-api-key.json}",
"aab_file": "build/app/outputs/bundle/release/app-release.aab",
"mapping_file": "build/app/outputs/mapping/release/mapping.txt",
"debug_symbols_dir": "build/app/intermediates/merged_native_libs/release/out/lib"
}
view raw config.json hosted with ❤ by GitHub
import sys
import os
import json
import zipfile
from google.oauth2 import service_account
from googleapiclient.discovery import build
from googleapiclient.http import MediaFileUpload
import logging
#logging.basicConfig(level=logging.DEBUG)
def load_config(config_file):
with open(config_file, 'r') as f:
return json.load(f)
def create_zip_from_directory(zip_filename, directory):
with zipfile.ZipFile(zip_filename, 'w', zipfile.ZIP_DEFLATED) as zipf:
for root, _, files in os.walk(directory):
for file in files:
file_path = os.path.join(root, file)
zipf.write(file_path, os.path.relpath(file_path, directory))
def upload_aab_and_symbols(config, release_notes):
credentials = service_account.Credentials.from_service_account_file(
config['service_account_file'],
scopes=['https://www.googleapis.com/auth/androidpublisher']
)
service = build('androidpublisher', 'v3', credentials=credentials)
package_name = config['package_name']
aab_file = config['aab_file']
mapping_file = config['mapping_file']
debug_symbols_dir = config['debug_symbols_dir']
try:
edit_request = service.edits().insert(body={}, packageName=package_name)
result = edit_request.execute()
edit_id = result['id']
# AAB 파일 업로드
print(f'AAB 파일 업로드 : start')
aab_response = service.edits().bundles().upload(
editId=edit_id,
packageName=package_name,
media_body=MediaFileUpload(aab_file, mimetype='application/octet-stream')
).execute()
print(f'AAB 파일이 성공적으로 업로드되었습니다. 버전 코드: {aab_response["versionCode"]}')
versionCode = aab_response["versionCode"]
debugUploadDone = 1
print(f'디버그 심볼 파일 업로드 : start')
# 디버그 심볼 ZIP 파일 생성 및 업로드
zip_filename = 'native_debug_symbols.zip'
create_zip_from_directory(zip_filename, debug_symbols_dir)
print(f'ZIP 파일 생성됨: {zip_filename}')
try:
deobfuscation_file = service.edits().deobfuscationfiles().upload(
editId=edit_id,
packageName=package_name,
apkVersionCode=versionCode,
deobfuscationFileType='nativeCode',
media_body=MediaFileUpload(
zip_filename,
mimetype='application/octet-stream',
resumable=True,
chunksize=262144
)
).execute()
print('디버그 심볼 ZIP 파일이 업로드되었습니다.')
except Exception as upload_error:
print(f'ZIP 파일 업로드 중 오류 발생: {upload_error}')
debugUploadDone = 0 # fail
# proguard
print(f'ReTrace 매핑 파일 업로드 : start')
try:
mapping_file_response = service.edits().deobfuscationfiles().upload(
editId=edit_id,
packageName=package_name,
apkVersionCode=versionCode,
deobfuscationFileType='proguard',
media_body=MediaFileUpload(mapping_file, mimetype='application/octet-stream')
).execute()
print('ReTrace 매핑 파일이 성공적으로 업로드되었습니다.')
except Exception as e:
print(f'ReTrace 매핑 파일 업로드 실패: 오류 내용: {str(e)}')
debugUploadDone = 0 # fail
# all process done.
if debugUploadDone == 1 :
# 릴리즈 노트 업데이트 및 트랙에 추가
service.edits().tracks().update(
editId=edit_id,
track='internal',
packageName=package_name,
body={
'releases': [{
'versionCodes': [versionCode],
'status': 'draft', # draft => only / statusUnspecified / inProgress / halted / - completed(X)
'releaseNotes': [
{
'language': 'ko-KR',
'text': release_notes
}
]
}]
}
).execute()
# 변경사항 커밋
commit_request = service.edits().commit(editId=edit_id, packageName=package_name)
commit_request.execute()
print('AAB 파일, 네이티브 디버그 기호, 그리고 ReTrace 매핑 파일이 성공적으로 Google Play Console에 업로드되었습니다.')
else :
print('\n===== Fail release upload')
except Exception as e:
print(f'오류 발생: {str(e)}')
finally:
# 임시로 생성한 ZIP 파일 삭제
if os.path.exists(zip_filename):
os.remove(zip_filename)
if __name__ == "__main__":
if len(sys.argv) < 2:
print(f"사용법: python {sys.argv[0]} '릴리즈 노트' [config.json-another]")
sys.exit(1)
release_notes = sys.argv[1]
config_file = 'config.json'
if len(sys.argv) > 2 and sys.argv[2]:
config_file = sys.argv[2]
try:
config = load_config(config_file)
except Exception as e:
print(f'config load 오류 발생: {str(e)}')
upload_aab_and_symbols(config, release_notes)
view raw play-release.py hosted with ❤ by GitHub

위와 같은 코드를 사용했다.

본인 프로젝트에 맞는 config.json 의 내용을 수정하고, python 환경에서 실행하면 된다.

flutter build appbundle 으로 aab 파일을 생성한 후에 업로드

프로젝트 폴더에서 실행

python3 play-release.py 'feature: auto relase code'

 

python 환경이 구성되어 있지 않다면.

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
brew install python

pip3 install google-auth google-auth-httplib2 google-api-python-client

python  venv 를 구성하는 방법도 좋은 팁이다.(이 부분도 추후 필요시 정리)

반응형

WRITTEN BY
1day1
하루하루 즐거운일 하나씩, 행복한일 하나씩 만들어 가요.

,

플러터 앱을 테스트 하기에 가장 좋은 방법이 뭘까?

apk 파일을 보내서 설치 테스트 하는 것은 뭔가 찜찜하다.

플레이 콘솔에서 공식 지원하는 내부테스트 방법으로 해보고자 한다. (apk 대신 aab - android app bundle ? )

플러터 기본 빌드 기능에서 "Build App Bundle" 이 있어서 해봤다.

플레이 콘솔에서 앱 업로드를 해보니,

디버그 모드로 서명한 APK 또는 Android App Bundle을 업로드했습니다. 
출시 모드로 APK 또는 Android App Bundle에 서명해야 합니다.

위와 같은 메시지. ( 서명 키를 만드는 방법 - https://developer.android.com/studio/publish/app-signing#generate-key )

위 방법중 "Generate Signed Bundle / APK " 플러터 프로젝트에는 메뉴가 없다.

android studio 를 플러터 프로젝트 로 열어서 그렇다.
프로젝트의 android 라는 폴더를 안드로이드 스튜디오에서 File / Open 으로 열면 해당 메뉴가 보인다. (즉, android 프로젝트 로 연다.)

키스토어 관련 정보를 입력하고,

일단 된 듯 하여, 플러터 프로젝트에서 빌드해서 업로드 해봐도 동일한 메시지가 나왔다. 뭔가 키스토어 연결이 안된 듯 하다.
(위 링크중 https://developer.android.com/studio/publish/app-signing#sign-auto 부분을 설정한다.)
요약하자면, singing config 를 만들고 => Build Type 에 해당 config 를 맞추고 =>

최근 안드로이드 스튜디오의 메뉴 위치는 살짝 다르다.

일단 release-test 로 릴리즈 테스트 로 만들었다. (기존 debug 설정에 넣어도 될 듯 함)
- 아! 화면에는 오타가 암튼 중요한 건 아니니 -

Build Variants 에 해당 Signing Config 를 선택해준다.

여기까지 하고, 플러터에서 빌드 => 업로드 . 다시 에러.

테스트 빌드도 세부적인 것을 많이 체크하나보다. 암튼 저것도 맞춰준다.

 

저것까지 맞추고 다시 업로드.

OK 성공.

 

그러나.

.

.

내부 테스트를 위해 할일이 많군. ( 경고 쪽은 안내대로 해주면 OK)

오류 => 계정문제 는 최근에 계정 등록해서 인증이 완료되지 않았다.

위 절차를 해야 한다.

근데, 관련 필요 서류로 할 만한 것이 없다.
오프라인 서류는 당연히 없고, 온라인 서류로 발급 될 만한 것을 찾아봐야 겠다.

=== 일단 될만한 것이 있어 시도 해본다. ===

위 서류 예시에는 없지만, 국세청에서 부가세 우편물에 이름/주소가 있어서 시도해본다.(공공요금 청구서에 해당하려나?)

과연 승인이 떨어질지...

 

[추가+2일]

승인이 되었다. - 영업일 기준 1일 이내에 처리되는 듯 하다(주말이 끼어 있어...)

내부테스터 로 출시 후에 설치 시도를 해보았다.

그런데, 아래 메시지 와 함께 설치가 안된다.

 

개발용 기기에 설치를 하려고 한 듯 해서, 폰에서 설정 > 앱  메뉴에서 해당 앱을 삭제 했다.

그래도 안된다.  "모든 사용자에서 삭제" 로 해야 한다고 하는데, 그 부분 때문일까?

위 현상이 안드로이드 스튜디오에서 Run 할때도 이상현상이 발생한다.

여기서 멈춘다. Install 로 안 넘어간다.

 

[설치 이상 현상 조치]

adb 로 직접 폰에 접속해서 조치 하기로 했다.

무선 디버깅 / USB 기기 연결 등 한 후에

adb shell
으로 접속

pm list packages | grep "패키지 명"

위 명령 하여 패키지 명을 알아낸다.

pm uninstall "전체패키지명"

예시) pm uninstall com.example.myapp


한번에 해도 된다.

adb shell pm uninstall com.example.myapp

위 명령으로 조치 후 - 설치 시도를 해보니 정상 설치 된다.

 

==>

그런데, 실제 앱 테스트 하니, 인터넷 접속이 안되는지, API 서비스 연결이 안되는지 , 정상동작 하지 않는다.
릴리즈 용 권한설정이 빠진것일까?

더 알아봐야 겠다.

 

[추가 - 역시 권한 문제]

디버그 쪽에는 자동으로 들어가 있는데, 릴리즈 시에는 없으니, 인터넷이 꼭 필요한 앱은 넣어줘야 한다.

다시 내부 테스트 릴리즈 해보니 정상 동작 함.

그런데, 개발기기에서는 위 adb shell pm uninstall 을 매번 해줘야 하네. (불편하군)

 

반응형

WRITTEN BY
1day1
하루하루 즐거운일 하나씩, 행복한일 하나씩 만들어 가요.

,