文章目录

  • 前言
  • 一、文件准备
  • 二、拷贝文件至内置存储
  • 三、解压及使用扩展文件
  • 四、博客资源

前言

在上一篇博客 【Google Play】APK 扩展包 ( 2021年09月02日最新处理方案 | 内部测试链接 | 安装 Google Play 中带 扩展文件 的 APK 安装包 | 验证下载的扩展文件 ) 中 , 成功从 Google Play 中下载了 APK 安装包 及 APK 扩展文件 ;

APK 扩展文件 , 成功下载到了 /sdcard/Android/obb/com.exapmple.app/main.6.com.example.app.obb 路径中

一、文件准备


在本案例中 , 需要使用到 /sdcard/Android/obb/com.exapmple.app/main.6.com.example.app.obb 文件 , 如果没有条件从 Google Play 中下载应用的话 , 可以创建 /sdcard/Android/obb/com.exapmple.app/ 目录 , 将 源码 根目录中的 main.6.com.example.app.obb 文件 , 拷贝到上述目录中 ;

在下图所示的路径 SD 卡下的 Android/obb 目录下创建 com.example.app 目录 , 然后将
main.6.com.example.app.obb 文件拷贝到该目录中 ;

在 Windows 文件系统中操作 ;

拷贝完毕后的 AS 中文件管理器 ;

二、拷贝文件至内置存储


文件拷贝前 , 声明 SD 卡权限 ;

<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="com.example.app"><uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /><uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /><application></application></manifest>

访问 SD 卡中的 /sdcard/Android/obb/ 目录 , 可以不用申请 SD 卡 运行时 动态访问权限 ; 在 AndroidManifest.xml 清单文件中声明 WRITE_EXTERNAL_STORAGE 和 READ_EXTERNAL_STORAGE 权限即可 ;

将 APK 扩展文件 , 拷贝到 Android 应用的内置存储空间的 cache 目录中 ;

即 将 /sdcard/Android/obb/com.exapmple.app/main.6.com.example.app.obb 文件 拷贝到 /data/data/com.example.app/cache/main.6.com.example.app.obb 目录中 ;

下面的类中 , 提供了 主扩展文件 和 补丁扩展文件 的 文件名拼接方法 ;
参考 【Google Play】APK 扩展包 ( 2021年09月02日最新处理方案 | 扩展文件名格式 | 扩展文件下载存放地址 ) 二、APK 扩展文件名格式 博客章节理解 ;

moveObb2Cache 方法是移动 APK 扩展文件的核心方法 , 从外置 SD 卡移动到了 应用内置存储空间 中 ;

完整的文件拷贝代码示例 :

package com.example.app;import android.content.Context;
import android.os.Build;
import android.os.Environment;
import android.util.Log;import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;/*** 处理 APK 扩展文件*/
public class ExpansionFileManager {public final static String TAG = "ExpansionFileManager";private Context mContext;public ExpansionFileManager(Context mContext) {this.mContext = mContext;}/*** 获取主扩展文件名称 main.6.com.example.app.obb** @return*/public String getMainExpansionFileName() {return "main." +BuildConfig.VERSION_CODE + "." +this.mContext.getApplicationInfo().packageName + "." +"obb";}/*** 获取补丁扩展文件名称 patch.6.com.example.app.obb** @return*/public String getPatchExpansionFileName() {return "patch." +BuildConfig.VERSION_CODE + "." +this.mContext.getApplicationInfo().packageName + "." +"obb";}/*** 将 obb 文件移动到内置存储中* 将 /sdcard/Android/obb/com.exapmple.app/main.6.com.example.app.obb* 移动到 /data/data/com.example.app/cache/main.6.com.example.app.obb 内置存储中*/public void moveObb2Cache() {// /sdcard/Android/obb/com.exapmple.app/main.6.com.example.app.obbString srcFileName = Environment.getExternalStorageDirectory() +"/Android/obb/" +this.mContext.getApplicationInfo().packageName +"/" +getMainExpansionFileName();// /data/data/com.example.app/cache/main.6.com.example.app.obbString dstFileName = this.mContext.getCacheDir() +"/" +getMainExpansionFileName();Log.i(TAG, "移动文件 : srcFileName = " +srcFileName +" , dstFileName = " +dstFileName);File srcFile = new File(srcFileName);File dstFile = new File(dstFileName);Log.i(TAG, "srcFile = " + srcFile.exists() + " , dstFile = " + dstFile.exists());FileInputStream fis = null;try {fis = new FileInputStream(srcFileName);} catch (FileNotFoundException e) {e.printStackTrace();}FileOutputStream fos = null;try {fos = new FileOutputStream(dstFileName);} catch (FileNotFoundException e) {e.printStackTrace();}byte buffer[] = new byte[1024 * 16];int readLen = 0;try {while ((readLen = fis.read(buffer)) != -1) {fos.write(buffer, 0, readLen);}fis.close();fos.close();} catch (IOException e) {e.printStackTrace();} finally {Log.i(TAG, "文件移动完成");}}
}

三、解压及使用扩展文件


使用 zip 压缩文件工具类 , 对文件进行压缩 , 解压缩 操作 ;

将拷贝到 /data/data/com.example.app/cache/main.6.com.example.app.obb 目录下的文件 , 解压到 /data/data/com.example.app/cache/unzip 目录中 ;

执行下面的代码即可完成文件的移动 及 解压 ;

