Google Play超大安装包分包技术详细流程

这里说的“分包”不是dex文件的“分包”技术,而是针对大体积(超过100MB)的APK上架Google Play的技术。首先奉上的是官方说明文档,如有需要可以自行查看,本文除了涵盖官方文档的步骤,还详细说明了可能遇到的一些坑。

一、什么是APK扩展文件

我们平时开发的应用安装包大小一般都不会超过100MB,但是在开发游戏应用或者包含较多资源文件的应用时,很可能会超过100MB,这个时候是不能直接上架Google Play的,需要使用谷歌提供的分包技术, 核心的逻辑代码编译到APK文件中,脚本及资源部分则放到几个扩展文件中。然后分别将APK文件和扩展文件上传到Google Play。当APP运行时,读取扩展文件内容,执行其中的代码和加载资源。

二、Google Play的APK扩展文件规则

1、 Google Play最多支持两个扩展文件,每个扩展文件最大为2GB,也就是说整个APP包括扩展文件在内最大不能超过4.1GB(两个2GB的扩展文件 + 一个100MB的apk文件)。Google Play对扩展文件的最小字节数没有限制, 只要不上传空文件就行,也不要求APK文件和扩展文件加起来要大于100MB,因此,上传一个1MB的APK文件和一个1KB的扩展文件也是可以的。

2、Google Play将两个扩展文件分别称为main扩展文件和patch扩展文件,第一个上传的扩展文件作为main扩展文件,之后可以追加patch扩展文件。虽然从名字上看,main扩展文件是一个主要的文件,而patch扩展文件则是一个补丁文件,但实际上main扩展文件和patch扩展文件对Google Play来说只是两个不同文件名的文件,它们的地位是完全一样的。Google Play对这两个的功能和使用没有做任何限制,完全由开发者自己决定哪些资源要放到main扩展文件中,哪些要放到patch扩展文件中。在开发的过程中,你可以只使用一个扩展文件,比如只使用main扩展文件。

3、Google Play对扩展文件的格式没有任何要求,可以是任意类型的文件。不过由于最多只能上传两个扩展文件,所以一般都会使用压缩文件的格式,以zip格式居多。

4、两个扩展文件下载下来后的文件名是固定的。
main扩展文件的文件名是:
main.你应用的版本号.你应用的包名.obb
patch扩展文件的文件名是:
patch.你应用的版本号.你应用的包名.obb

你应用的版本号就是obb文件上传时所关联的APK的版本号,是一个整数值,在gradle文件里versionCode对应的那个数字。应用的包名就是gradle文件里applicationId对应的字符串,扩展名始终为obb。文件名是上传时由Google Play自动重命名的,不需要开发者自己修改要上传文件的文件名,当然,你把扩展文件命名为下载之后的名字(上面粗体那样),也是没关系的。

5、 扩展文件下载到手机后放置的位置同样是固定的,始终在这个目录里:
/SD卡根目录/Android/obb/你的应用包名/
例如,我的一个应用包名为“com.itant.wuji”的应用,它的扩展文件下载之后的路径是在这个目录下:

/storage/emulated/0/Android/obb/com.itant.wuji/

打个比方,我是在versionCode为13的APK上传的时候添加一个main扩展文件,那么我在Google Play下载versionCode为13的APK之后,它会自动把我的main扩展文件下载下来,最终它的存放路径为:

/storage/emulated/0/Android/obb/com.itant.wuji/main.13.com.itant.wuji.obb

Android SDK中并没有API可以直接获取到扩展文件的路径,只能通过getExternalStorageDirectory()和getPackageName()拼接得到完整的路径。比如我可能会定义一个获取main扩展文件的方法:

public static File getMainObbFile(Context context) {
    File mainObbFile = new File(Environment.getExternalStorageDirectory() +
            "/Android/obb/" +
            context.getPackageName() +
            File.separator +
            "main." + OBB_VERSION_CODE + "." + context.getPackageName() + ".obb");
    return mainObbFile;
}

而由于我们的扩展文件默认是下载到
/SD卡根目录/Android/obb/你的应用包名/
目录下的,那么在app被卸载的时候,扩展文件也会一起被移除,这点需要留意一下。

三、扩展文件开发流程

