Downloader Library 的使用範例如下
Declaring user permissions
AndroidManifest.xml 宣告如下:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" <uses-sdk <!-- Required to access Google Play Licensing --> <!-- Required to download files from Google Play --> <!-- Required to keep CPU alive while downloading files (NOT to keep screen awake) --> <!-- Required to poll the state of the network connection and respond to changes --> <!-- Required to check whether Wi-Fi is enabled --> <!-- Required to read and write the expansion files on shared storage --> <application </manifest> |
Implementing the downloader service
你必須實作一個 Service 去繼承 DownloaderService,並覆寫以下三個 method:
- getPublicKey():回傳一組 Base64-encoded RSA public key,可以從你的 Google Play Developer Console Publisher 帳號中取得。
- getSALT():回傳一組 rondom bytes array,供 licensing Policy 使用。
- getAlarmReceiverClassName():回傳自定義的 BoradcastReceiver 的 Class Name,用以處理異常情形發生時作錯誤處理。
SampleDownloaderService.java 內容如下:
package com.test.playapkexpansiontest; import com.google.android.vending.expansion.downloader.impl.DownloaderService; public class SampleDownloaderService extends DownloaderService { // stuff for LVL -- MODIFY FOR YOUR APPLICATION! // used by the preference obfuscater
@Override } |
Implementing the alarm receiver
為了去監控 downloader process 與必要時重啟 download process,DownloaderService 會去排程 RTC_WAKEUP alarm 發出 intent 通知你的程式,因此你必須實作一個 BroadcastReceiver ,並覆寫 onReceive() 去呼叫 Downloader Library 的 DownloaderClientMarshaller.startDownloadServiceIfRequired() API。
SampleAlarmReceiver.java 內容如下:
package com.test.playapkexpansiontest; import com.google.android.vending.expansion.downloader.DownloaderClientMarshaller; import android.content.BroadcastReceiver;
@Override } |
Starting the download
你的 Main Activity 必須去驗證是否 expansion file 有在 device 上,如果沒有則啟動 download 程序。
啟動 download 程序的步驟如下:
- 檢查 expansion file 是否有正確被下載
使用 Downloader Library 的 Helper class 所提供的 function
- getExpansionAPKFileName(Context, c, boolean mainFile, int versionCode)
- doesFileExist(Context c, String fileName, long fileSize) - 若檢查後發現 expansion file 下載失敗,則啟動 download process
呼叫 DownloaderClientMarshaller.startDownloadServiceIfRequired(Context c, PendingIntent notificationClient, Class<?> serviceClass),傳入參數如下:
- context:你程式的 Context。
- notificationClient:一個可啟動你程式的 PendingIntent 。
- serviceClass:你實作 DownloaderService 的 class object。
呼叫此 function 後會回傳是否 download 需要被啟動,可能的回傳值如下:
- NO_DOWNLOAD_REQUIRED:expansion file 已經存在,或是下載程序正在執行中。
- LVL_CHECK_REQUIRED:進行 LVL 驗證以取得 expansion file 的 URL。
- DOWNLOAD_REQUIRED:URL 已經取得,但還沒開始下載 expansion file。 - 若 startDownloadServiceIfRequired 傳回的不是 NO_DOWNLOAD_REQUIRED,則 Downloader Library 會開始進行下載程序,你必須更新你的 UI 已顯示目前下載狀況。
呼叫 DownloaderClientMarshaller.CreateStub(IDownloaderClient client, Class<?> downloaderService) 去建立一個 IStub,已取得 Downloader Library 的 cllbacks。
建議在 Main Thread 的 onCreate() 中在下載程序開始之後馬上呼叫 CreateStub(),並在 onResume() 呼叫 connect(),最後在 onStop() 呼叫 disconnect()。
Receiveing download process
你必須實作 Downloader Library 提供的 IDownloaderClient interface 去取得 download process 的更新或與之互動。需要的 interface method 如下:
- onServiceConnected(Messenger m):當與 download process 連結建立之後,此 function 會被呼叫,此時可以從參數中得到 DownloaderService 的 instance。
你可以透過該 instance 呼叫以下 function 以控制下載程序:
- requestPauseDownload():暫停下載程序。
- requestContinueDownload():恢復下載程序。
- setDownloadFlags(int flags):設定那些 Network Type 可以允許下載檔案,預設 user 只能透過 Wi-Fi 下載檔案,若你欲讓使用者也能透過手機網路下載,須加上 FLAGS_DOWNLOAD_OVER_CELLULAR 這個 FLAG。 - onDownloadStateChanged(int newState):當 download state 改變時,此 function 會被呼叫。
- onDownloadProgress(DownloadProgressInfo progress):download process 呼叫此 function,將 DownloadProgressInfo 傳遞出來,你可藉著裡面的資訊更新 UI。
activity_main.xml 內容如下:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout <TextView android:id="@+id/statusText" <LinearLayout android:id="@+id/downloaderDashboard" <RelativeLayout <TextView android:id="@+id/progressAsFraction" <TextView <ProgressBar android:id="@+id/progressBar" <TextView android:id="@+id/progressAverageSpeed" <TextView android:id="@+id/progressTimeRemaining" </RelativeLayout> <LinearLayout <Button android:id="@+id/pauseButton" </LinearLayout> |
MainActivity.java 內容如下:
import com.android.vending.expansion.zipfile.ZipResourceFile; import android.app.Activity; import java.io.DataInputStream; public class MainActivity extends Activity implements IDownloaderClient { private TextView mStatusText; private View mDashboard; private Button mPauseButton; private boolean mStatePaused; private IDownloaderService mRemoteService; private IStub mDownloaderClientStub; static private final float SMOOTHING_FACTOR = 0.005f; private boolean mCancelValidation; private void setState(int newState) { private void setButtonPausedState(boolean paused) { private static class XAPKFile { XAPKFile(boolean isMain, int fileVersion, long fileSize) { private static final XAPKFile[] xAPKS = {
void validateXAPKZipFiles() { @Override mPauseButton.setText(R.string.text_button_cancel_verify); @Override if (!Helpers.doesFileExist(MainActivity.this, fileName, xf.mFileSize, false)) fileName = Helpers.generateSaveFileName(MainActivity.this, fileName); try { for (ZipEntryRO entry : entries) { float averageVerifySpeed = 0; for (ZipEntryRO entry : entries) { try { long startTime = SystemClock.uptimeMillis(); while (length > 0) { if (timePassed > 0) { if (0 != averageVerifySpeed) { totalBytesRemaining -= seek; this.publishProgress( startTime = currentTime; if (mCancelValidation) if (crc.getValue() != entry.mCRC32) { return true; @Override @Override @Override }; validationTask.execute(new Object()); mPB = (ProgressBar) findViewById(R.id.progressBar); mPauseButton.setOnClickListener(new View.OnClickListener() { mWiFiSettingsButton.setOnClickListener(new View.OnClickListener() { @Override Button resumeOnCell = (Button) findViewById(R.id.resumeOverCellular); @Override } @Override initializeDownloadUI(); if (!expansionFilesDelivered()) { try { if (launchIntent.getCategories() != null) { PendingIntent pendingIntent = PendingIntent.getActivity( if (startResult != DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED) { } else { } @Override @Override switch (newState) { case IDownloaderClient.STATE_FAILED_CANCELED: case IDownloaderClient.STATE_PAUSED_BY_REQUEST: int newDashboardVisibility = showDashboard ? View.VISIBLE : View.GONE; if (mDashboard.getVisibility() != newDashboardVisibility) { int cellMessageVisibility = showCellMessage ? View.VISIBLE : View.GONE; if (mCellMessage.getVisibility() != cellMessageVisibility) { mPB.setIndeterminate(indeterminate); @Override progress.mOverallTotal = progress.mOverallTotal; @Override |