Google PlayAPK 扩展包 ( 2021年09月 最新处理方案 | 文件准备 | 拷贝文件至内置存储 | 解压及使用扩展文件 )
Posted 韩曙亮
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Google PlayAPK 扩展包 ( 2021年09月 最新处理方案 | 文件准备 | 拷贝文件至内置存储 | 解压及使用扩展文件 )相关的知识,希望对你有一定的参考价值。
前言
在上一篇博客 【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(以上是关于Google PlayAPK 扩展包 ( 2021年09月 最新处理方案 | 文件准备 | 拷贝文件至内置存储 | 解压及使用扩展文件 )的主要内容,如果未能解决你的问题,请参考以下文章
Google PlayAPK 扩展包 ( 2021年09月 最新处理方案 | 文件准备 | 拷贝文件至内置存储 | 解压及使用扩展文件 )
Google PlayAPK 扩展包 ( 2021年09月02日最新处理方案 | 内部测试链接 | 安装 Google Play 中带 扩展文件 的 APK 安装包 | 验证下载的扩展文件 )(代码片