1、生成展文件
① 你可以使用谷歌提供的工具jobb,在Android SDK的tools/bin/目录下有一个jobb的工具。

jobb -d [目录名称的完整路径] -o [输出目标文件的完整路径] -pn [软件包名] -pv [包版本]

举一个例子:
源文件目录 D:contents\main\assets\
目标文件夹: D:\obb\output.obb
软件包名 com.example.app
包版本 25

jobb -d D:\contents\ main\assets\ -o D:\obb\output.obb -pn com.example.app -pv 25

② 使用WinRAR等压缩工具(我使用这个方法)。
把你所有需要压缩的文件都扔进一个名字为assets的文件夹里(你也可以使用其他文件夹名字)
Google Play超大安装包分包技术详细流程_第1张图片
然后右键点击assets文件夹–>添加到压缩文件(A)…
Google Play超大安装包分包技术详细流程_第2张图片
压缩文件名修改为“assets.obb”,压缩文件格式选择“zip”,压缩方式©选择“存储”,这样,就生成了一个符合规范的obb文件(不能大于2GB)。

2、 上传扩展文件
由于扩展文件是依附于APP的,所以你必须要发布一个APP到Google Play(当然你可以选择内测发布版本),我这里直接使用的是正式版本。登录Google Play开发者控制台,点击“创建应用”或者点击你已经发布的应用,由于我以前在Google Play发布过该APP了,所以我这里的步骤是:

点击控制台列表里要添加扩展文件的APP --> 版本管理 --> 应用版本 --> 正式版 --> 创建版本 --> 上传新的APK

Google Play超大安装包分包技术详细流程_第3张图片
APK上传之后,在右侧有一个圆圈圈起来的“+”号,点击它,就是在那里上传扩展文件的。

Google Play超大安装包分包技术详细流程_第4张图片

设备上使用的app可以不和Google Play上的一致,例如上传apk后发现了一个bug,修改了bug后可以直接打出最新的apk安装到手机上测试,不需要重新发布后再测试。但是测试设备上的app的包名,签名,尤其是版本号必须和Google Play上的版本一致,否则下载扩展文件的时候会提示签名错误。

一个示例,你在versionCode为13的APP上传了扩展文件,那么这个扩展文件的版本号就是固定的13,不会变化;

某一天你更新了APK,版本号为15了,并且在更新versionCode为15的APP时上传了新的扩展文件,那么要看情况,如果你在发布的时候,选中了versionCode为13的扩展文件,那么你在版本号为15的APP只能使用versionCode为13的扩展文件,如果你选中的是versionCode为15的扩展文件,那么你在版本号为15的APP只能使用versionCode为15的扩展文件。

只更新扩展文件,而不更新APK是不行的,因为我们从上面的流程可以知道,扩展文件是依附于APK文件的,如果你要更新APK扩展文件,那么一定要上传新的APK文件,由于Google Play更新APK文件时要求每次的版本号都要比上次的大, 所以即使APK文件自身没有任何变化,也需要修改APP的版本号(versionCode),然后重新打包生成APK文件上传。

3、下载扩展文件
终于到重头戏了,我们要做一些准备工作:
① 打开Android SDK的SDK Manager,下载以下两个package(我是用SDK Manager下载的)
Google Play超大安装包分包技术详细流程_第5张图片
如果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

② 解压上面下载下来的两个package,然后把里面的lib代码转换为我们Android Studio能使用的Module。

Android Studio --> File --> New --> New Module --> Android Library
按照以上步骤,我们要新建3个module,分别是:

obb_licensing:从market_licensing的library文件夹里拷贝代码,res文件也一并拷贝过来。主要是相关的一些权限的校验、许可的校验等。aidl文件夹直接复制到libs同级目录下,包名保持和原library的一致。
obb_downloader:从market_apk_expansion的downloader_library拷贝代码和res文件,是谷歌提供的下载器,功能非常完善,需要引用obb_licensing这个module
obb_zip:从market_apk_expansion的zip_file拷贝代码,提供zip相关的一些方法。

由于obb_licensing和obb_downloader这两个module要用到HttpClient的相关工具类,而Android 6.0(API 23)已经废弃HttpClient,所以需要在这两个module的gradle文件的android节点添加以下声明以获取相关的类库:

useLibrary 'org.apache.http.legacy'

