下载完成后使用market_licensing-r02.zip文件中的目录google_market_licensing\library来创建一个库项目;然后使用market_apk_expansion-r01.zip中的google_market_apk_expansion\downloader_library来创建另外一个库项目。同时为了简化对ZIP格式扩展文件的处理,在market_apk_expansion-r01.zip文件中还包含了一个对ZIP文件处理的库项目:google_market_apk_expansion\zip_file。 如果您使用的扩展文件格式是ZIP,那么也可以创建这个库项目。 1. 声明需要的权限
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
< manifest ...>
< uses-permission android:name = "com.android.vending.CHECK_LICENSE" />
< uses-permission android:name = "android.permission.INTERNET" />
< uses-permission android:name = "android.permission.WAKE_LOCK" />
< uses-permission android:name = "android.permission.ACCESS_NETWORK_STATE" />
< uses-permission android:name = "android.permission.ACCESS_WIFI_STATE" />
< uses-permission android: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 )来显示下载的进度以及下载错误等状态
- 允许您的程序手工的暂停和恢复下载
- 检测共享存储区挂载了并且可用,在下载文件之前检测 文件是否已经存在、存储空间是否足够。如果出现问题就通知用户。
您仅仅需要创建一个继承自DownloaderService的类,并且实现如下三个函数即可: 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 extends DownloaderService {
public static final String BASE64_PUBLIC_KEY = "YourAndroidMarketLVLKey" ;
public static final byte [] SALT = new byte [] { 1 , 42 , - 12 , - 1 , 54 , 98 ,
- 100 , - 12 , 43 , 2 , - 8 , - 4 , 9 , 5 , - 106 , - 107 , - 33 , 45 , - 1 , 84
};
@Override
public String getPublicKey() {
return BASE64_PUBLIC_KEY;
}
@Override
public byte [] getSALT() {
return SALT;
}
@Override
public String getAlarmReceiverClassName() {
return SampleAlarmReceiver. 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 extends BroadcastReceiver {
@Override
public void onReceive(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 ))
return false ;
}
return true ;
}
|
这里的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已经获取到了,但是还没开始下载。
LVL_CHECK_REQUIRED 和 DOWNLOAD_REQUIRED 在本质上是一样的,一般而言您无需关注这个状态。在您的主Activity中调用 startDownloadServiceIfRequired(),你只需要看看返回值是否为NO_DOWNLOAD_REQUIRED即可。如果返回值不是NO_DOWNLOAD_REQUIRED, Downloader Library 开始启动下载,您应该更新程序界面来显示下载进度;如果返回值是 NO_DOWNLOAD_REQUIRED,表明该文件已经下载好了,您的程序可以正常启动了。
例如:
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) {
if (!expansionFilesDelivered()) {
Intent notifierIntent = new Intent( 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);
int startResult = DownloaderClientMarshaller.startDownloadServiceIfRequired( this ,
pendingIntent, SampleDownloaderService. class );
if (startResult != DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED) {
...
return ;
}
}
startApp();
}
|
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
|
int startResult = DownloaderClientMarshaller.startDownloadServiceIfRequired( this ,
pendingIntent, SampleDownloaderService. class );
if (startResult != DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED) {
mDownloaderClientStub = DownloaderClientMarshaller.CreateStub( this ,
SampleDownloaderService. class );
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
protected void onResume() {
if ( null != mDownloaderClientStub) {
mDownloaderClientStub.connect( this );
}
super .onResume();
}
@Override
protected void onStop() {
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
|
private final static String EXP_PATH = "/Android/obb/" ;
static String[] getAPKExpansionFiles(Context ctx, int mainVersion, int patchVersion) {
String packageName = ctx.getPackageName();
Vector<String> ret = new Vector<String>();
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
File root = Environment.getExternalStorageDirectory();
File expPath = new File(root.toString() + EXP_PATH + packageName);
if (expPath.exists()) {
if ( mainVersion > 0 ) {
String strMainPath = expPath + File.separator + "main." +
mainVersion + "." + packageName + ".obb" ;
File main = new File(strMainPath);
if ( main.isFile() ) {
ret.add(strMainPath);
}
}
if ( patchVersion > 0 ) {
String strPatchPath = expPath + File.separator + "patch." +
mainVersion + "." + packageName + ".obb" ;
File main = new File(strPatchPath);
if ( main.isFile() ) {
ret.add(strPatchPath);
}
}
}
}
String[] retArray = new String[ret.size()];
ret.toArray(retArray);
return retArray;
}
|
您可以在开始下载的时候,把扩展文件的版本号保存到 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 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);
上面的代码可以读取main扩展文件或patch扩展文件(通过读取两个文件的合并文件来实现)中的任何文件
如果要读取指定的扩展文件,其方法如下:
// 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);
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中导入即可使用。