                // 移动文件ExpansionFileManager manager = new ExpansionFileManager(MainActivity.this);manager.moveObb2Cache();// 解压文件File cacheObb = new File(getCacheDir(), manager.getMainExpansionFileName());File unzipDir = new File(getCacheDir(), "unzip");ZipUtils.unZip(cacheObb, unzipDir);

文件压缩解压工具类 : 主要调用其 unZip 方法 ;

package com.example.app;import android.util.Log;import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.zip.CRC32;
import java.util.zip.CheckedOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;public class ZipUtils {public static final String TAG = "ZipUtils";/*** 删除文件, 如果有目录, 则递归删除*/private static void deleteFile(File file){if (file.isDirectory()){File[] files = file.listFiles();for (File f: files) {deleteFile(f);}}else{file.delete();}}/*** 解压文件* @param zip 被解压的压缩包文件* @param dir 解压后的文件存放目录*/public static void unZipApk(File zip, File dir) {try {// 如果存放文件目录存在, 删除该目录deleteFile(dir);// 获取 zip 压缩包文件ZipFile zipFile = new ZipFile(zip);// 获取 zip 压缩包中每一个文件条目Enumeration<? extends ZipEntry> entries = zipFile.entries();// 遍历压缩包中的文件while (entries.hasMoreElements()) {ZipEntry zipEntry = entries.nextElement();// zip 压缩包中的文件名称 或 目录名称String name = zipEntry.getName();// 如果 apk 压缩包中含有以下文件 , 这些文件是 V1 签名文件保存目录 , 不需要解压 , 跳过即可if (name.equals("META-INF/CERT.RSA") || name.equals("META-INF/CERT.SF") || name.equals("META-INF/MANIFEST.MF")) {continue;}// 如果该文件条目 , 不是目录 , 说明就是文件if (!zipEntry.isDirectory()) {File file = new File(dir, name);//创建目录if (!file.getParentFile().exists()) {file.getParentFile().mkdirs();}// 向刚才创建的目录中写出文件FileOutputStream fos = new FileOutputStream(file);InputStream is = zipFile.getInputStream(zipEntry);byte[] buffer = new byte[2048];int len;while ((len = is.read(buffer)) != -1) {fos.write(buffer, 0, len);}is.close();fos.close();}}// 关闭 zip 文件zipFile.close();} catch (Exception e) {e.printStackTrace();}}/*** 解压文件* @param zip 被解压的压缩包文件* @param dir 解压后的文件存放目录*/public static void unZip(File zip, File dir) {try {// 如果存放文件目录存在, 删除该目录deleteFile(dir);// 获取 zip 压缩包文件ZipFile zipFile = new ZipFile(zip);// 获取 zip 压缩包中每一个文件条目Enumeration<? extends ZipEntry> entries = zipFile.entries();// 遍历压缩包中的文件while (entries.hasMoreElements()) {ZipEntry zipEntry = entries.nextElement();// zip 压缩包中的文件名称 或 目录名称String name = zipEntry.getName();Log.i(TAG, "解压文件 " + name);// 如果该文件条目 , 不是目录 , 说明就是文件if (!zipEntry.isDirectory()) {File file = new File(dir, name);//创建目录if (!file.getParentFile().exists()) {file.getParentFile().mkdirs();}// 向刚才创建的目录中写出文件FileOutputStream fos = new FileOutputStream(file);InputStream is = zipFile.getInputStream(zipEntry);byte[] buffer = new byte[2048];int len;while ((len = is.read(buffer)) != -1) {fos.write(buffer, 0, len);}is.close();fos.close();}}// 关闭 zip 文件zipFile.close();} catch (Exception e) {e.printStackTrace();} finally {Log.i(TAG, "文件解压完成");}}/*** 压缩目录为zip* @param dir 待压缩目录* @param zip 输出的zip文件* @throws Exception*/public static void zip(File dir, File zip) throws Exception {zip.delete();// 对输出文件做CRC32校验CheckedOutputStream cos = new CheckedOutputStream(new FileOutputStream(zip), new CRC32());ZipOutputStream zos = new ZipOutputStream(cos);//压缩compress(dir, zos, "");zos.flush();zos.close();}/*** 添加目录/文件 至zip中* @param srcFile 需要添加的目录/文件* @param zos   zip输出流* @param basePath  递归子目录时的完整目录 如 lib/x86* @throws Exception*/private static void compress(File srcFile, ZipOutputStream zos,String basePath) throws Exception {if (srcFile.isDirectory()) {File[] files = srcFile.listFiles();for (File file : files) {// zip 递归添加目录中的文件compress(file, zos, basePath + srcFile.getName() + "/");}} else {compressFile(srcFile, zos, basePath);}}private static void compressFile(File file, ZipOutputStream zos, String dir)throws Exception {// temp/lib/x86/libdn_ssl.soString fullName = dir + file.getName();// 需要去掉tempString[] fileNames = fullName.split("/");//正确的文件目录名 (去掉了temp)StringBuffer sb = new StringBuffer();if (fileNames.length > 1){for (int i = 1;i<fileNames.length;++i){sb.append("/");sb.append(fileNames[i]);}}else{sb.append("/");}//添加一个zip条目ZipEntry entry = new ZipEntry(sb.substring(1));zos.putNextEntry(entry);//读取条目输出到zip中FileInputStream fis = new FileInputStream(file);int len;byte data[] = new byte[2048];while ((len = fis.read(data, 0, 2048)) != -1) {zos.write(data, 0, len);}fis.close();zos.closeEntry();}}

文件解压完成后的效果 :

四、博客资源


GitHub 源码地址 : https://github.com/han1202012/APK_Expansion_File

【Google Play】APK 扩展包 ( 2021年09月 最新处理方案 | 文件准备 | 拷贝文件至内置存储 | 解压及使用扩展文件 )相关推荐

  1. 【Google Play】APK 扩展包 ( 2021年09月02日最新处理方案 | 扩展文件名格式 | 扩展文件下载存放地址 )

    文章目录 前言 一.当前 Google Play 上传 APK 文件现状 二.APK 扩展文件名格式 三.APK 扩展文件下载地址 四.博客资源 前言 2021年08月01日 之后 , Google ...

  2. 【Google Play】APK 扩展包 ( 2021年09月02日最新处理方案 | 制作 APK 扩展包 | 上传 APK 扩展包到 Google Play | APK 扩展文件上传时机 )

    文章目录 一.制作 APK 扩展包 二.上传 APK 和 主扩展文件到 Google Play 三.APK 扩展文件上传时机 一.制作 APK 扩展包 apk.txt 内容 : APK 扩展文件测试文 ...

  3. 【Google Play】APK 扩展包 ( 2021年09月02日最新处理方案 | 内部测试链接 | 安装 Google Play 中带 扩展文件 的 APK 安装包 | 验证下载的扩展文件 )

    文章目录 前言 一.获取内部测试邀请链接 二.在手机中打开内部测试邀请链接 三.检查 APK 扩展文件 前言 参考 [Google Play]内部测试版本分发设置 ( 测试链接 | 配置测试权限 | ...

  4. 珍贵!分享!全国各省市最全乡镇、街道行政区划边界shp矢量数据+wgs84坐标系+2021年7月最新获取+2018年更新数据

    珍贵!全网独一份!全国各省市最全乡镇.街道行政区划边界shp矢量数据+wgs84坐标系+2021年7月最新获取+2018年更新数据 该数据为2021年7月获取,数据为2018年版本数据,官方未更新 温 ...

  5. 剑与远征服务器无限换,剑与远征兑换码2021年5月最新 剑与远征兑换码永久有效整理...

    剑与远征兑换码2021年5月最新整合是小编为大家带来的目前能用的所有兑换码整理,你可以通过在游戏中的兑换来获得大量的游戏资源,更好的帮助你进行游戏,每个码都是小编亲测的哦,快去享受游戏吧! 剑与远征五 ...

  6. 2021年11月最新搜狗验证码识别,6位全对正确率高达96%

    训练数据准备 标记数据是最花费时间的事情.最开始手工标记验证码1万条,训练后正确率在50%左右.然后写写代码使用这种低正确率的去自动标记.使用搜狗自动验证,来实现自动标记.逐渐积累数据量,后期只需要人 ...

  7. mtk android apk lib,Android MTK 拷贝第三方App 内置apk文件到系统目录

    MTK 的第三方App文件或者so库,都存在在Vendor目录下面 vendor\retch\thirdapp 我们要实现App 的拷贝,可以在pacakage\apps 下面去新建一个文件夹Retc ...

  8. 【电子学会】2021年09月图形化四级 -- 成语接龙

    成语接龙 小猫从"一鸣惊人"开始岀题,以"人"字开头接下一个成语,如果输入的不是四字成语或者输入成语的第一个字不是上一个成语的最后一个字,游戏结束. 1. 准备 ...

  9. 【电子学会】2021年09月图形化三级 -- 接红包游戏

    接红包游戏 1. 准备工作 (1)背景:使用原始的背景: (2)角色:除小猫外,添加角色Milk作为红包: (3)变量:建立一个得分变量用于计分. 2. 功能实现 (1)小猫在舞台下方,只能通过键盘左 ...

最新文章

  1. 大数据调度平台Airflow(一):什么是Airflow
  2. LDO的最小输入输出压差和最小负载电流
  3. 计算机cad论文参考文献,Auto CAD在计算机绘制矿图中的应用探索
  4. Ubuntu NEF to JPEG(Linux NEF 原生格式转jpeg)
  5. 关于MySQL 5.6 中文乱码的问题(尤其是windows的gbk编码)
  6. 梦之队奥运30人大名单:詹皇库里甜瓜双少领衔
  7. while(getchar()=='\n')continue;为什么作用是清空行
  8. 规范的.net 事件原理
  9. vim编写python_用vim写python代码
  10. 钉钉微应用怎么进入_钉钉微应用开发免登流程
  11. 猜数游戏,随机目标数字,直到猜中退出
  12. web开发 学习_是否想学习Web开发但不知道从哪里开始?
  13. MEncoder的基础用法—6.8. 从多个输入图像文件进行编码(JPEG, PNG, TGA等)
  14. 后台填充_单元格噩梦终于有救?500多行隔行填充,我就两步!
  15. hdp对应hadoop的版本_hadoop不同版本区别
  16. 论文绘图与合成图片过程中常见问题
  17. 白蛋白纳米-超声微泡载组织型纤溶酶原激活物基因靶向制备研究
  18. Linux用户不同UID分类区别
  19. 《江苏省ITS体系框架与规划——需求分析子课题》工作大纲评审会在南京举行[转贴,出处:ITSC 作者:刘浩,张可]
  20. Python使用matplotlib可视化哑铃图、强调从一个点到另一个点的变化、数量的变化、客户满意度的变化等(Dumbbell Plot)

热门文章

  1. 暗月渗透实战靶场-项目六(上)
  2. C primer Plus 9.3.4 递归和倒序计算 DE3
  3. 有码变高清!AI一秒还原马赛克,杜克大学出品
  4. 基于vue2编写的md编辑器-Bytemd
  5. 盛大哼唱检索前端提取算法分析
  6. 十年风雨,一个普通程序员的成长之路(十)如果曾经……如果未来……
  7. 跨平台SIP 客户端-linphone下载、使用
  8. 解决linux的-bash: ./xx: Permission denied/tensorflow 运行cpu还是gpu的方法
  9. ligh@local-host$ ssh-copy-id -i ~/.ssh/id_rsa.pub root@192.168.0.3
  10. 视频中的音频提取如何操作?一分钟教会你