然后让我们的项目引用obb_downloader和obb_zip这两个module。

③ 大多数情况下,当用户从Google Play上下载应用时,Google Play会自动将APK文件和扩展文件同时下载下来, 应用启动时就可以直接使用扩展文件了。但是Google Play并不总是保证一定会下载扩展文件, 官方文档中的原文是”Most of the time, Google Play downloads and saves your expansion files at the same time it downloads the APK to the device. However, in some cases Google Play cannot download the expansion files.”,至于具体是哪些cases下Google Play无法下载扩展文件并没有说明,此外即使Google Play正确的下载了扩展文件,但是由于扩展文件存放的目录是可以被用户和其他应用访问的,下载下来的扩展文件有可能会被用户或其他应用删除。为此,开发者必须在应用中自行实现扩展文件完整性检查和下载的机制,这个时候,我们就可以使用上面配置的module进行手动下载了。(在测试环境测试得知,在Google Play下载的APK大小等于APK+扩展文件大小,比如,APK为10MB,扩展文件为80MB,那么在Google Play下载的时候,会下载90MB)

④ 要想在设备下载扩展文件,必须满足以下条件:
A. 设备必须已安装Google框架和Google Play;
B. 设备的Google Play必须已经登陆Google账号;
C. 应用发布时有很多限制,例如国家,系统版本,屏幕大小等。测试设备必须在app的发布范围内,不在范围内的测试设备无法下载。简单来说就是通过设备上的Google Play能够搜索到需要测试的app;
D. 需要通过设备上的Google Play手动下载一次最新版本的app,这是因为下载APK扩展文件时,Google Play会验证该app是否合法,如果设备未从Google Play上下载过该版本的app,则Google Play会认为该app是从第三方渠道下载的, 是非法的,从而拒绝提供下载服务。
如果是测试版本,还要满足:
E. 测试设备Google Play登陆的Google账号需要通过开发者控制台加入到app的测试人员列表中。成为测试人员后,稍后便可以在Google Play搜索并下载发布的测试版本APP了。

⑤ 源码填坑:
A. 在com.google.android.vending.licensing.LicenseChecker类的checkAccess()中,有这样一段代码:

boolean bindResult = mContext.bindService(new Intent(new String(Base64.decode("Y29tLmFuZHJvaWQudmVuZGluZy5saWNlbnNpbmcuSUxpY2Vuc2luZ1NlcnZpY2U="))),
                                this, // ServiceConnection.
                                Context.BIND_AUTO_CREATE);

这段在代码在Android5.0上会抛出IllegalArgumentException,需要替换成如下代码:

Intent serviceIntent = new Intent(new String(Base64.decode("Y29tLmFuZHJvaWQudmVuZGluZy5saWNlbnNpbmcuSUxpY2Vuc2luZ1NlcnZpY2U=")));
serviceIntent.setPackage("com.android.vending");
boolean bindResult = mContext.bindService(serviceIntent, this, Context.BIND_AUTO_CREATE);

B. 在com.google.android.vending.expansion.downloader.DownloaderClientMarshaller类的connect()方法中,有这样一段代码:

if ( !c.bindService(bindIntent, mConnection, Context.BIND_DEBUG_UNBIND) ) {
     if ( Constants.LOGVV ) {
         Log.d(Constants.TAG, "Service Unbound");
     }
 } else {
     mBound = true;
 }

这里会导致绑定的服务在某些情况下无法启动,服务不启动,IDownloaderClient接口的onServiceConnected()方法就不会执行,mRemoteService为null,从而导致NullPointerException。虽然在使用mRemoteService前增加对其是否为null的判断可以避免crash,但是下载过程仍然无法监控,无法得到下载的结果。需要将这段代码替换成如下代码。也就是将BIND_DEBUG_UNBIND替换成BIND_AUTO_CREATE。

if ( !c.bindService(bindIntent, mConnection, Context.BIND_AUTO_CREATE) ) {
     if ( Constants.LOGVV ) {
         Log.d(Constants.TAG, "Service Unbound");
     }
 } else {
     mBound = true;
 }

C. 在com.google.android.vending.expansion.downloader.impl.DownloadNotification这个类里有用到:

mCurrentNotification.setLatestEventInfo(mContext, mCurrentTitle, mCurrentText,mContentIntent);

