Android는 Github Actions를 사용하여 Dandelion App 내부 테스트 배포 플랫폼에 apk를 지속적으로 통합하고 자동으로 업로드합니다(인증서 암호 둔감화 포함).

목차

1. 소개

2. Github Actions 지속적 통합

3. Dandelion에 APK 업로드

4. Gradle은 Github Actions의 비밀과 함께 사용됩니다.

4.1 Github 작업 비밀 설정

4.2 Github Actions Secrets 세트를 읽기 위해 app.gradlede의signingConfigs 수정

5. YAML, YML 온라인 편집기(검증기)


1. 소개

지속적인 통합에 대한 기사의 저자는 이전에 Jenkins 및 Travis CI를 사용한 여러 사례를 작성했습니다.

Docker+Jenkins는 Android의 지속적인 통합을 실현합니다. (1)

Docker+Jenkins는 Android의 지속적인 통합을 실현합니다. (2)

Travis CI를 기반으로 한 Android의 지속적인 통합 실습

Travis CI는 원래 유료와 무료의 두 가지 버전으로 나뉘며 개인 프로젝트에 대해 요금을 청구하고 모든 무료 및 오픈 소스(OSS) 프로젝트에 대해 무료 크레딧을 제공합니다. 2019년 1월 Travis CI는 Idera에 인수 되었으며 당시 공식 개런티는 오픈 소스 프로젝트에 대한 무료 서비스를 계속 제공하는 것이지만 지금은 비즈니스 전략을 변경했습니다. 최신 공식 가격 체계를 확인하십시오.

제한된 크레딧, 즉시 불미스러운?

• 10,000크레딧

• Assembla, Bitbucket, GitHub 및 GitL ab 와 호환 가능

써클 CI와 비교하면 판단이다.

말할 필요도 없이 Jenkins, 돈과 의지가 있다면 직접 서버를 설정할 수 있습니다.

Github Actions의 무료 식사 제안을 살펴보십시오.

지원되는 실행기 및 하드웨어 리소스
Windows 및 Linux 가상 머신의 하드웨어 사양:

2코어 CPU
7GB RAM 메모리
14GB SSD 디스크 공간
macOS 가상 머신의 하드웨어 사양:

3코어 CPU
14GB RAM 메모리
14GB SSD 디스크 공간

위의 정보는 Github Actions의 공식 문서에서 가져온 것입니다.

GitHub에서 호스팅하는 러너 정보 - GitHub Docs

높은 성능과 무료, Github Actions가 좋은지 아닌지 물어보십시오.

2. Github Actions 지속적 통합

반복되는 내용을 다시 작성할 필요가 없습니다. 다음은 참고용으로 더 잘 작성된 두 개의 기사입니다.

【 지속적인 통합 】Android는 Github Action을 사용하여 Fir.im 내부 테스트를 자동으로 패키징하고 릴리스합니다.

Xue XuanGithub Actions 사용자 가이드 및 Android 지속적 통합 예제

3. Dandelion에 APK 업로드

fir.im(betaqr.com)이 아닌 민들레인 이유는 많이 말하지 않겠습니다. 먼저 android.yml 파일의 전체 그림을 살펴보세요.

