android 如何使用 command 產生 .apk 檔案

在 Android 開發中,現在大多使用 Gradle 來處理編譯。如果你已經有專案原始碼,可以直接在專案根目錄使用 ./gradlew 指令。這是最常見也最推薦的做法。

使用 Gradle Wrapper 編譯

Gradle Wrapper 是一個腳本。它能確保專案在不同機器上使用相同的 Gradle 版本。打開終端機並切換到專案路徑,執行以下指令:

./gradlew assembleDebug

這個指令會編譯出一個用於測試的 Debug 版本 APK。如果你需要正式上架的版本,則改用:

./gradlew assembleRelease

執行成功後,產出的檔案通常會放在 app/build/outputs/apk/ 目錄下。你會在裡面看到 debug 或 release 資料夾,副檔名就是 .apk。


常用指令與路徑

如果你的環境是 Windows,指令要改成 gradlew.bat assembleDebug。在執行編譯前,建議先清理舊的暫存檔:

./gradlew clean assembleDebug

這樣可以避免舊的檔案影響新的編譯結果。如果你的專案有多個不同的 Product Flavors,你可以用 ./gradlew tasks 查看所有可用的編譯指令。


關於 AAB 格式

現在 Google Play 要求上架必須使用 .aab 格式。如果你是為了上架準備,指令會稍微不同:

./gradlew bundleRelease

這會產生 Android App Bundle 檔案。位置在 app/build/outputs/bundle/release/ 之下。這比傳統的 APK 能提供更小的下載體積。


如果修改了 keystore 的存放路徑,最直接的調整位置是在專案中的 build.gradle 檔案。通常這個檔案位於 app 目錄下。你需要找到 signingConfigs 區塊。

修改 build.gradle 設定

在 signingConfigs 裡面,有一個屬性叫做 storeFile。你需要把後面的路徑指向新的位置。如果路徑是相對路徑,它是以專案根目錄為基準。

android {
    signingConfigs {
        release {
            storeFile file('/新的路徑/my-release-key.jks')
            storePassword '你的密碼'
            keyAlias '你的別名'
            keyPassword '你的密碼'
        }
    }
}

建議把路徑寫成相對路徑。例如 file(‘../keystores/release.jks’)。這樣換一台電腦開發時,只要相對位置正確,就不會出錯。


使用 gradle.properties 管理

為了安全與方便,很多人會把路徑寫在 gradle.properties 檔案裡。這樣就不會把私密路徑寫死在程式碼。你可以打開 gradle.properties 調整變數值。

MYAPP_RELEASE_STORE_FILE=/新的路徑/my-release-key.jks

然後在 build.gradle 引用這個變數。改完後記得執行一次 ./gradlew clean。這能確保 Gradle 抓到最新的路徑設定。


檢查環境變數

有些團隊會把路徑放在系統環境變數。如果你的設定是透過 System.getenv 取得。那你就要去修改作業系統的環境設定。修改完後可能需要重啟終端機或 IDE 才會生效。


build.gradle 引用 gradle.properties 的變數很簡單。Gradle 會自動將 properties 檔案中的屬性載入。

引用變數的方式

假設你在 gradle.properties 設定了變數名為 MYAPP_RELEASE_STORE_FILE。在 app/build.gradle 裡,你可以直接像這樣寫:

android {
    signingConfigs {
        release {
            // 直接使用變數名稱即可
            storeFile file(MYAPP_RELEASE_STORE_FILE)
            storePassword MYAPP_RELEASE_STORE_PASSWORD
            keyAlias MYAPP_RELEASE_KEY_ALIAS
            keyPassword MYAPP_RELEASE_KEY_PASSWORD
        }
    }
}

使用專案屬性檢查

有時候為了保險,會建議使用 project.hasProperty 來檢查變數是否存在。這樣即使變數沒設定,編譯也不會直接噴錯。這在多人協作時很好用。

Gradle

storeFile file(project.hasProperty('MYAPP_RELEASE_STORE_FILE') ? MYAPP_RELEASE_STORE_FILE : "default_path.jks")

關於路徑的提醒

如果你在變數中寫的是相對路徑,它是相對於專案根目錄。如果你希望路徑更靈活,可以搭配 rootDir 變數來組合:

