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 安装包 | 验证下载的扩展文件 )(代码片

扩展包名android

在 React 中使用 Firebase 进行 Google 登录以进行 chrome 扩展

谷歌公布2021年最热门Chrome开发者工具

我可以在 Google People API 和 Task API 中创建扩展属性吗?