赞
踩
在上一篇博客 【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.obb String srcFileName = Environment.getExternalStorageDirectory() + "/Android/obb/" + this.mContext.getApplicationInfo().packageName + "/" + getMainExpansionFileName(); // /data/data/com.example.app/cache/main.6.com.example.app.obb String 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.so String fullName = dir + file.getName(); // 需要去掉temp String[] 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
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。