a. 在低于API Level 11版本,也就是Android 2.3.3以下的系统中,setLatestEventInfo()函数是唯一的实现方法。
b. 高于API Level 11,低于API Level 16 (Android 4.1.2)版本的系统中,可使用Notification.Builder来构造函数。但要使用getNotification()来使notification实现。此时,前面版本在notification中设置的Flags,icon等属性都已经无效,要在builder里面设置。
c. 高于API Level 16的版本,就可以用Builder和build()函数来配套的方便使用notification了。
由于我的apk最低版本为16,所以用以下代码代替(要兼容Android 8.0还要加上Channel):

if (Build.VERSION.SDK_INT >= 26) {
            int importance = NotificationManager.IMPORTANCE_LOW;
            NotificationChannel notificationChannel = new NotificationChannel(LOGTAG, LOGTAG, importance);
            notificationChannel.enableLights(true);
            notificationChannel.setLightColor(Color.RED);
            notificationChannel.enableVibration(true);
            notificationChannel.setVibrationPattern(new long[]{100, 200, 300, 400, 500, 400, 300, 200, 400});
            mNotificationManager.createNotificationChannel(notificationChannel);
        }
        if (progress.mOverallTotal <= 0) {
            // we just show the text
            //mNotification.tickerText = mCurrentTitle;
            //mNotification.icon = android.R.drawable.stat_sys_download;
            //mNotification.setLatestEventInfo(mContext, mLabel, mCurrentText, mContentIntent);
            mNotification = new Notification.Builder(mContext)
                    .setAutoCancel(true)
                    .setContentTitle(mCurrentTitle)
                    .setContentText(mCurrentText)
                    .setContentIntent(mContentIntent)
                    .setSmallIcon(android.R.drawable.stat_sys_download)
                    .setWhen(System.currentTimeMillis())
                    .build();
            mCurrentNotification = mNotification;
        } else {
            mCustomNotification.setCurrentBytes(progress.mOverallProgress);
            mCustomNotification.setTotalBytes(progress.mOverallTotal);
            mCustomNotification.setIcon(android.R.drawable.stat_sys_download);
            mCustomNotification.setPendingIntent(mContentIntent);
            mCustomNotification.setTicker(mLabel + ": " + mCurrentText);
            mCustomNotification.setTitle(mLabel);
            mCustomNotification.setTimeRemaining(progress.mTimeRemaining);
            mCurrentNotification = mCustomNotification.updateNotification(mContext);
        }
        mNotificationManager.notify(NOTIFICATION_ID, mCurrentNotification);

D. 下载过程提示APKExpensionPolicy.java报找不到相关类的错误,这是Google Play Service的一个bug,要在应用的AndroidManifest.xml文件的application节点加上以下代码:


E. 某些机器的onResume()里不能直接判断,需要延迟几秒后再判断,否则mDownloaderClientStub总是为null

@Override
public void onResume() {
    new Handler().postDelayed(new Runnable() {
        @Override
        public void run() {
            if(null!= mDownloaderClientStub) {
                mDownloaderClientStub.connect(getActivity());
            }
        }
    }, 2000);

    super.onResume();
}

F. 在V14CustomNotification.java里,要兼容Android 8.0的话,需要添加Channel

Notification.Builder builder = new Notification.Builder(c);
        if (Build.VERSION.SDK_INT >= 26) {
            builder = new Notification.Builder(c, LOGTAG);
        }

⑥ 编码实现
A. 声明权限(注意Android 6.0以上动态权限的获取):


    
    

    
    

    
    

    
    

    
    

    
    

B. 获取你APP的Base64 编码 RSA 公共密钥
打开
https://play.google.com/apps/publish/?account=7314772929017087211#AppListPlace
点击你想集成分包技术的APP --> 开发工具 --> 服务和API
Google Play超大安装包分包技术详细流程_第6张图片

C. 扩展文件的大小,可以用以下代码计算:

File file = new File("C:\\assets.obb");
System.out.println(file.length());

我们可以用一个类来记录经常用到的常量:

