前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【Google Play】APK 扩展包 ( 2021年09月 最新处理方案 | 文件准备 | 拷贝文件至内置存储 | 解压及使用扩展文件 )

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

作者头像
韩曙亮
发布2023-03-29 16:46:43
5160
发布2023-03-29 16:46:43
举报
文章被收录于专栏:韩曙亮的移动开发专栏

文章目录

前言

在上一篇博客 【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 卡权限 ;

代码语言:javascript
复制
<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 卡移动到了 应用内置存储空间 中 ;

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

代码语言:javascript
复制
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 目录中 ;

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

代码语言:javascript
复制
                // 移动文件
                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 方法 ;

代码语言:javascript
复制
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

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021-09-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 文章目录
  • 前言
  • 一、文件准备
  • 二、拷贝文件至内置存储
  • 三、解压及使用扩展文件
  • 四、博客资源
相关产品与服务
对象存储
对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档