ggp对APK包做了50M的上传限制,如果超过50M的话就要上传扩展包
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].
main or patch
指定文件是main扩展文件还是patch扩展文件,每个APK只能有一个main扩展文件和一个patch扩展文件。
和第一次上传该扩展文件的APK文件的android:versionCode一致。后续版本的APK可以重用前面上传的扩展文件。
您程序的Java包名
例如程序的版本为5,程序的包名为org.goodev.expansion.downloader。则上传的main扩展文件会被重命名为:
main.5.org.goodev.expansion.downloader.obb
三、扩展文件的保存位置
当Android Market下载程序的扩展文件的时候会保存到系统的共享存储区。为了确保程序正常运行,您不能删除、移动或者重命名扩展文件。在某些设备上Market无法自动下载该扩展文件,那么您应该在程序启动的时候去下载该文件并且保存到同样的位置。
扩展文件保存位置如下:
对于每个App而言,该目录下最多只能包含2个扩展文件。一个是main扩展文件另外一个是patch扩展文件。当更新程序的时候,如果有新的扩展文件则新文件会覆盖旧的扩展文件。
如果您需要解压缩扩展文件来使用,请注意不要删除该.obb文件,并且也不要把文件解压缩到该目录。您应该把解压缩后的文件保存到getExternalFilesDir()返回的目录下面。如果有可能的话,最好使用程序能直接读取的文件格式而不用再次解压缩文件了。Android开发团队提供了一个项目( APK Expansion Zip Library)可以直接读取ZIP文件中的内容而不用解压缩该文件.
需要注意的是:保存在系统共享存储区的文件,用户和其他APP也可以访问。
四、下载扩展文件的流程
在大多数情况下,Market会在下载APK的同时去下载扩展文件。然而,在某些情况下Market无法下载扩展文件或者用户删除了以前下载的扩展文件,您的程序需要处理这种异常情况。当您的程序启动的时候,可以检测文件是否存在并且可以从Market上下载。
开发者检查清单:
您可以通过下面的清单来检查是否需要使用扩展文件
五、扩展文件的规则和限制
要在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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
... |
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(); } } |
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(); } } } |
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; } |
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 } |
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; } |
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(); } |
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()); } |
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 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; } |