google play上傳apk大小受阻問題
ggp對APK包做了50M的上傳限制,如果超過50M的話就要上傳擴充套件包
一、APK擴充套件檔案基本知識Android Market (Google Play Store)中每個APK檔案的最大限制是50MB。如果您的程式中包含大量的資料檔案,以前您只能把這些資料檔案放到自己的伺服器上,當用戶啟動程式的時候讓使用者去下載。現在這些資料檔案可以直接上傳到Android Market了。在新的Market控制檯上傳App的時候,可以新增擴充套件檔案了。
下面就來看看什麼時候該使用擴充套件檔案,該如何使用?
每個APK可以有2個擴充套件檔案,每個檔案最大限制是2GB。為了減少使用者的頻寬消耗,最好使用壓縮格式檔案吧。 這兩擴充套件檔案具有不同的用途:
第一個被稱為 main (主)擴充套件檔案,該擴充套件檔案保護您程式中需要用到的附加資料;
第二個被稱為 patch 擴充套件(修補)檔案,該檔案是可選的,並且應該只包含一些不同版本的補丁資料。
當然您可以按照您需要的方式來使用這兩個擴充套件檔案,不過Android官方還是推薦把這兩個檔案的功能分開。main擴充套件檔案包含核心資料,並且儘量不隨程式版本的升級去修改;而patch擴充套件檔案可以隨程式版本的升級做修改。為了幫助大家理解具體的含義,我們使用一個地圖App來解釋下:
比如 Google 地圖程式需要包含一個離線地圖資料包,這樣可以方便使用者離線檢視地圖,在程式釋出的時候,可以把現有的離線資料包作為main擴充套件檔案上傳到Market。 然後過了半年Google地圖更新了,新添加了一些剛剛修好的高速公路、新建立的商場 等資訊,可以把這些新增的資訊作為patch擴充套件檔案使用。 這樣Google 地圖 1.0版本對應一個main擴充套件檔案;而Google地圖1.1版本對應一個main擴充套件檔案和一個1.1版本的patch擴充套件檔案;Google地圖1.2版本對應一個main擴充套件檔案和一個1.2版本的patch擴充套件檔案。
這裡面的main擴充套件檔案是同一個檔案而patch擴檔案是隨版本變化的。
這樣的好處就是當程序升級的時候, 使用者不用重新下載main擴充套件檔案了,只需要下載少量的新增檔案即可,節省使用者流量。
二、擴充套件檔案的命名格式
擴充套件檔案可以使用任何檔案格式(ZIP, PDF, MP4, 等)。不管任何檔案格式Android都認為他們是obb(opaque binary blobs)檔案,並且會根據如下檔案命名規則來重新命名擴充套件檔案:
[main|patch].<expansion-version>.<package-name>.obb
main or patch
指定檔案是main擴充套件檔案還是patch擴充套件檔案,每個APK只能有一個main擴充套件檔案和一個patch擴充套件檔案。
<expansion-version>
和第一次上傳該擴充套件檔案的APK檔案的android:versionCode一致。後續版本的APK可以重用前面上傳的擴充套件檔案。
您程式的Java包名
<package-name>
例如程式的版本為5,程式的包名為org.goodev.expansion.downloader。則上傳的main擴充套件檔案會被重新命名為:
main.5.org.goodev.expansion.downloader.obb
三、擴充套件檔案的儲存位置
當Android Market下載程式的擴充套件檔案的時候會儲存到系統的共享儲存區。為了確保程式正常執行,您不能刪除、移動或者重新命名擴充套件檔案。在某些裝置上Market無法自動下載該擴充套件檔案,那麼您應該在程式啟動的時候去下載該檔案並且儲存到同樣的位置。
擴充套件檔案儲存位置如下:
<shared-storage>/Android/obb/<package-name>/
<shared-storage> 代表共享檔案的目錄路徑,通過函式getExternalStorageDirectory()獲取;
<package-name> APK的Java包名。
對於每個App而言,該目錄下最多隻能包含2個擴充套件檔案。一個是main擴充套件檔案另外一個是patch擴充套件檔案。當更新程式的時候,如果有新的擴充套件檔案則新檔案會覆蓋舊的擴充套件檔案。
如果您需要解壓縮擴充套件檔案來使用,請注意不要刪除該.obb檔案,並且也不要把檔案解壓縮到該目錄。您應該把解壓縮後的檔案儲存到getExternalFilesDir()返回的目錄下面。如果有可能的話,最好使用程式能直接讀取的檔案格式而不用再次解壓縮檔案了。Android開發團隊提供了一個專案(
APK Expansion Zip Library)可以直接讀取ZIP檔案中的內容而不用解壓縮該檔案.
需要注意的是:儲存在系統共享儲存區的檔案,使用者和其他APP也可以訪問。
四、下載擴充套件檔案的流程
在大多數情況下,Market會在下載APK的同時去下載擴充套件檔案。然而,在某些情況下Market無法下載擴充套件檔案或者使用者刪除了以前下載的擴充套件檔案,您的程式需要處理這種異常情況。當您的程式啟動的時候,可以檢測檔案是否存在並且可以從Market上下載。
開發者檢查清單:
您可以通過下面的清單來檢查是否需要使用擴充套件檔案
- 1. 您的程式是否真的需要超過50MB的大小限制。在移動裝置上空間是非常寶貴的,您應該儘可能減少App的大小。如果您僅僅是為了提供支援多種顯示裝置的圖片資源的話,可以考慮使用釋出多個APK的方式來減少每個APK的大小。
- 2.判斷哪些資料需要打包為擴充套件檔案釋出。
- 3.在程式中新增訪問共享儲存區中擴充套件檔案的程式碼
- 4.在程式的啟動Activity中新增檢測擴充套件檔案是否存在,以及下載擴充套件檔案的程式碼
五、擴充套件檔案的規則和限制
- 1.每個擴充套件檔案最大為2GB
- 2.使用者必需要從Android Market獲取您的程式才能自動從Market中下載擴充套件檔案
- 3.當在您的程式中下載擴充套件檔案的時候,Market每次都會為每個檔案生成一個唯一的下載URL,該URL會在短期內失效。
- 4.當你上傳一個新的APK的時候,可以選擇使用以前上傳的擴充套件檔案
- 5.如果您使用多個APK檔案來適配不同的裝置,並且也希望使用多個擴充套件檔案。為了獲取一個唯一的versionCode和不同的Market filter, 您需要分別為每個裝置上傳不同的APK檔案。
- 6. 不能通過更新擴充套件檔案來發佈一個新的版本。
- 7. 不要在obb/資料夾中儲存其他資料
- 8.不要刪除或者重新命名.obb檔案
要在App中使用擴充套件檔案,需要兩個附加的Android庫專案:
1)Google Market Licensing package
2)Google Market APK Expansion Library package
可以通過Android SDK Manager來下載,也可以直接通過如下連結下載:
https://dl-ssl.google.com/android/repository/market_licensing-r02.zip
https://dl-ssl.google.com/android/repository/market_apk_expansion-r01.zip
1. 宣告需要的許可權
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<manifest...> <!-- Required to access Android Market Licensing --> <uses-permissionandroid:name="com.android.vending.CHECK_LICENSE"/> <!-- Required to download files from Android Market --> <uses-permissionandroid:name="android.permission.INTERNET"/> <!-- Required to keep CPU alive while downloading files (NOT to keep screen awake) --> <uses-permissionandroid:name="android.permission.WAKE_LOCK"/> <!-- Required to poll the state of the network connection and respond to changes --> <uses-permissionandroid:name="android.permission.ACCESS_NETWORK_STATE"/> <!-- Required to check whether Wi-Fi is enabled --> <uses-permissionandroid:name="android.permission.ACCESS_WIFI_STATE"/> <!-- Required to read and write the expansion files on shared storage --> <uses-permissionandroid:name="android.permission.WRITE_EXTERNAL_STORAGE"/> ... </manifest> |
注意:預設情況下,下載庫專案需要的API level為4 而APK擴充套件ZIP庫專案需要API level為5.
準備工作完成後,下面來具體看看如何使用擴充套件檔案。
2. 實現下載服務(Implementing the downloader service)
為了實現在後臺下載檔案,下載庫專案提供了一個Service實現,名稱為DownloaderService。您應該繼承自這個檔案來實現您的下載服務。為了簡化下載服務的開發,該DownloaderService還實現瞭如下功能:
- 註冊一個BroadcastReceiver來監聽裝置的網路連線狀態的改變。如果網路連線斷開就暫停下載;如果網路連線恢復就繼續下載。
- 安排一個 RTC_WAKEUP 通知,當下載服務被終結的時候可以通過該通知來啟動下載服務
- 生成一個通知(Notification )來顯示下載的進度以及下載錯誤等狀態
- 允許您的程式手工的暫停和恢復下載
- 檢測共享儲存區掛載了並且可用,在下載檔案之前檢測 檔案是否已經存在、儲存空間是否足夠。如果出現問題就通知使用者。
getPublicKey():您Market賬號的 Base64 編碼 RSA 公共金鑰,可以通過如下網址獲取:
https://market.android.com/publish/Home#ProfileEditorPlace:
getSALT(): 許可策略用來生成混淆器(Obfuscator)的一組隨機bytes。
getAlarmReceiverClassName(): 返回您程式中用來重啟下載程序的BroadcastReceiver類名稱。當某些情況下,下載服務被意外終止的時候通過該BroadcastReceiver類來重新下載。比如 程序管理的程式終止了下載服務。
下面是一個DownloaderService類的實現程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
public class SampleDownloaderService extendsDownloaderService { // You must use the public key belonging to your publisher account publicstaticfinalString BASE64_PUBLIC_KEY ="YourAndroidMarketLVLKey"; // You should also modify this salt publicstaticfinalbyte[] SALT =newbyte[] {1,42, -12, -1,54,98, -100, -12,43,2, -8, -4,9,5, -106, -107, -33,45, -1,84 }; @Override publicString getPublicKey() { returnBASE64_PUBLIC_KEY; } @Override publicbyte[] getSALT() { returnSALT; } @Override publicString getAlarmReceiverClassName() { returnSampleAlarmReceiver.class.getName(); } } |
然後在manifest檔案中宣告該Service即可。非常簡單吧!
3. 實現 alarm receiver
為了檢測下載程序和重啟下載服務,DownloaderService會安排一個RTC_WAKEUP Alarm來發送一個Intent到程式的 BroadcastReceiver。你必需定義這個 BroadcastReceiver 來呼叫 Downloader Library提供的函式,通過該函式來檢測下載狀態和在必要的情況下重啟下載服務。
實現這個類也是非常簡單的,一般來說只要覆寫onReceive()函式並且呼叫DownloaderClientMarshaller.startDownloadServiceIfRequired()函式即可。如下所示:
1 2 3 4 5 6 7 8 9 10 11 |
public class SampleAlarmReceiver extendsBroadcastReceiver { @Override publicvoidonReceive(Context context, Intent intent) { try{ DownloaderClientMarshaller.startDownloadServiceIfRequired(context, intent, SampleDownloaderService.class); }catch(NameNotFoundException e) { e.printStackTrace(); } } } |
注意這個類的名字就是前面getAlarmReceiverClassName()函式返回的名稱。然後在manifest檔案中宣告該receiver即可。 同樣很簡單吧!
4. 開始下載擴充套件檔案
程式的主Activity(通過Launcher圖示啟動的Activity)應該負責檢查擴充套件檔案是否存在、如果不存在就啟動下載服務。
使用Downloader Library來下載需要遵守如下步驟:
1)檢查檔案是否已經下載了
Downloader Library中的Helper類中包含了一些函式來簡化這個步驟:
getExtendedAPKFileName(Context, c, boolean mainFile, int versionCode)
doesFileExist(Context c, String fileName, long fileSize)
例如在示例專案中,在Activity的onCreate()函式中通過如下函式來檢查檔案是否存在:
1 2 3 4 5 6 7 8 |
boolean expansionFilesDelivered() { for(XAPKFile xf : xAPKS) { String fileName = Helpers.getExpansionAPKFileName(this, xf.mIsBase, xf.mFileVersion); if(!Helpers.doesFileExist(this, fileName, xf.mFileSize,false)) returnfalse; } returntrue; } |
這裡的XAPKFile物件儲存了已知擴充套件檔案的版本號和大小以及是否為main擴充套件檔案。如果該函式返回false則啟動下載服務。
2)通過 DownloaderClientMarshaller.startDownloadServiceIfRequired(Context c, PendingIntent notificationClient, ClassserviceClass)該函式來開始下載。
該函式的引數如下:
context: Your application’s Context.
notificationClient: 用來啟動主Activity的PendingIntent。用在DownloaderService 建立的用來顯示下載進度的通知中。當用戶選擇該通知,系統呼叫該PendingIntent來開啟顯示下載進度的Activity(一般而言就是啟動下載的Activity)。
serviceClass: 程式中繼承自DownloaderService的類。在必要的情況下會啟動該服務來開始下載。
這個函式返回一個整數來表示是否有必要下載檔案。有如下幾個值:
- NO_DOWNLOAD_REQUIRED: 表示檔案已經存在或者當前正在下載。
- LVL_CHECK_REQUIRED:表示需要授權驗證來獲取下載擴充套件檔案的URL。
- DOWNLOAD_REQUIRED: 表示擴充套件檔案的URL已經獲取到了,但是還沒開始下載。
例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
@Override public void onCreate(Bundle savedInstanceState) { // Check if expansion files are available before going any further if(!expansionFilesDelivered()) { // Build an Intent to start this activity from the Notification Intent notifierIntent =newIntent(this, MainActivity.getClass()); notifierIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); ... PendingIntent pendingIntent = PendingIntent.getActivity(this,0, notifierIntent, PendingIntent.FLAG_UPDATE_CURRENT); // Start the download service (if required) intstartResult = DownloaderClientMarshaller.startDownloadServiceIfRequired(this, pendingIntent, SampleDownloaderService.class); // If download has started, initialize this activity to show download progress if(startResult != DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED) { // This is where you do set up to display the download progress (next step) ... return; }// If the download wasn't necessary, fall through to start the app } startApp();// Expansion files are available, start the app } |
3)當 startDownloadServiceIfRequired() 函式的返回值不是NO_DOWNLOAD_REQUIRED的時候,呼叫DownloaderClientMarshaller.CreateStub(IDownloaderClient client, ClassdownloaderService)函式來建立一個IStub例項。這個IStub例項提供了Activity和下載服務之前的繫結功能,這樣您的Activity就可以收到下載事件了。
CreateStub()函式需要一個實現了IDownloaderClient介面的類和DownloaderService的實現類作為引數。一般而言只要讓Activity實現IDownloaderClient介面即可。
Android開發團隊推薦在Activity的onCreate()函式中建立IStub物件(在startDownloadServiceIfRequired()函式之後建立)。
例如:
1 2 3 4 5 6 7 8 9 10 11 12 |
// Start the download service (if required) int startResult = DownloaderClientMarshaller.startDownloadServiceIfRequired(this, pendingIntent, SampleDownloaderService.class); // If download has started, initialize activity to show progress if (startResult != DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED) { // Instantiate a member instance of IStub mDownloaderClientStub = DownloaderClientMarshaller.CreateStub(this, SampleDownloaderService.class); // Inflate layout that shows download progress setContentView(R.layout.downloader_ui); return; } |
當onCreate()函式返回以後,Activity會執行onResume()函式,在該函式中呼叫IStub的 connect() 函式。同樣在onStop()函式中呼叫IStub的 disconnect()函式。
例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
@Override protectedvoidonResume() { if(null!= mDownloaderClientStub) { mDownloaderClientStub.connect(this); } super.onResume(); } @Override protectedvoidonStop() { if(null!= mDownloaderClientStub) { mDownloaderClientStub.disconnect(this); } super.onStop(); } |
呼叫connect()用來繫結Activity和DownloaderService 。
5. 處理下載進度
要接收下載進度資訊,需要實現IDownloaderClient 介面。該介面有如下函式:
onServiceConnected(Messenger m)
在初始化完IStub後,會回撥該函式。該函式的引數是用來訪問您的DownloaderService的,通過 DownloaderServiceMarshaller.CreateProxy()函式來建立這個IDownloaderService物件,然後可以用這個物件來控制下載服務,比如 暫停、繼續下載等。
推薦的實現方式:
1 2 3 4 5 6 7 8 |
private IDownloaderService mRemoteService; ... @Override public void onServiceConnected(Messenger m) { mRemoteService = DownloaderServiceMarshaller.CreateProxy(m); mRemoteService.onClientUpdated(mDownloaderClientStub.getMessenger()); } |
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下載。
6. 讀取擴充套件檔案
首先要獲取擴充套件檔案的路徑,可以通過如下程式碼完成該操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
// The shared path to all app expansion files private final static String EXP_PATH ="/Android/obb/"; static String[] getAPKExpansionFiles(Context ctx, intmainVersion,intpatchVersion) { String packageName = ctx.getPackageName(); Vector<String> ret =newVector<String>(); if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { // Build the full path to the app's expansion files File root = Environment.getExternalStorageDirectory(); File expPath =newFile(root.toString() + EXP_PATH + packageName); // Check that expansion file path exists if(expPath.exists()) { if( mainVersion >0) { String strMainPath = expPath + File.separator +"main."+ mainVersion +"."+ packageName +".obb"; File main =newFile(strMainPath); if( main.isFile() ) { ret.add(strMainPath); } } if( patchVersion >0) { String strPatchPath = expPath + File.separator +"patch."+ mainVersion +"."+ packageName +".obb"; File main =newFile(strPatchPath); if( main.isFile() ) { ret.add(strPatchPath); } } } } String[] retArray =newString[ret.size()]; ret.toArray(retArray); returnretArray; } |
您可以在開始下載的時候,把擴充套件檔案的版本號儲存到 SharedPreferences 中,然後在這裡使用。
7. 使用 APK Expansion Zip Library
APK Expansion Zip Library專案包含了對ZIP檔案的處理,您可以通過該專案提供的函式來直接讀取ZIP檔案內容而不用解壓縮擴充套件檔案,它把ZIP擴充套件檔案當一個虛擬檔案系統來使用。
APK Expansion Zip Library專案包含如下類和函式:
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
大多數的程式都不會用到這個類。具體情況請參考其文件。
使用APK 擴充套件ZIP庫從ZIP檔案中讀取檔案參考程式碼如下:
// Get a ZipResourceFile representing a merger of both the main and patch filesZipResourceFile expansionFile = APKExpansionSupport.getAPKExpansionZipFile(appContext, mainVersion, patchVersion); // Get an input stream for a known file inside the expansion file ZIPsInputStream fileStream = expansionFile.getInputStream(pathToFileInsideZip);
上面的程式碼可以讀取main擴充套件檔案或patch擴充套件檔案(通過讀取兩個檔案的合併檔案來實現)中的任何檔案
如果要讀取指定的擴充套件檔案,其方法如下:
// Get a ZipResourceFile representing a specific expansion fileZipResourceFile expansionFile = new ZipResourceFile(filePathToMyZip);// Get an input stream for a known file inside the expansion file ZIPsInputStream fileStream = expansionFile.getInputStream(pathToFileInsideZip);
8. 測試擴充套件檔案
在釋出之前要測試兩個東東,下載檔案和讀取檔案。
測試讀取檔案
在釋出您的程式之前應該先測試下您的程式能否讀取擴充套件檔案,測試很簡單,只要把擴充套件檔案放到共享儲存區的特殊位置,然後啟動程式即可。
1)建立檔案目錄:
如果程式的包名為org.goodev,就建立如下的目錄:Android/obb/org.goodev/
2)把擴充套件檔案新增到該目錄
如果程式的包名為org.goodev,則主擴充套件檔名如下:main.03.org.goodev.obb。 版本號可以為大於零的任意值。
3)現在可以啟動程式來測試讀取擴充套件檔案的功能了。
測試下載檔案
由於在某些情況下需要在程式第一次使用的時候手工下載擴充套件檔案。所以需要測試來確保您的程式可以成功的獲取下載URL、下載檔案並且儲存到裝置中。
您可以把程式上傳到Market,同時上傳擴充套件檔案,然後不要釋出程式。這樣擴充套件檔案已經可以從Market下載了。 當你測試完成後再發布您的程式。
9. 更新程式
使用擴充套件檔案的一大好處就是每次更新App 使用者不用重新下載幾十上百兆的資料檔案了。Android Market讓你可以為每個APK提供兩個擴充套件檔案,這樣可以避免每次更新App都重新下載主擴充套件檔案資料,減少下載時間。
為了方便大家研究如下使用擴充套件檔案,可以到這裡下載示例專案程式碼:
http://code.google.com/p/goodev-demo-code/downloads/list
檔名:Market_Downloader_Sample.zip 裡面包含了所需要的各種庫專案。 在Eclipse中匯入即可使用。