Gradle

storeFile file("${rootDir}/keystores/${MYAPP_RELEASE_STORE_FILE}")

這樣無論在哪個層級的 gradle 檔引用,路徑都不會跑掉。改完設定後,建議執行 ./gradlew assembleRelease 測試看看能不能成功讀取。


看到 unsigned 代表這個 APK 還沒有經過數位簽署。Android 系統出於安全考慮,不會允許安裝任何未簽署的 APK。這通常是因為你的編譯指令或設定沒有正確關聯到簽署設定。

檢查 build.gradle 的類型設定

build.gradle 中,你雖然定義了 signingConfigs,但還必須在 buildTypes 裡面告訴 Gradle,release 版本要使用哪一組簽署設定。

android {
    buildTypes {
        release {
            // 這一行是關鍵,必須手動指定
            signingConfig signingConfigs.release 
            
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

如果漏掉了 signingConfig signingConfigs.release 這行,Gradle 就會預設產生不帶簽署的 APK,檔名就會出現 unsigned。


檢查簽署內容是否完整

如果設定已經寫了,但產出還是 unsigned,請檢查 gradle.propertiessigningConfigs 區塊中的資訊是否齊全。簽署需要這四個要素:

  • storeFile: keystore 檔案的路徑。
  • storePassword: 儲存庫的密碼。
  • keyAlias: 密鑰的別名。
  • keyPassword: 該密鑰的獨立密碼。

只要缺一個,Gradle 有時候會因為找不到資訊而跳過簽署步驟,直接產出 unsigned 版本。


清除快取重試

有時候是之前的編譯產物殘留,導致你看到的是舊檔案。調整完設定後,請務必先執行清理指令再編譯:

./gradlew clean assembleRelease

如果設定正確,生成的檔名應該會變成 app-release.apk

你需要我幫你檢查 signingConfigsbuildTypes 的對應程式碼嗎?


error message:

gradlew clean assembleRelease

* What went wrong:
A problem occurred evaluating project ':app'.
> Could not get unknown property 'release' for SigningConfig container of type org.gradle.api.internal.FactoryNamedDomainObjectContainer.

這個錯誤訊息代表你在 buildTypes 裡面引用了一個還沒定義的 signingConfigs 名稱。Gradle 的執行順序是由上而下,如果你先在 buildTypes 寫了 signingConfig signingConfigs.release,但當時 release 這個設定還沒被建立,就會報錯。

調整 build.gradle 的順序

在 Gradle 腳本中,定義的順序非常重要。你必須確保 signingConfigs 區塊寫在 buildTypes 之前。請檢查你的 app/build.gradle 結構是否如下:

android {
    // 1. 先定義簽署設定
    signingConfigs {
        release {
            storeFile file(MYAPP_RELEASE_STORE_FILE)
            storePassword MYAPP_RELEASE_STORE_PASSWORD
            keyAlias MYAPP_RELEASE_KEY_ALIAS
            keyPassword MYAPP_RELEASE_KEY_PASSWORD
        }
    }

    buildTypes {
        release {
            // 2. 這裡才能引用上面定義好的 release
            signingConfig signingConfigs.release
            
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-bundle.txt'), 'proguard-rules.pro'
        }
    }
}

常見的命名陷阱

錯誤訊息提到的 Could not get unknown property 'release',有時候是因為你在 signingConfigs 裡面把名稱取錯了。例如:

  • 如果你在 signingConfigs 裡寫的是 myConfig { ... }
  • 那在 buildTypes 就必須寫 signingConfig signingConfigs.myConfig

兩邊的名稱必須完全對應。請確認你的 signingConfigs 括號內確實有名為 release 的區塊。


暫時註解法排查

如果你想確認是不是路徑變數導致的問題,可以先試著在 buildTypes 裡把 signingConfig signingConfigs.release 這一行註解掉。如果編譯能過(雖然產出又是 unsigned),那就代表問題百分之百出在簽署區塊的定義。

修正順序後,再執行一次編譯試試看。

你需要我幫你看一下你目前 build.gradle 第 20 行附近的程式碼長什麼樣子嗎?

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *