NiceLeeのBlog 用爱发电 bilibili~

一次GitHub Action自动化发布集成部署(Android 篇)

2021-09-09
nIceLee

阅读:


分为三步。

  1. 在本地实现脱离IDE直接脚本打包。
  2. 分离敏感资源,确定在远程部署这些的方式方法。
  3. 在Github Actions机器上实现脚本打包并发布。

本地脚本打包

  • 实际上IDE在创建工程时已经将基本的脚本什么的写好了
    • gradlew - Linux
    • gradlew.bat - Windows
    • 配置完毕以后直接运行脚本
        chmod +x gradlew #linux需要先授权, windows不需要
        ./gradlew assembleRelease
      
  • 混淆(可选)
    • 修改app/build.gradle文件
      buildTypes {
        release {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
      }
      
    • 根据需要配置app/proguard-rules.pro文件
      proguard-rules.pro
        
        # Add project specific ProGuard rules here.
        # You can control the set of applied configuration files using the
        # proguardFiles setting in build.gradle.
        #
        # For more details, see
        #   http://developer.android.com/guide/developing/tools/proguard.html
      
        # If your project uses WebView with JS, uncomment the following
        # and specify the fully qualified class name to the JavaScript interface
        # class:
        #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
        #   public *;
        #}
      
        # Uncomment this to preserve the line number information for
        # debugging stack traces.
        #-keepattributes SourceFile,LineNumberTable
      
        # If you keep the line number information, uncomment this to
        # hide the original source file name.
        #-renamesourcefileattribute SourceFile
      
        #############################################
        #
        # 基本指令区域(没什么别的需求不需要动)
        #
        #############################################
        # 代码混淆压缩比,在0~7之间,默认为5,一般不做修改
        -optimizationpasses 5
      
        # 混合时不使用大小写混合,混合后的类名为小写
        -dontusemixedcaseclassnames
      
        # 指定不去忽略非公共库的类
        -dontskipnonpubliclibraryclasses
      
        # 这句话能够使我们的项目混淆后产生映射文件
        # 包含有类名->混淆后类名的映射关系
        -verbose
      
        # 指定不去忽略非公共库的类成员
        -dontskipnonpubliclibraryclassmembers
      
        # 不做预校验,preverify是proguard的四个步骤之一,Android不需要preverify,去掉这一步能够加快混淆速度。
        -dontpreverify
      
        # 保留Annotation不混淆
        -keepattributes *Annotation*,InnerClasses
      
        # 避免混淆泛型
        -keepattributes Signature
      
        # 抛出异常时保留代码行号
        -keepattributes SourceFile,LineNumberTable
      
        # 指定混淆是采用的算法,后面的参数是一个过滤器
        # 这个过滤器是谷歌推荐的算法,一般不做更改
        -optimizations !code/simplification/cast,!field/*,!class/merging/*
      
      
        #############################################
        #
        # Android开发中一些需要保留的公共部分(没什么别的需求不需要动)
        #
        #############################################
      
        # 保留我们使用的四大组件,自定义的Application等等这些类不被混淆
        # 因为这些子类都有可能被外部调用
        -keep public class * extends android.app.Activity
        -keep public class * extends android.app.Appliction
        -keep public class * extends android.app.Service
        -keep public class * extends android.content.BroadcastReceiver
        -keep public class * extends android.content.ContentProvider
        -keep public class * extends android.app.backup.BackupAgentHelper
        -keep public class * extends android.preference.Preference
        -keep public class * extends android.view.View
        -keep public class com.android.vending.licensing.ILicensingService
      
      
        # 保留support下的所有类及其内部类
        -keep class android.support.** {*;}
      
        # 保留继承的
        -keep public class * extends androidx.**
        -keep public class * extends android.support.v4.**
        -keep public class * extends android.support.v7.**
        -keep public class * extends android.support.annotation.**
      
        # 保留R下面的资源
        -keep class **.R$* {*;}
      
        # 保留本地native方法不被混淆
        -keepclasseswithmembernames class * {
            native ;
        }
      
        # 保留在Activity中的方法参数是view的方法,
        # 这样以来我们在layout中写的onClick就不会被影响
        -keepclassmembers class * extends android.app.Activity{
            public void *(android.view.View);
        }
      
        # 保留枚举类不被混淆
        -keepclassmembers enum * {
            public static **[] values();
            public static ** valueOf(java.lang.String);
        }
      
        # 保留我们自定义控件(继承自View)不被混淆
        -keep public class * extends android.view.View{
            *** get*();
            void set*(***);
            public (android.content.Context);
            public (android.content.Context, android.util.AttributeSet);
            public (android.content.Context, android.util.AttributeSet, int);
        }
      
        # 保留Parcelable序列化类不被混淆
        -keep class * implements android.os.Parcelable {
            public static final android.os.Parcelable$Creator *;
        }
      
        # 保留Serializable序列化的类不被混淆
        -keepclassmembers class * implements java.io.Serializable {
            static final long serialVersionUID;
            private static final java.io.ObjectStreamField[] serialPersistentFields;
            !static !transient ;
            !private ;
            !private ;
            private void writeObject(java.io.ObjectOutputStream);
            private void readObject(java.io.ObjectInputStream);
            java.lang.Object writeReplace();
            java.lang.Object readResolve();
        }
      
        # 对于带有回调函数的onXXEvent、**On*Listener的,不能被混淆
        -keepclassmembers class * {
            void *(**On*Event);
            void *(**On*Listener);
        }
      
        # webView处理,项目中没有使用到webView忽略即可
        #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
        #    public *;
        #}
        #-keepclassmembers class * extends android.webkit.webViewClient {
        #    public void *(android.webkit.WebView, java.lang.String, android.graphics.Bitmap);
        #    public boolean *(android.webkit.WebView, java.lang.String);
        #}
        #-keepclassmembers class * extends android.webkit.webViewClient {
        #    public void *(android.webkit.webView, jav.lang.String);
        #}
      
        # 移除Log类打印各个等级日志的代码,打正式包的时候可以做为禁log使用,这里可以作为禁止log打印的功能使用
        # 记得proguard-android.txt中一定不要加-dontoptimize才起作用
        # 另外的一种实现方案是通过BuildConfig.DEBUG的变量来控制
        #-assumenosideeffects class android.util.Log {
        #    public static int v(...);
        #    public static int i(...);
        #    public static int w(...);
        #    public static int d(...);
        #    public static int e(...);
        #
        #############################################
        #
        # 项目中特殊处理部分
        #
        #############################################
      
        #-----------处理反射类---------------
        #-dontwarn aaa.bbb.ccc.model.**
        -keep class aaa.bbb.ccc.model.** {*;}
      
        #-----------处理js交互---------------
      
        #-----------处理实体类---------------
        # 在开发的时候我们可以将所有的实体类放在一个包内,这样我们写一次混淆就行了。
        #-keep class com.ghs.ghspm.bean.** { *; }
      
      
        #-----------处理第三方依赖库---------
        -keep class io.netty.** { *;}
        -dontwarn io.netty.**
        </blockcode>
        </pre>
        </details>
      
      
  • 签名
    • 修改app/build.gradle文件

        signingConfigs {
            release{
                keyAlias 这里填写keyAlias
                keyPassword 这里填写keyPassword
                storeFile file(这里填写xxx.jks的路径)
                storePassword 这里填写storePassword
            }
        }
        buildTypes {
            // 这里debug和release使用同样的配置
            release {
                signingConfig signingConfigs.release
            }
            debug{
                signingConfig signingConfigs.release
            }
        }
      

分离敏感资源

  • 主要是签名文件和相关密码等,可以将其统一放到额外的配置文件中,它不上传的公共云端。
    此处将敏感信息放入了signing.properties
    • app/signing.properties
        KEYSTORE_FILE = C:\\Users\\xxx\\abc.jks
        KEYSTORE_PASSWORD = pwd1
        KEY_ALIAS = alias_xxxxx
        KEY_PASSWORD = pwd2
      
    • app/build.gradle

        // 加载signing.properties 读取配置
        Properties props = new Properties()
        props.load(new FileInputStream(file("signing.properties")))
        android {
            compileSdkVersion 29
            buildToolsVersion "29.0.3"
            defaultConfig {
                applicationId "a.b.c"
                minSdkVersion 24
                targetSdkVersion 29
                versionCode 10
                versionName "1.2.0r"
                testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
            }
            signingConfigs {
                release{
                    keyAlias props['KEY_ALIAS']
                    keyPassword props['KEY_PASSWORD']
                    storeFile file(props['KEYSTORE_FILE'])
                    storePassword props['KEYSTORE_PASSWORD']
                }
            }
            buildTypes {
                release {
                    // ...省略其他配置
                    signingConfig signingConfigs.release
                }
                debug{
                    signingConfig signingConfigs.release
                }
            }
        }
        // ...省略其他配置
      
  • 如何在云端部署signing.propertiesabc.jks
    • signing.properties
      • 机密信息只有KEYSTORE_PASSWORDKEY_ALIASKEY_PASSWORD
      • 我们可以将它们放到Github Repo的secrets里面,然后直接echo写进配置文件
    • abc.jks
      • 我们可以将它上传到自己私密服务器上,通过wget临时下载至Github Actions的机器上。
        将可以实现该功能的单行shell命令放到Github Repo的secrets里面,其名为 BASH_DOWNLOAD_JKS
      • 举个例子,我们可以将它上传到我们Github的私有repo里面,并申请一个有接触权限的token。
        此时,BASH_DOWNLOAD_JKS可以是这样子,里面的变量自己去替换:
        wget https://raw.githubusercontent.com/{user}/{repo}/{branch}/{path}/abc.jks --header="Authorization: token {token}" -O abc.jks
      
    • Github Action 脚本

          - name: Generate signing.properties
            run: |
              rm -rf xxx.jks
              ${{ secrets.BASH_DOWNLOAD_JKS }}
              echo "KEYSTORE_FILE = ${{github.workspace}}/xxx.jks" > app/signing.properties
              echo "KEYSTORE_PASSWORD = ${{ secrets.KEYSTORE_PASSWORD }}" >> app/signing.properties
              echo "KEY_ALIAS = ${{ secrets.KEY_ALIAS }}" >> app/signing.properties
              echo "KEY_PASSWORD = ${{ secrets.KEY_PASSWORD }}" >> app/signing.properties
      

Github Actions 打包并发布

name: android master CI

on:
  push:
    branches: [ master ]
    paths:
    # Trigger only when src/** changes
      - ".github/release.json"

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: 'adopt'

      - name: Read tag_latest
        id: tag_latest
        uses:  ashley-taylor/read-json-property-action@v1.0
        with:
          path: ./.github/release.json
          property: tag_latest

      - name: Read description
        id: description
        uses: juliangruber/read-file-action@v1
        with:
          path: ./.github/release.info

      - name: Generate signing.properties
        run: |
          rm -rf freedom.jks
          ${{ secrets.BASH_DOWNLOAD_JKS }}
          echo "KEYSTORE_FILE = ${{github.workspace}}/freedom.jks" > app/signing.properties
          echo "KEYSTORE_PASSWORD = ${{ secrets.KEYSTORE_PASSWORD }}" >> app/signing.properties
          echo "KEY_ALIAS = ${{ secrets.KEY_ALIAS }}" >> app/signing.properties
          echo "KEY_PASSWORD = ${{ secrets.KEY_PASSWORD }}" >> app/signing.properties
          cat app/signing.properties
      - name: Grant execute permission for gradlew
        run: chmod +x gradlew
      - name: Build with Gradle
        run: ./gradlew assembleRelease
    
      - name: Create Release
        id: create_release
        uses: actions/create-release@latest
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 
        with:
          tag_name: ${{steps.tag_latest.outputs.value}}
          release_name: Freedom - v${{steps.tag_latest.outputs.value}}
          body: |
            ${{steps.description.outputs.content}}
          draft: false
          prerelease: false

      - name: Upload Release Asset
        id: upload-release-asset
        uses: actions/upload-release-asset@v1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          upload_url: ${{ steps.create_release.outputs.upload_url }}
          asset_path: ./app/build/outputs/apk/release/app-release.apk
          asset_name: Freedom.${{steps.tag_latest.outputs.value}}.apk
          asset_content_type: application/vnd.android.package-archive


内容
隐藏