LVL 類別和介面
表 1 列出了 Android SDK 提供的授權驗證庫 (LVL) 中所有來源檔案。所有檔案都是 com.android.vending.licensing
套件的一部分。
類別 | 名稱 | 說明 |
---|---|---|
授權檢查與結果 | LicenseChecker | 為啟動授權檢查而執行個體化 (或子類別化) 的類別。 |
LicenseCheckerCallback | 為處理授權檢查結果而實作的介面。 | |
政策 | 政策 | 為依據授權回應決定是否允許存取應用程式而實作的介面。 |
ServerManagedPolicy | 預設 Policy 實作。透過授權伺服器提供的設定管理授權資料的本機儲存、授權有效性和重試作業。 |
|
StrictPolicy | 替代 Policy 實作。僅根據伺服器的直接授權回應來強制執行授權。不使用快取,也不重試要求。 |
|
資料模糊處理 (選用) |
模糊處理工具 | 使用 Policy (例如 ServerManagedPolicy) 在永久儲存空間中快取授權回應資料時實作的介面。
透過模糊處理演算法對寫入或讀取的資料進行編碼及解碼。 |
AESObfuscator | 預設模糊處理工具實作,使用 AES 加密/解密演算法對資料執行模糊處理/解除模糊處理。 | |
裝置限制 (選用) |
DeviceLimiter | 如果您希望只有特定裝置可以使用應用程式,可以實作這個介面。從 LicenseValidator 呼叫。對於大多數應用程式而言,除非設計時十分謹慎,否則不建議實作 DeviceLimiter,因為 DeviceLimiter 需要使用後端伺服器,而且可能會導致使用者無法存取授權的應用程式。 |
NullDeviceLimiter | 預設 DeviceLimiter 實作,此為一種免人工管理 (允許存取所有裝置)。 | |
程式庫核心,無需整合 | ResponseData | 這個類別包含授權回應欄位。 |
LicenseValidator | 這個類別解密及驗證從授權伺服器收到的回應。 | |
ValidationException | 這個類別表示在驗證模糊處理工具管理的資料完整性時所發生的錯誤。 | |
PreferenceObfuscator | 公用程式類別,用於將經模糊處理的資料寫入系統的 SharedPreferences 儲存空間或從中讀取此類資料。 |
|
ILicensingService | 單向 IPC 介面,用於向 Google Play 用戶端傳遞授權檢查要求。 | |
ILicenseResultListener | 單向 IPC 回呼實作,用於讓應用程式接收來自授權伺服器的非同步回應。 |
伺服器回應
表 2 列出了 授權伺服器
欄位 | 說明 |
---|---|
responseCode |
授權伺服器傳回的回應代碼。回應代碼為 伺服器回應代碼中所述的步驟。 |
signedData |
一個字串串連,保留授權伺服器傳回的資料,如下所示:
responseCode|nonce|packageName|versionCode|userId|timestamp:extras 。
|
signature |
使用應用程式特定金鑰的 signedData 簽章。
|
伺服器回應代碼
表 3 列出了授權伺服器支援的所有授權回應代碼 授權伺服器一般來說,應用程式應處理所有這些回應代碼。根據預設,LVL 中的 LicenseValidator 類別可為您對這些回應代碼執行所有必要處理。
回應代碼 | 整數值 | 說明 | 是否已簽署? | 額外項目 | 注釋 |
---|---|---|---|---|---|
LICENSED |
0 |
使用者已獲得應用程式授權。使用者已購買應用程式,或是獲得授權,可以下載及安裝 Alpha 版或 Beta 版應用程式。 | 是 | VT 、GT 、GR |
依據 Policy 限制條件允許存取。 |
LICENSED_OLD_KEY |
2 |
使用者已獲得應用程式授權,但有透過其他金鑰簽署的應用程式更新版本可使用。 | 是 | VT 、GT 、GR 、UT |
依據 Policy 的限制條件,可以選擇允許存取。
可能表示已安裝的應用程式版本使用的金鑰組無效或遭駭。應用程式可視需要允許存取,或告知使用者有可用的升級,並在升級前限制進一步使用應用程式。 |
NOT_LICENSED |
1 |
使用者尚未獲得應用程式授權。 | 否 | 不允許存取。 | |
ERROR_CONTACTING_SERVER |
257 |
本機錯誤:Google Play 應用程式無法連線至授權伺服器,原因可能是網路可用性問題。 | 否 | 依據 Policy 重試限制條件,再次嘗試授權檢查。 |
|
ERROR_SERVER_FAILURE |
4 |
伺服器錯誤:伺服器無法載入應用程式的授權金鑰組。 | 否 | 依據 Policy 重試限制條件,再次嘗試授權檢查。
|
|
ERROR_INVALID_PACKAGE_NAME |
258 |
本機錯誤:應用程式要求對裝置未安裝的套件進行授權檢查。 | 否 | 請勿再次嘗試授權檢查, 這通常是由開發錯誤引起。 |
|
ERROR_NON_MATCHING_UID |
259 |
本機錯誤:應用程式要求對套件進行授權檢查,而該套件的 UID (套件、使用者 ID 對) 與應用程式的 UID 不符。 | 否 | 請勿再次嘗試授權檢查, 這通常是由開發錯誤引起。 |
|
ERROR_NOT_MARKET_MANAGED |
3 |
伺服器錯誤:Google Play 無法辨識應用程式 (套件名稱)。 | 否 | 請勿再次嘗試授權檢查。 這可能表示應用程式並非透過 Google Play 發布,或實作授權時有開發錯誤。 |
注意:如設定測試環境中所述,回應代碼可由應用程式開發人員和所有已註冊的測試使用者透過 Google Play 管理中心手動覆寫。
注意:您之前可以上傳未發布的「草稿」版本,並藉此測試應用程式,但我們已不再支援這項功能,因此您必須改為將應用程式發布至 Alpha 或 Beta 版發行管道。詳情請參閱不再支援草稿應用程式。
伺服器回應額外項目
為協助應用程式在整個應用程式退款期內管理應用程式的存取權並提供其他資訊,授權伺服器會在授權回應中提供多項資訊。具體來說,這項服務針對應用程式的授權有效期、重試寬限期、允許重試次數上限以及其他設定提供建議值。如果您的應用程式使用 APK 擴充檔案,回應還會包含檔案名稱、大小和網址。伺服器會在授權回應的「額外項目」欄位中附加鍵/值組合形式的設定。
任何 Policy
實作都可以從授權回應擷取額外項目設定,並視需要使用這些設定。LVL 的預設 Policy
實作 (ServerManagedPolicy
) 會做為運作實作使用,並用於說明如何取得、儲存及使用這項設定。
額外項目 | 說明 |
---|---|
VT |
授權有效期時間戳記。指定目前 (快取) 授權回應過期而且必須在授權伺服器上重新檢查的日期/時間。請參閱下文有關授權有效期的部分。 |
GT |
寬限期時間戳記。指定政策可能允許存取應用程式的期限何時結束 (即使回應狀態是 RETRY )。這個值由伺服器管理,但一般值為 5 天或更長時間。請參閱下文有關重試期限和重試次數上限的部分。 |
GR |
重試次數上限。指定在系統拒絕使用者存取應用程式前,Policy 應允許連續執行多少次 RETRY 授權檢查。
這個值由伺服器管理,但一般值為「10」或更高。請參閱下文有關重試期限和重試次數上限的部分。 |
UT |
更新時間戳記。指定上傳與發布這個應用程式最近更新的日期/時間。 伺服器只會為 |
FILE_URL1 或 FILE_URL2 |
擴充檔案的網址 (1 代表主要檔案,2 代表修補型檔案)。使用這個項目就能透過 HTTP 下載檔案。 |
FILE_NAME1 或 FILE_NAME2 |
擴充檔案的名稱 (1 代表主要檔案,2 代表修補型檔案)。在裝置上儲存檔案時,您必須使用這個名稱。 |
FILE_SIZE1 或 FILE_SIZE2 |
以位元組為單位的檔案大小 (1 代表主要檔案,2 代表修補型檔案)。使用這個項目可幫助您下載,並且確保在下載之前裝置的共用儲存位置有足夠的可用空間。 |
授權有效期
Google Play 授權伺服器會為所有下載的應用程式設定授權有效期。這個有效期表示一段時間間隔,期間應用程式的授權狀態應視為在應用程式中未變更,並且可由授權 Policy
快取。授權伺服器會在所有授權檢查的回應中納入有效期,並在鍵 VT
之下以額外項目的形式,將有效期結束時間戳記附加到回應中。Policy
可擷取 VT 鍵值,並使用這個值有條件地允許存取應用程式而無須重新檢查授權,直到有效期到期。
如果授權 Policy
必須透過授權伺服器重新檢查授權狀態,授權有效期會向這個授權發出信號,目的「不是」為了表明應用程式是否已獲得使用授權。也就是說,如果應用程式的授權有效期到期,並不代表應用程式不再具有使用授權,而是表示 Policy
必須透過伺服器重新檢查授權狀態。因此,只要授權有效期未到期,Policy
就可以在本機快取初始授權狀態並傳回快取的授權狀態,而不是向伺服器傳送新的授權檢查。
授權伺服器透過管理有效期,協助應用程式在 Google Play 為付費應用程式提供的退款期內正確執行授權。授權伺服器會依據應用程式是否已被購買 (以及如果已有人購買應用程式,是多久前購買) 來設定有效期。具體來說,伺服器會按照以下做法設定有效期:
- 如果是付費應用程式,伺服器會設定初始授權有效期,如此一來,只要應用程式仍在可退款期內,授權回應就仍會有效。應用程式中的授權
Policy
可以快取初始授權檢查的結果,而且在有效期結束前不需要重新檢查授權。 - 應用程式過了可退款期後,伺服器會設定較長的有效期 (通常為幾天)。
- 如果是免費應用程式,伺服器會將有效期設為非常高的值 (
long.MAX_VALUE
)。這樣可確保只要Policy
在本機快取有效期時間戳記,日後就不需重新檢查應用程式的授權狀態。
ServerManagedPolicy
實作使用擷取的時間戳記 (mValidityTimestamp
) 做為主要條件,藉此判斷是否要先透過伺服器重新檢查授權狀態,再允許使用者存取應用程式。
重試期限和重試次數上限
在某些情況下,系統或網路狀態會讓應用程式的授權檢查無法傳送至授權伺服器,或導致無法將伺服器的回應傳送至 Google Play 用戶端應用程式。例如,使用者可能在沒有行動網路或數據連線 (例如搭乘飛機),或網路連線不穩定/行動網路訊號微弱時啟動應用程式。
當網路問題導致授權檢查無法執行或中斷時,Google Play 用戶端會向 Policy
的 processServerResponse()
方法傳回 RETRY
回應代碼,藉此通知應用程式。如果發生系統問題,例如應用程式無法與 Google Play 的 ILicensingService
實作繫結,LicenseChecker
程式庫會自行使用 RETRY
回應代碼呼叫政策 processServerResponse()
方法。
一般來說,RETRY
回應代碼是向應用程式傳送的信號,表示系統發生錯誤,導致授權檢查無法完成。
Google Play 伺服器透過設定重試「寬限期」和建議的重試次數上限,協助應用程式在發生錯誤時管理授權。伺服器會在所有授權檢查回應中納入這些值,並透過額外項目的形式將這些值附加到鍵 GT
和 GR
之下。
應用程式 Policy
可以擷取 GT
和 GR
額外項目,並使用這些項目有條件地允許存取應用程式,如下所示:
- 如果授權檢查的結果是
RETRY
回應,則Policy
應快取RETRY
回應代碼,並增加一次RETRY
回應的計數。 - 如果重試寬限期仍有效,或者未達到重試次數上限,則
Policy
應允許使用者存取應用程式。
ServerManagedPolicy
使用伺服器提供的上述 GT
和 GR
值。以下範例顯示了 allow()
方法中重試回應的有條件處理。RETRY
回應的計數在 processServerResponse()
方法中保持不變,並且未顯示。
Kotlin
fun allowAccess(): Boolean { val ts = System.currentTimeMillis() return when(lastResponse) { LICENSED -> { // Check if the LICENSED response occurred within the validity timeout. ts <= validityTimestamp // Cached LICENSED response is still valid. } RETRY -> { ts < lastResponseTime + MILLIS_PER_MINUTE && // Only allow access if we are within the retry period // or we haven't used up our max retries. (ts <= retryUntil || retryCount <= maxRetries) } else -> false } }
Java
public boolean allowAccess() { long ts = System.currentTimeMillis(); if (lastResponse == LicenseResponse.LICENSED) { // Check if the LICENSED response occurred within the validity timeout. if (ts <= validityTimestamp) { // Cached LICENSED response is still valid. return true; } } else if (lastResponse == LicenseResponse.RETRY && ts < lastResponseTime + MILLIS_PER_MINUTE) { // Only allow access if we are within the retry period // or we haven't used up our max retries. return (ts <= retryUntil || retryCount <= maxRetries); } return false; }