public class ObbConstant {
    // 应用的Base64编码RSA公共密钥
    public static final String APP_PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4Mhd6T15Lxt1/fkCSsSPRYxhnxuboUvGXd1kOndR1O5Sx+wodlqBVsY/cSGbV4ep3Tb1eOHn5RqiHg0d4d2xVbggOvYY2TncInAWFkB/VFzxMuLxRmJ8tLALFZ5lzc6mK+TqERq1KnBWzlY7noGBBJk5pOQuctRZJYs84j3K9CwIA9qU1X+522eEfTKV9m/DixMBBcK5mIMxm14Fdd6JJguE+SIzx5HHxe4OiEtieCRWSLHxlWGVeA+TJWnMBfH4BIPiysVUaiko9fHzPaPjvoPbEVAwuv/uJMrf2KMcPq31AkaUU0fe7fyoxeEqUyOc9PyHSztMuCyA+YdOY9qpQwIDAQAB";
    // 随机数组,可以自定义
    public static final byte[] SALT = {3, 0, 6, 2, 4, 7, 0, 0, 3, 0, 6, 2, 4, 7, 7, 0};
    public static final String LOG_TAG = "tag_obb";

    // OBB扩展文件的版本号,必须和Google Play最新发布的APK里选中的扩展文件版本一致
    public static final int OBB_VERSION_CODE = 13;
    // OBB扩展文件大小
    public static final long OBB_FILE_SIZE_MAIN = 454;
    public static final long OBB_FILE_SIZE_PATCH = 1024;
}

D. 为了实现在后台下载文件,下载库项目提供了一个DownloaderService,我们要继承自这个文件来实现您的下载服务。为了简化下载服务的开发,该DownloaderService还实现了如下功能:
a. 注册一个BroadcastReceiver来监听设备的网络连接状态的改变。如果网络连接断开就暂停下载;如果网络连接恢复就继续下载;
b. 安排一个 RTC_WAKEUP 通知,当下载服务被终结的时候可以通过该通知来启动下载服务;
c. 生成一个通知(Notification )来显示下载的进度以及下载错误等状态
允许您的程序手工的暂停和恢复下载;
d. 检测共享存储区挂载了并且可用,在下载文件之前检测 文件是否已经存在、存储空间是否足够。如果出现问题就通知用户。

您仅仅需要创建一个继承自DownloaderService的类,并且实现新建一个ObbDownloadService继承DownloaderService,并在AndroidManifest.xml文件声明:


代码如下:

import com.google.android.vending.expansion.downloader.impl.DownloaderService;

/**
 * Created by SKY218790 on 2018/11/14.
 */

public class ObbDownloadService extends DownloaderService {
    @Override
    public String getPublicKey() {
        return ObbConstant.APP_PUBLIC_KEY;
    }

    @Override
    public byte[] getSALT() {
        return ObbConstant.SALT;
    }

    @Override
    public String getAlarmReceiverClassName() {
        return AlarmReceiver.class.getName();
    }
}

E. 为了检测下载进程和重启下载服务,DownloaderService会安排一个RTC_WAKEUP Alarm来发送一个Intent到程序的 BroadcastReceiver。你必需定义这个 BroadcastReceiver 来调用 Downloader Library提供的函数,通过该函数来检测下载状态和在必要的情况下重启下载服务。实现这个类也是非常简单的,一般来说只要覆写onReceive()函数并且调用DownloaderClientMarshaller.startDownloadServiceIfRequired()函数即可。 定义AlarmReceiver继承自BroadcastReceiver如下所示:

public class AlarmReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        try {
            Log.d(ObbConstant.LOG_TAG, "AlarmReceiver startDownloadServiceIfRequired");
            DownloaderClientMarshaller.startDownloadServiceIfRequired(context, intent, ObbDownloadService.class);
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }       
    }
}

在AndroidManifest.xml文件声明:


F. 提供一个可能使用的Activity,别忘了在AndroidManifest.xml中声明这个Activity哦。

import android.app.PendingIntent;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Messenger;
import android.support.v7.app.AppCompatActivity;

import com.android.vending.expansion.zipfile.APKExpansionSupport;
import com.google.android.vending.expansion.downloader.DownloadProgressInfo;
import com.google.android.vending.expansion.downloader.DownloaderClientMarshaller;
import com.google.android.vending.expansion.downloader.DownloaderServiceMarshaller;
import com.google.android.vending.expansion.downloader.IDownloaderClient;
import com.google.android.vending.expansion.downloader.IDownloaderService;
import com.google.android.vending.expansion.downloader.IStub;
import com.itant.wuji.obb.ObbConstant;
import com.itant.wuji.obb.ObbDownloadService;
import com.itant.wuji.obb.ObbUtils;
import com.itant.wuji.tools.ToastManager;

