應用場景
當應用程式超過50M時,可以采用谷歌官方提供的APK Expansion Files.在Market控制台上傳App時,可将多餘的資料添加為擴充檔案.
請注意:每個APK最多隻能有兩個擴充檔案(Expansion File).這兩個擴充檔案通常被命名為main和patch.
main是擴充檔案包含核心資料,并且盡量不随程式版本的更新去修改;而patch擴充檔案可以随程式版本的更新做修改.
擴充檔案(Expansion File)的命名格式
擴充檔案可以使用任何檔案格式(ZIP,PDF,MP4等).常用的為ZIP格式.
但不管任何檔案格式 Google Play都認為他們是obb(opaque binary blobs)檔案.
命名格式:
[main|patch].<expansion-version>.<package-name>.obb
各字段意義如下描述
[main|patch]:指定檔案是main擴充檔案還是patch擴充檔案,每個APK隻能有一個main擴充檔案和一個patch擴充檔案
<expansion-version>:與第一次上傳該擴充檔案的APK檔案的android:versionCode一緻
<package-name>:包名
示例如下:
程式的版本為5,包名為org.goodev.expansion.downloader則上傳的main擴充檔案會被重命名為:
main.5.org.goodev.expansion.downloader.obb
擴充檔案(Expansion File)的儲存路徑
從Android Market下載下傳程式的擴充檔案時會将其儲存到裝置的共享存儲區.
為了確定程式正常運作請勿随意删除,移動或者重命名擴充檔案.
請注意:
在大多數情況下,Market會在下載下傳APK的同時去下載下傳擴充檔案.
但有時Market無法下載下傳擴充檔案或者使用者删除了以前下載下傳的擴充檔案.
是以當啟動APP時可檢測檔案是否存在,若不存在則從Market下載下傳且儲存到同樣的路徑.
路徑格式:
<shared-storage>/Android/obb/<package-name>/
<shared-storage>:通過getExternalStorageDirectory()擷取即可.
關于ZIP的使用,請注意:
若需要解壓縮擴充檔案來使用,請勿删除該.obb檔案,也不要把檔案解壓縮到該目錄.
您應該把解壓縮後的檔案儲存到getExternalFilesDir()傳回的目錄下面.
在此推薦使用Android開發團隊提供的一個項目(APK Expansion Zip Library)可直接讀取ZIP檔案中的内容而不用解壓縮該檔案.
擴充檔案(Expansion File)的規則和限制
1.每個擴充檔案最大為2GB
2.使用者必需要從Android Market擷取您的程式才能自動從Market中下載下傳擴充檔案
3.當在您的程式中下載下傳擴充檔案的時候,Market每次都會為每個檔案生成一個唯一的下載下傳URL,該URL會在短期内失效
4.當你上傳一個新的APK的時候,可選擇使用以前上傳的擴充檔案
5.如果您使用多個APK檔案來适配不同的裝置,并且也希望使用多個擴充檔案。
為了擷取一個唯一的versionCode和不同的Market filter您需要分别為每個裝置上傳不同的APK檔案
6.不能通過更新擴充檔案來釋出一個新的版本
7.勿在obb/檔案夾中儲存其他資料
8.勿删除或者重命名.obb檔案
下載下傳擴充檔案(Expansion File)
為簡化下載下傳擴充檔案的操作,提供以下工程作為技術支援:
1 Market Downloader Library
2 Market Downloader Sample
3 Market License Library
實作下載下傳服務(Implementing the downloader service)
在Market Downloader Library包下有一個類DownloaderService,我們的業務類需要繼承自該類來實作下載下傳.
在DownloaderService提供了如下的功能:
1 注冊一個BroadcastReceiver來監聽裝置的網絡連接配接狀态的改變.
如果網絡連接配接斷開就暫停下載下傳,如果網絡連接配接恢複就繼續下載下傳
2 安排一個RTC_WAKEUP通知(alarm),當下載下傳服務被終結的時候可以通過該通知來啟動重新下載下傳服務
3 生成一個通知(Notification)來顯示下載下傳的進度以及下載下傳錯誤等狀态
4 允許您的程式手工實作暫停和恢複下載下傳
5 檢測SDCarcd,且可在下載下傳檔案之前檢測檔案是否已經存在,存儲空間是否足夠.
若如果出現問題則通知使用者
當我們的業務類繼承該類需要實作以下方法:
1 getPublicKey():您Market賬号的Base64編碼RSA公共密鑰.
該資料從Developer Console擷取即可.
2 getSALT():用于生成混淆器(Obfuscator)的一組随機bytes
3 getAlarmReceiverClassName():得到alarm receiver類的名稱.
實作alarm receiver(BroadcastReceiver)
為檢測下載下傳程序和重新開機下載下傳服務,DownloaderService會安排一個RTC_WAKEUP類型的Alarm來發送
一個Intent(即Market Downloader Library包下DownloaderService類的PendingIntent mAlarmIntent)到App的BroadcastReceiver.
是以,我們應定義這個BroadcastReceiver來調用Market Downloader Library提供的函數
進而監測下載下傳狀态和在必要的情況下重新開機下載下傳服務.
實作這個alarm receiver是比較非常簡單的,繼承自BroadcastReceiver并在覆寫的onReceive()裡調用DownloaderClientMarshaller.startDownloadServiceIfRequired()函數即可
同時,我們可以注意到,這個類的名字就是前面提到的getAlarmReceiverClassName()的傳回值.
開始下載下傳擴充檔案(Expansion File)
1.檢查檔案是否已經下載下傳了
利用Downloader Library中的Helper類的兩個方法即可判斷:
Helpers.getExtendedAPKFileName(Context, c, boolean mainFile, int versionCode)
Helpers.doesFileExist(Context c, String fileName, long fileSize)
詳細代碼可參見SampleDownloaderActivity中的expansionFilesDeliveredexpansionFilesDelivered()方法
2 檔案不存在時下載下傳,方法如下
int startResult=DownloaderClientMarshaller.startDownloadServiceIfRequired(Context context, PendingIntent notificationClient, Class<?> serviceClass)
注意方法的參數:
Context context:應用程式的Context
PendingIntent notificationClient:點選通知欄中的通知後跳轉到的Activity(通常和開始下載下傳的界面一樣)
Class<?> serviceClass:繼承自Market Downloader Library包下DownloaderService的業務Service
詳細代碼可參見SampleDownloaderActivity中的onCreate()方法
3 判斷在上一步傳回的startResult
startResult表示:
whether the service was started and the reason for starting the service.表示是否有必要下載下傳檔案
傳回值有:NO_DOWNLOAD_REQUIRED,LVL_CHECK_REQUIRED,DOWNLOAD_REQUIRED.
NO_DOWNLOAD_REQUIRED:表示檔案已經存在或者目前正在下載下傳
LVL_CHECK_REQUIRED:表示需要授權驗證來擷取下載下傳擴充檔案的URL
DOWNLOAD_REQUIRED:表示擴充檔案的URL已經擷取到了,但還未開始下載下傳
通常我們隻需要關注結果是否是NO_DOWNLOAD_REQUIRED即可:
若傳回值不是NO_DOWNLOAD_REQUIRED,那麼采用Downloader Library開始啟動下載下傳,此時我們應該更新程式界面來顯示下載下傳進度
若傳回值是NO_DOWNLOAD_REQUIRED,表明該檔案已經下載下傳好,程式可正常啟動
4 若傳回值不是NO_DOWNLOAD_REQUIRED,那麼采用Downloader Library開始啟動下載下傳
DownloaderClientMarshaller.CreateStub(IDownloaderClient client, ClassdownloaderService)函數來建立一個IStub執行個體.
該IStub執行個體提供了Activity和下載下傳服務之間的綁定功能,這樣Activity就可以接收到下載下傳事件了
對于該IStub執行個體,我們onResume()在應該調用IStub的connect();同樣在onStop()函數中調用IStub的disconnect()
處理下載下傳進度
為了接收和處理下載下傳進度資訊,Activity需要實作 Downloader Library下的IDownloaderClient接口
我們需要覆寫該接口的以下方法:
onServiceConnected(Messenger m)
在初始化完IStub後,會回調該函數
該函數的參數用來通路DownloaderService的
通過 DownloaderServiceMarshaller.CreateProxy()函數來建立這個IDownloaderService對象
然後可以用這個對象來控制下載下傳服務,比如暫停,繼續下載下傳等。
onDownloadStateChanged(int newState)
當下載下傳狀态發生變化的時候調用該函數,例如開始下載下傳或者下載下傳完成
參數newState的值是IDownloaderClient接口中定義的一些常量(以 STATE_ 開頭的);
可以通過函數Helpers.getDownloaderStringResourceIDFromState()來擷取一個狀态的文本描述,這樣使用者更容易了解
例如 STATE_PAUSED_ROAMING 對應的文本描述是:"Download paused because you are roaming"即:目前在漫遊狀态,下載下傳停止
onDownloadProgress(DownloadProgressInfo progress)
該函數的參數DownloadProgressInfo包含了下載下傳進度的各種資訊.
例如:預計完成時間,目前下載下傳速度,完成的百分比等;可以根據該資訊來更新下載下傳界面
requestPauseDownload()
暫停下載下傳
requestContinueDownload()
恢複下載下傳
setDownloadFlags(int flags)
設定下載下傳的網絡标示
目前隻支援一個标示:FLAGS_DOWNLOAD_OVER_CELLULAR
通過移動網絡下載下傳擴充檔案.
預設情況下該标示沒有啟用,是以預設情況下隻通過WIFI下載下傳!!!!!!
擷取擴充檔案(Expansion File)路徑
參見Market Downloader Zip包下APKExpansionSupport類中的方法:
getAPKExpansionFiles(Context ctx, int mainVersion, int patchVersion)
使用Expansion Zip Library
Expansion Zip Library項目包含了對ZIP檔案的處理
我們可以通過該項目提供的函數來直接讀取ZIP檔案内容而不用解壓縮擴充檔案.
在該項目下包含了三個類APKExpansionSupport和ZipResourceFile以及APEZProvider
以下分别介紹:
APKExpansionSupport類
提供一些函數來通路擴充檔案名稱和ZIP檔案
主要方法:
getAPKExpansionFiles()
傳回擴充檔案的檔案路徑
getAPKExpansionZipFile(Context ctx, int mainVersion, int patchVersion)
傳回一個包含main擴充檔案和patch擴充檔案的ZipResourceFile
若同時提供了mainVersion和patchVersion則該函數傳回main和patch擴充檔案的所有内容.
若patch中的内容和main中的有重複,則使用patch的内容覆寫main中的内容
ZipResourceFile類
用來處理ZIP檔案的類
getInputStream(String assetPath)
讀取ZIP檔案中的具體檔案,參數assetPath是相對于ZIP檔案的路徑資訊.
getAssetFileDescriptor(String assetPath)
擷取ZIP檔案中具體檔案的AssetFileDescriptor資訊.
APEZProvider類
一般情況下不使用該類.
暫無
讀取本地ZIP檔案
1 讀取main和patch
// Get a ZipResourceFile representing a merger of both the main and patch files
ZipResourceFile expansionFile = APKExpansionSupport.getAPKExpansionZipFile(appContext,mainVersion, patchVersion);
// Get an input stream for a known file inside the expansion file ZIPs
InputStream fileStream = expansionFile.getInputStream(pathToFileInsideZip);
2 讀取單一檔案
// Get a ZipResourceFile representing a specific expansion file
ZipResourceFile expansionFile = new ZipResourceFile(filePathToMyZip);
測試讀取檔案
測試下載下傳檔案
AndroidManifest.xml配置資訊
權限設定:
<!-- Required to access Android Market Licensing -->
<uses-permission android:name="com.android.vending.CHECK_LICENSE" />
<!-- Required to download files from Android Market -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- Required to keep CPU alive while downloading files (NOT to keep screen awake) -->
<uses-permission android:name="android.permission.WAKE_LOCK" />
<!-- Required to poll the state of the network connection and respond to changes -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!-- Required to check whether Wi-Fi is enabled -->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<!-- Required to read and write the expansion files on shared storage -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
注冊元件:
<service android:name="org.goodev.expansion.downloader.SampleDownloaderService" />
<receiver android:name="org.goodev.expansion.downloader.SampleAlarmReceiver" />
參考資料:
<a target="_blank" href="http://developer.android.com/google/play/expansion-files.html">http://developer.android.com/google/play/expansion-files.html</a>
<a target="_blank" href="http://blog.chengyunfeng.com/?p=342">http://blog.chengyunfeng.com/?p=342</a>
<a target="_blank" href="http://yunfeng.sinaapp.com/?p=343#ixzz1oKclZQjT">http://yunfeng.sinaapp.com/?p=343#ixzz1oKclZQjT</a>
<a target="_blank" href="http://blog.csdn.net/tonyfield/article/details/11739035">http://blog.csdn.net/tonyfield/article/details/11739035</a>
<a target="_blank" href="http://android-developers.blogspot.com/2012/03/android-apps-break-50mb-barrier.html">http://android-developers.blogspot.com/2012/03/android-apps-break-50mb-barrier.html</a>
<a target="_blank" href="http://www.iteye.com/news/24446">http://www.iteye.com/news/24446</a>
Thank you very much