应用场景
当应用程序超过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()获取即可.
<package-name>:包名
关于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,表明该文件已经下载好,程序可正常启动
详细代码可参见SampleDownloaderActivity中的onCreate()方法
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);
// Get an input stream for a known file inside the expansion file ZIPs
InputStream fileStream = expansionFile.getInputStream(pathToFileInsideZip);
测试读取文件
测试下载文件
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" />
参考资料:
http://developer.android.com/google/play/expansion-files.html
http://blog.chengyunfeng.com/?p=342
http://yunfeng.sinaapp.com/?p=343#ixzz1oKclZQjT
http://blog.csdn.net/tonyfield/article/details/11739035
http://android-developers.blogspot.com/2012/03/android-apps-break-50mb-barrier.html
http://www.iteye.com/news/24446
Thank you very much