import java.io.File;

public class DemoActivity extends AppCompatActivity implements IDownloaderClient {
    private IStub mDownloaderClientStub;

    private void dealObbTask() {
        if (ObbUtils.isMainObbExist(this)) {
            // main obb文件已经下载好了,执行解压(其实在这之前应该先判断是否存在解压之后的文件,
            // 如果解压之后的文件不存在,再查看main obb文件是否存在)
            File targetObbFile = ObbUtils.getMainObbFile(this);
            File destFolder = ObbUtils.getExtractFolder(this);

            return;
        }

        Intent notifierIntent = new Intent(this, DemoActivity.class);
        notifierIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notifierIntent, PendingIntent.FLAG_UPDATE_CURRENT);
        ToastManager.getInstance().justToastShort("开始下载");
        try {
            // 开始下载
            int startResult = DownloaderClientMarshaller.startDownloadServiceIfRequired(this, pendingIntent, ObbDownloadService.class);
            if (startResult != DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED) {
                // todo 此时要展示下载进度
                mDownloaderClientStub = DownloaderClientMarshaller.CreateStub(this, ObbDownloadService.class);
            }
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
            ToastManager.getInstance().justToastShort(e.toString());
        }
    }

    @Override
    public void onResume() {
        if (null != mDownloaderClientStub) {
            mDownloaderClientStub.connect(this);
        }
        super.onResume();
    }

    @Override
    public void onStop() {
        if (null != mDownloaderClientStub) {
            mDownloaderClientStub.disconnect(this);
        }
        super.onStop();
    }

    private IDownloaderService mRemoteService;

    @Override
    public void onServiceConnected(Messenger m) {
        ToastManager.getInstance().justToastShort("已连接");
        if (mDownloaderClientStub == null) {
            ToastManager.getInstance().justToastShort("mDownloaderClientStub为null");
            return;
        }
        mRemoteService = DownloaderServiceMarshaller.CreateProxy(m);
        mRemoteService.onClientUpdated(mDownloaderClientStub.getMessenger());
    }

    @Override
    public void onDownloadStateChanged(int newState) {
        if (newState == IDownloaderClient.STATE_COMPLETED) {
            ToastManager.getInstance().justToastShort("下载完成");
            // 下载完成
            String[] files = APKExpansionSupport.getAPKExpansionFiles(this, ObbConstant.OBB_VERSION_CODE, ObbConstant.OBB_VERSION_CODE);
            // todo 解压
        }
    }

    @Override
    public void onDownloadProgress(DownloadProgressInfo progress) {
        // 预计完成时间、当前下载速度、完成的百分比
    }
}

IStub可以控制暂停、继续下载、取消等。
注意:某些机器的onResume()里不能直接判断,需要延迟几秒后再判断,否则mDownloaderClientStub总是为null。

@Override
public void onResume() {
    new Handler().postDelayed(new Runnable() {
        @Override
        public void run() {
            if(null!= mDownloaderClientStub) {
                mDownloaderClientStub.connect(getActivity());
            }
        }
    }, 2000);

    super.onResume();
}

四、使用扩展文件

扩展文件的使用通常需要由开发者自行实现,扩展文件一般都是一个zip格式的压缩包,可以解压后再使用。不过Android SDK中附带了一个APK Expansion Zip Library(在SDK的/extras/google/google_market_apk_expansion/zip_file/目录中,其实就是我们新建的obb_zip这个module),可以直接读取zip文件中的内容,官方建议是不解压,直接用APK Expansion Zip Library来访问zip文件中包含的其他文件,具体参考官方文档。

此外,即使需要解压,也不要解压到扩展文件所在的目录中,而是放到getExternalFilesDir()所在的目录中。
这里提供一个AndroidZip,需要的可自行下载。

参考文档:
https://blog.csdn.net/u013334392/article/details/81392097
https://blog.csdn.net/ccpat/article/details/51519821

你可能感兴趣的:(Android)