name: Android CI

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v2
    - name: set up JDK 11
      uses: actions/setup-java@v2
      with:
        java-version: '11'
        distribution: 'temurin'
        cache: gradle

    - name: Grant execute permission for gradlew
      run: chmod +x gradlew

    - name: Build with Gradle
      run: ./gradlew app:assembleRelease

    - name: Upload apk to pgyer.com
      run: find ${
   
   { github.workspace }}/app/build/outputs/apk/release/ -name "*.apk*" -type f -exec curl -F "file=@{}" -F "uKey=${
   
   { secrets.PGYER_UKEY }}" -F "_api_key=${
   
   { secrets.PGYER_API_KEY }}" https://upload.pgyer.com/apiv1/app/upload  \;

    - name: Upload apk to artifact
      uses: actions/upload-artifact@master
      if: always()
      with:
        name: lottery_app
        path: ${
   
   { github.workspace }}/app/build/outputs/apk/release/*.apk

위는 저자의 오픈 소스 프로젝트에서 가져온 것입니다.

https://github.com/xiangang/RecyclerViewLoopScrollAnimation/blob/main/.github/workflows/android.yml

android.yml의 다른 부분에 대해서는 너무 많이 설명하지 않겠습니다. 여기서는 파일명이 불확실한 apk를 업로드하는 방법을 주로 소개하겠습니다. apk 이름과 채널 이름, 버전 번호, 컴파일 시간 등이 포함되어 있으므로 apk 이름을 하드코딩하는 것은 명백히 부적절합니다. 작성자는 처음에 *.apk를 사용하여 일치시키려고 했지만, curl이 이러한 방식의 와일드카드 사용을 지원하지 않기 때문에 파일을 열 수 없다는 메시지가 계속 표시되었습니다.

무엇을 해야 합니까? *.apk는 일치할 수 없으며 먼저 apk 이름을 찾은 다음 find 명령으로 완료할 수 있습니다.

다음 명령어를 사용하여 apk 파일 이름을 쉽게 조회할 수 있으며, 물론 apk가 여러 개인 경우 일치 조건을 더 정확하게 수정해야 합니다.

find ./ -name "*.apk*" -type f

파일명이 발견되면 curl 명령어로 사용하는 것은 당연하지 않을까요? 전적으로. 여기에서는 find 명령의 사용법을 소개하지 않습니다. 관심이 있는 경우 man find 명령을 사용하여 도움말 문서를 볼 수 있습니다.

다음 명령의 의미를 간략하게 설명하십시오.

실행: 찾기 ${
    
   { github.workspace }}/app/build/outputs/apk/release/ -name "*.apk*" -type f -exec curl -F "file=@{}" -F "uKey= ${
    
   {비밀.PGYER_UKEY }}" -F "_api_key=${
    
   {비밀.PGYER_API_KEY }}" https://upload.pgyer.com/apiv1/app/upload \;

찾기 ${ { github.workspace }}/app/build/outputs/apk/release/ -name "*.apk*" -type f

${ { github.workspace }}/app/build/outputs/apk/release/ 디렉터리 에서 *.apk* 파일을 찾습니다. 그 중 -type은 지정된 파일 유형을 나타내고 f는 공통 문서 유형을 나타냅니다.

find xxx -exec xxxx {} \; : 찾은 파일에서 특정 명령을 실행하는 것을 나타내며 명령은 find로 찾은 파일 집합을 사용할 수 있습니다.

-exec : xxxx 명령의 실행을 나타냅니다.

{} : find 명령으로 찾은 정규화된 파일 모음에 저장된 변수를 나타냅니다.

\; : find 명령의 범위는 다음에서 끝납니다.

위의 사용법을 이해하고 나면 앞으로 다른 빌드 제품을 만날 때 업로드하거나 이메일을 보내는 것이 매우 간단할 것입니다 . 새로운 스킬 겟!

4. Gradle은 Github Actions의 비밀과 함께 사용됩니다.

일반 상업 프로젝트의 경우 일반적으로 기밀 유지 조치가 마련되어 있습니다. 즉, apk를 컴파일할 때 서명에 ​​사용되는 인증서의 암호 또는 기타 기밀 콘텐츠를 일반 텍스트로 코드 저장소에 직접 제출하면 안 됩니다. 지속적인 통합 환경에서는 일반적으로 노출된 평문 비밀번호를 환경변수로 대체할 수 있으며, 물론 내부 회사 서버나 기타 인가된 통합 환경이라면 암호화 알고리즘을 작성하여 평문 비밀번호를 암호화한 후 컴파일한 다음 복호화합니다.

이 문서에서는 환경 변수를 설정하여 둔감화 효과에 대해서만 소개합니다. 이 부분의 내용은 "트래비스 CI 기반의 안드로이드 연속 통합 실습" 에서도 설명할 예정이니 관심 있으신 분들은 읽어보시면 좋을 것 같습니다.

4.1 Github 작업 비밀 설정

Github 리포지토리 설정 페이지에 들어가서 왼쪽의 비밀 메뉴를 클릭하고 오른쪽 상단 모서리에 있는 새 저장소 비밀을 클릭하고 리디렉션된 페이지에서 해당 이름 값을 입력하고 비밀 추가 버튼을 클릭합니다. 이름은 자동으로 대문자로 변환됩니다.

이름에 해당하는 값을 수정하는 것도 매우 간단합니다. 수정할 목록에서 업데이트를 클릭하면 됩니다. Value만 수정할 수 있고 Name은 수정할 수 없으니, Name이 틀리면 제거하고 다시 추가하면 됩니다. 그림과 같이 작성자는 서명에 필요한 세 가지 비밀을 설정했습니다.

4.2 Github Actions Secrets 세트를 읽기 위해 app.gradlede의signingConfigs 수정

먼저 원래 인증서 비밀번호를 읽는 방법을 살펴보십시오.

static def getAppReleaseTime() {
    return new Date().format("yyyyMMdd_HHmm", TimeZone.getTimeZone("Asia/Shanghai"))
}

// Remove private signing information from your project
// 创建一个名为keystorePropertiesFile的变量,并将其初始化为rootProject文件夹中的keystore.properties文件。
def keystorePropertiesFile = rootProject.file("keystore.properties")
// 初始化一个名为keystoreProperties的新Properties()对象
def keystoreProperties = new Properties()
// 将keystore.properties文件加载到keystoreProperties对象中
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))


android {
    compileSdk 31

    defaultConfig {
        applicationId "com.nxg.app"
        minSdk 21
        targetSdk 31
        versionCode 1
        versionName "1.0.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    signingConfigs {
        release {
            storeFile keystoreProperties['keyAlias'] != null ? file(keystoreProperties['storeFile']) : file('../demo.jks')
            storePassword keystoreProperties['storePassword'] != null ? keystoreProperties['storePassword'] : System.getenv("storePassword")
            keyAlias keystoreProperties['keyAlias'] != null ? keystoreProperties['keyAlias'] : System.getenv("keyAlias")
            keyPassword keystoreProperties['keyPassword'] != null ? keystoreProperties['keyPassword'] : System.getenv("keyPassword")
        }
    }

    buildTypes {
        debug {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
            shrinkResources true //是否清理无用资源,依赖于minifyEnabled
            zipAlignEnabled true //是否启用zipAlign压缩
            signingConfig signingConfigs.release
            manifestPlaceholders = [RELEASE_TIME: getAppReleaseTime()]
            multiDexEnabled = true
            versionNameSuffix = ''
        }

        release {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
            shrinkResources true //是否清理无用资源,依赖于minifyEnabled
            zipAlignEnabled true //是否启用zipAlign压缩
            signingConfig signingConfigs.release
            manifestPlaceholders = [RELEASE_TIME: getAppReleaseTime()]
            multiDexEnabled = true
            versionNameSuffix = ''
            // 自定义apk名称
            applicationVariants.all { variant ->
                variant.outputs.all { output ->
                    def fileName = "lucky_cube_app_release_${variant.versionName}_${appReleaseTime}.apk"
                    def outFile = output.outputFile
                    if (outFile != null && outFile.name.endsWith('.apk')) {
                        outputFileName = fileName
                    }
                }
            }
        }

    }
}

프로젝트의 루트 디렉터리에는 keystore.properties 파일이 있습니다.

# 线上版本持续集成使用环境变量,本地开发时手动填上对应的值
storePassword =
keyAlias = 
keyPassword =
storeFile= 

로컬로 컴파일된 경우 일반적인 방법은 keystore.properties에서 해당 암호 및 인증서 파일 경로를 채우는 것입니다. 온라인 컴파일의 경우 온라인 컴파일 환경에서 설정한 환경 변수를 사용합니다.

앞서 Github Actions Serects를 통해 서명에 필요한 Secret을 설정했는데 어떻게 사용할까요? 물론 Github Actions Serects는 실제로 Github Actions에서 제공하는 서버에 저장되어 있기 때문에 환경변수를 사용하고 Gradle에서 직접 읽을 수 있는 방법이 없기 때문에 환경변수를 읽어서 해당 Github Actions Serects를 간접적으로 읽을 수 밖에 없다.

코드로 바로 이동:

static def getAppReleaseTime() {
    return new Date().format("yyyyMMdd_HHmm", TimeZone.getTimeZone("Asia/Shanghai"))
}

// Remove private signing information from your project
// 创建一个名为keystorePropertiesFile的变量,并将其初始化为rootProject文件夹中的keystore.properties文件。
def keystorePropertiesFile = rootProject.file("keystore.properties")
// 初始化一个名为keystoreProperties的新Properties()对象
def keystoreProperties = new Properties()
// 将keystore.properties文件加载到keystoreProperties对象中
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
println "keystoreProperties->$keystoreProperties"

def getStoreFile = {
    def storeFile = keystoreProperties['storeFile']
    if (storeFile == null || storeFile.isEmpty()) {
        storeFile = '../demo.jks'
    }
    return storeFile
}

def getStorePassword = {
    def storePassword = keystoreProperties['storePassword']
    if (storePassword == null || storePassword.isEmpty()) {
        storePassword = System.getenv("storePassword")
    }
    return storePassword
}

def getKeyAlias = {
    def keyAlias = keystoreProperties['keyAlias']
    if (keyAlias == null || keyAlias.isEmpty()) {
        keyAlias = System.getenv("keyAlias")
    }
    return keyAlias
}

def getKeyPassword = {
    def keyPassword = keystoreProperties['keyPassword']
    if (keyPassword == null || keyPassword.isEmpty()) {
        keyPassword = System.getenv("keyPassword")
    }
    return keyPassword
}

println "storePassword->${System.getenv("storePassword")}"
println "keyAlias->${System.getenv("keyAlias")}"
println "keyPassword->${System.getenv("keyPassword")}"

println "getStoreFile->${getStoreFile()}"
println "getStorePassword->${getStorePassword()}"
println "getKeyAlias->${getKeyAlias()}"
println "getKeyPassword->${getKeyPassword()}"

android {
    compileSdk 31

    defaultConfig {
        applicationId "com.nxg.app"
        minSdk 21
        targetSdk 31
        versionCode 1
        versionName "1.0.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    signingConfigs {

        release {
            storeFile file(getStoreFile())
            storePassword getStorePassword()
            keyAlias getKeyAlias()
            keyPassword getKeyPassword()
        }
    }

    buildTypes {
        debug {
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
            manifestPlaceholders = [RELEASE_TIME: getAppReleaseTime()]
            multiDexEnabled = true
        }

        release {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
            shrinkResources true //是否清理无用资源,依赖于minifyEnabled
            zipAlignEnabled true //是否启用zipAlign压缩
            signingConfig signingConfigs.release
            manifestPlaceholders = [RELEASE_TIME: getAppReleaseTime()]
            multiDexEnabled = true
            versionNameSuffix = ''
            // 自定义apk名称
            applicationVariants.all { variant ->
                variant.outputs.all { output ->
                    def fileName = "lucky_cube_app_release_${variant.versionName}_${appReleaseTime}.apk"
                    def outFile = output.outputFile
                    if (outFile != null && outFile.name.endsWith('.apk')) {
                        outputFileName = fileName
                    }
                }
            }
        }

    }
}

에서 ENV_NAME 이라는 환경 변수 값을 얻기 위해 app.gradle사용되는 것을 볼 수 있으며 , 물론 다른 방법으로 작성할 수도 있습니다 .System.getenv("ENV_NAME")System.env.ENV_NAME

여기서는 storePassword를 예로 들어 컴파일 시 keystore.properties에 정의된 storePassword의 값을 먼저 얻고, 비어 있으면 환경 변수에 정의된 값을 얻게 되므로 온라인 컴파일 환경에 대응하여 일반적으로 keystore.properties에 정의된 값을 제출하지 않습니다. 이 값은 로컬에서만 사용되며 keystore.properties도 코드 웨어하우스에 제출되지 않습니다.

def getStorePassword = {
    def storePassword = keystoreProperties['storePassword']
    if (storePassword == null || storePassword.isEmpty()) {
        storePassword = System.getenv("storePassword")
    }
    return storePassword
}

이러한 방식으로, signingConfigs 에서 해당 메소드를 직접 호출하여 환경 변수의 값을 얻을 수 있습니다.

signingConfigs {
    release {
        storeFile file(getStoreFile())
        storePassword getStorePassword()
        keyAlias getKeyAlias()
        keyPassword getKeyPassword()
    }
}

그게 끝인가요? 물론 아닙니다. 환경변수와 Github Actions Serects가 아직 연결되어 있지 않기 때문에 이때 읽은 환경변수는 아무런 값이 없다는 점을 잊지 마세요.

환경 변수는 Github Actions Serects와 어떤 관련이 있습니까?

Linux 시스템 사용에 익숙한 학생들은 바로 export명령어를 떠올릴 것입니다. export명령어를 통해 임시 환경 변수를 설정할 수 있습니다. 변수의 값은 Github Actions Serects 에서 설정한 값 에 해당합니다.

핵심 android.yml 코드는 다음과 같습니다.

name: Android CI

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v2
    - name: set up JDK 11
      uses: actions/setup-java@v2
      with:
        java-version: '11'
        distribution: 'temurin'
        cache: gradle

    - name: Grant execute permission for gradlew
      run: chmod +x gradlew

    - name: Build with Gradle
      run: |
        export storePassword=${
   
   { secrets.STOREPASSWORD }}
        export keyAlias=${
   
   { secrets.KEYALIAS }}
        export keyPassword=${
   
   { secrets.KEYPASSWORD }}
        ./gradlew app:assembleRelease


    - name: Upload apk to pgyer.com
      run: find ${
   
   { github.workspace }}/app/build/outputs/apk/release/ -name "*.apk*" -type f -exec curl -F "file=@{}" -F "uKey=${
   
   { secrets.PGYER_UKEY }}" -F "_api_key=${
   
   { secrets.PGYER_API_KEY }}" https://upload.pgyer.com/apiv1/app/upload  \;

    - name: Upload apk to artifact
      uses: actions/upload-artifact@master
      if: always()
      with:
        name: lottery_app
        path: ${
   
   { github.workspace }}/app/build/outputs/apk/release/*.apk

Github Actions에서 설정비밀을 이런 형태로 얻은 것을 볼 수 있는데 ${ { secrets.STOREPASSWORD }}, 현재 실행 중인 컴파일 환경에서 시스템에 설정된 임시 환경 변수를 읽기 위해 명령과 협력하기만 하면 된다 export.왜 일시적인가? 컴파일 마지막에 이 임시 환경 변수는 더 이상 읽을 수 없으며 언제든지 컴파일 및 사용할 수 있으며 시스템이 다 사용해도 영향을 받지 않습니다.

우리는 assembleRelease 작업을 사용하여 apk를 컴파일하므로 환경 변수 설정은 assembleRelease 작업을 실행하기 전에 apk를 컴파일할 때 System.getenv("ENV_NAME")환경 변수에 설정된 Github Actions Serects를 읽을 수 있도록 해야 합니다.

지금까지 Github Actions Serects 및 환경 변수를 통해 인증서 암호를 둔감하게 처리하여 온라인 컴파일 환경의 보안을 어느 정도 보장합니다. 새로운 스킬 겟!

5. YAML, YML 온라인 편집기(검증기)

YAML, YML 온라인 편집기(서식 검증)-BeJSON.com

추가 보너스로 android.yml 작성으로 인해 실수로 스크립트 컴파일 문제가 자주 발생하는데, 이때 이 온라인 웹 사이트를 통해 확인할 수 있으므로 문제 해결에 매우 편리합니다. 당신의 시간이 매우 소중하다는 것을 아십시오! 오른쪽.

끝으로 글을 마치며 먼저 글 전체를 읽어주신 여러분의 인내심에 진심으로 감사드립니다. 당신의 격려는 저자의 고집입니다. 기사에 잘못된 부분이 있으면 수정해 주시면 다시 한 번 감사드립니다.

추천

출처blog.csdn.net/xiangang12202/article/details/122594984