Android 安装包优化使用 lib7zr.so 动态库处理压缩文件 ( jni 中 main 函数声明 | 命令行处理 | jni 调用 lib7zr.so 函数库处理压缩文件完整代码 )(代码片

Posted 韩曙亮

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 安装包优化使用 lib7zr.so 动态库处理压缩文件 ( jni 中 main 函数声明 | 命令行处理 | jni 调用 lib7zr.so 函数库处理压缩文件完整代码 )(代码片相关的知识,希望对你有一定的参考价值。



前置博客 :





一、JNI 中 main 函数声明



使用 7zr 可执行程序处理压缩文件时时 , 调用的是其主函数 , CPP\\7zip\\UI\\Console\\MainAr.cpp 中的 main 函数 , 传入 7z a outputFile inputFile -mx=compressDegree -tcompressType 压缩命令 , 或 7z x [输入文件] -o[输出目录] 解压命令 , 都是使用该主函数接收相关参数 ;

int MY_CDECL main
(
  #ifndef _WIN32
  int numArgs, char *args[]
  #endif
)

int numArgs 参数表示字符串个数 ;

7zr a files.7z files -mx=9 -t7z 命令中 , 有 6 6 6 个字符串 , 由 5 5 5 个空格隔开 ;

char *args[] 是 指针数组 , 数组中的元素是 char * 类型的指针 , 就是字符串 , 这是个字符串数组 ;


7zr 程序中的主要的头文件是 7zTypes.h , 该头文件中 声明了主要的 类型 和 函数 ; 引入该头文件 ;

#include <7zTypes.h>

声明外部函数 :

// 表示该函数在其它代码中实现
// 这是 CPP\\7zip\\UI\\Console\\MainAr.cpp 中的 main 函数
extern int MY_CDECL main
        (
#ifndef _WIN32
        int numArgs, char *args[]
#endif
);

点击声明左侧的双向箭头按钮 , 可以跳转到 MainAr.cpp 的 main 函数位置 ;

在这里插入图片描述

跳转位置 :

在这里插入图片描述





二、命令字符串切割并传入 main 函数



调用 main 函数 , 需要传入对应的参数 , 分别是

  • int numArgs 字符串个数 ;

  • char *args[] 字符串数组 ; 指针数组 , 每个数组元素中都有一个 char * 指针元素 , 指向字符串 ;

int MY_CDECL main
(
  #ifndef _WIN32
  int numArgs, char *args[]
  #endif
)

从 Java 传入 C 的指令如下 :

7zr a files.7z files -mx=9 -t7z

使用空格将上述指令切割成 6 6 6 个字符串 , 然后传入 main 函数 ;

字符串切割过程 :

    // 命令示例 : 7zr a files.7z files -mx=9 -t7z
    // 参数个数
    int argCount = 0;
    // 存放多个字符串, 最多 20 个字符串 , 每个最多 1024 个字符
    char argArray[20][1024] = {0};

    //分割字符串 将值填入变量

    // 获取  cmd_java 字符串长度
    int cmd_size = strlen(cmd_java);
    // 二维数组 循环控制变量, 第一个是字符串数组 , 第二个是字符串中的字符数组
    int str_index = 0, char_index = 0;

    // 标记字符是否是非空字符, tab, 如果是则设置 1 , 如果不是设置 0
    // 开始时默认 0, 不是空格
    int isChar = 0;

    // 逐个字节遍历 字符
    for(int i = 0; i < cmd_size; i ++){
        // 获取一个字符
        char c = cmd_java[i];

        //LOGI("遍历 %d . %c , cmd_size = %d", i, c, cmd_size);

        switch (c) {
            case ' ': // 判断是否是空格
            case '\\t': // 判断是否是 TAB 空格
                if(isChar){
                    // 如果上一个字符不是空格 , 则需要结束当前的字符串
                    argArray[str_index][char_index++] = '\\0';
                    // 字符串索引自增 1
                    str_index ++;
                    // 字符串内字符索引归零
                    char_index = 0;
                    // 设置当前的字符为空格标志位 1
                    isChar = 0;
                }else{
                    // 如果之前是空格, 那么现在也是空格 ,
                    // 说明命令中有多个空格 , 此处不做任何处理
                }
                break;
            default:
                isChar = 1;
                // 将当前字符放入数组中
                argArray[str_index][char_index++] = c;
                break;
        }
    }

    // 如果最后一位不是空格 , 则需要手动将最后一个字符串写入到数组中
    if (cmd_java[cmd_size - 1] != ' ' && cmd_java[cmd_size - 1] != '\\t') {
        // 如果上一个字符不是空格 , 则需要结束当前的字符串
        argArray[str_index][char_index++] = '\\0';
        // 字符串索引自增 1
        str_index ++;
    }

    // 统计字符串个数
    argCount = str_index;

    // 拼装字符串数组
    char *args[] = {0};
    for (int i = 0; i < argCount; ++i) {
        args[i] = argArray[i];
        // 打印字符串数组
        LOGI("%d . %s", i, args[i]);
    }

最终的字符串个数是 argCount , 字符串数组 args ;

将这两个参数传入 main 函数即可 ;





三、完整代码示例





1、完整 jni 代码


完整 jni 层 C++ 代码如下 :

#include <jni.h>
#include <string>
#include <7zTypes.h>
#include "logging_macros.h"

// 表示该函数在其它代码中实现
// 这是 CPP\\7zip\\UI\\Console\\MainAr.cpp 中的 main 函数
extern int MY_CDECL main
        (
#ifndef _WIN32
        int numArgs, char *args[]
#endif
);

extern "C"
JNIEXPORT void JNICALL
Java_kim_hsl_a7_1zip_MainActivity_executeCmd(JNIEnv* env, jobject thiz, jstring cmd) {
    // 将 Java 字符串转为 C 字符串
    const char *cmd_java = env->GetStringUTFChars(cmd, 0);

    LOGI("jni 中处理压缩文件命令 : %s", cmd_java);

    // 命令示例 : 7zr a files.7z files -mx=9 -t7z
    // 参数个数
    int argCount = 0;
    // 存放多个字符串, 最多 20 个字符串 , 每个最多 1024 个字符
    char argArray[20][1024] = {0};

    //分割字符串 将值填入变量

    // 获取  cmd_java 字符串长度
    int cmd_size = strlen(cmd_java);
    // 二维数组 循环控制变量, 第一个是字符串数组 , 第二个是字符串中的字符数组
    int str_index = 0, char_index = 0;

    // 标记字符是否是非空字符, tab, 如果是则设置 1 , 如果不是设置 0
    // 开始时默认 0, 不是空格
    int isChar = 0;

    // 逐个字节遍历 字符
    for(int i = 0; i < cmd_size; i ++){
        // 获取一个字符
        char c = cmd_java[i];

        //LOGI("遍历 %d . %c , cmd_size = %d", i, c, cmd_size);

        switch (c) {
            case ' ': // 判断是否是空格
            case '\\t': // 判断是否是 TAB 空格
                if(isChar){
                    // 如果上一个字符不是空格 , 则需要结束当前的字符串
                    argArray[str_index][char_index++] = '\\0';
                    // 字符串索引自增 1
                    str_index ++;
                    // 字符串内字符索引归零
                    char_index = 0;
                    // 设置当前的字符为空格标志位 1
                    isChar = 0;
                }else{
                    // 如果之前是空格, 那么现在也是空格 ,
                    // 说明命令中有多个空格 , 此处不做任何处理
                }
                break;
            default:
                isChar = 1;
                // 将当前字符放入数组中
                argArray[str_index][char_index++] = c;
                break;
        }
    }

    // 如果最后一位不是空格 , 则需要手动将最后一个字符串写入到数组中
    if (cmd_java[cmd_size - 1] != ' ' && cmd_java[cmd_size - 1] != '\\t') {
        // 如果上一个字符不是空格 , 则需要结束当前的字符串
        argArray[str_index][char_index++] = '\\0';
        // 字符串索引自增 1
        str_index ++;
    }

    // 统计字符串个数
    argCount = str_index;

    // 拼装字符串数组
    char *args[] = {0};
    for (int i = 0; i < argCount; ++i) {
        args[i] = argArray[i];
        // 打印字符串数组
        LOGI("%d . %s", i, args[i]);
    }

    // 量参数传入 main 函数
    main(argCount, args);

    // 释放 Java 字符串以及 C 字符串
    env->ReleaseStringUTFChars(cmd, cmd_java);

    LOGI("7zr 命令执行完毕 !");
}


2、完整 java 代码


package kim.hsl.a7_zip

import android.os.Build
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import java.io.*

class MainActivity : AppCompatActivity() {
    companion object {
        val TAG = "MainActivity"

        init {
            System.loadLibrary("native-lib")
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        copy7zr()

        compress7z()

        uncompress7z()

        compress7zJni()
    }

    /**
     * 将 7zr 文件拷贝到应用私有目录
     */
    fun copy7zr() {
        Log.i(TAG, "开始拷贝 7zr 文件")

        // /data/user/0/kim.hsl.a7_zip/files/7zr
        var exeFile = File(filesDir, "7zr")
        Log.i(TAG, "filesDir = ${filesDir.absolutePath} , exeFile = ${exeFile.absolutePath}")

        // 查看该文件是否存在, 如果存在设置该文件可执行
        // 如果不存在 , 拷贝文件
        if (exeFile.exists()) {
            exeFile.setExecutable(true)
            Log.i(TAG, "内置存储空间存在该 /data/user/0/kim.hsl.a7_zip/files/7zr 文件")
            return
        } else {
            Log.i(TAG, "内置存储空间不存在 7zr 可执行文件 , 开始拷贝文件")
        }

        // 如果不存在 , 拷贝文件
        var inputStream: InputStream = assets.open("libs/arm64-v8a/7zr")
        // /data/user/0/kim.hsl.a7_zip/files/7zr
        var fileOutputStream: FileOutputStream = FileOutputStream(exeFile)

        Log.i(TAG, "Build.CPU_ABI = ${Build.CPU_ABI}")

        // 不同 CPU 架构拷贝不同的可执行程序
        if (Build.CPU_ABI.startsWith("armeabi-v7a")) {
            inputStream = assets.open("libs/armeabi-v7a/7zr")

        } else if (Build.CPU_ABI.startsWith("arm64-v8a")) {
            inputStream = assets.open("libs/arm64-v8a/7zr")

        } else if (Build.CPU_ABI.startsWith("x86")) {
            inputStream = assets.open("libs/x86/7zr")

        } else if (Build.CPU_ABI.startsWith("x86_64")) {
            inputStream = assets.open("libs/x86_64/7zr")
        }

        // 拷贝文件
        var buffer: ByteArray = ByteArray(1024)
        var readCount = inputStream.read(buffer);
        while (readCount != -1) {
            fileOutputStream.write(buffer)
            readCount = inputStream.read(buffer);
        }
        fileOutputStream.flush()
        fileOutputStream.close()

        Log.i(TAG, "拷贝 7zr 文件结束")
    }

    /**
     * 使用 7zr 进行压缩
     */
    fun compress7z() {
        // /data/user/0/kim.hsl.a7_zip/files/7zr
        var exeFile = File(filesDir, "7zr")
        // 执行前赋予可执行权限
        exeFile.setExecutable(true)

        var file_7z = File("${filesDir.absolutePath}/files.7z")
        if(file_7z.exists()){
            file_7z.delete()
        }

        var cmd = "${exeFile.absolutePath} a ${filesDir.absolutePath}/files.7z ${filesDir.absolutePath} -mx=9 -t7z"
        Log.i(TAG, "压缩命令 : $cmd")

        var process: Process = Runtime.getRuntime().exec(cmd)

        // 读取命令执行过程数据
        var reader = BufferedReader(InputStreamReader(process.inputStream))
        while (true) {
            val line = reader.readLine()
            if (line != null) {
                Log.i(TAG, "$line")
            }else{
                break
            }
        }
        reader.close()

        val exitValue = process.exitValue()
        Log.i(TAG, "压缩文件 , 执行完毕 , exitValue = $exitValue")
    }

    /**
     * 判定命令是否执行完毕
     * 调用 process.exitValue 方法 , 如果没有执行完毕 , 会抛异常,
     * 如果执行完毕会返回一个确定的值
     */
    fun isComplete(process: Process): Boolean {
        try {
            // 已经执行完毕
            process.exitValue()
            return true
        } catch (e: IllegalThreadStateException) {
            // 未执行完毕
            return false
        }
    }

    /**
     * 使用 7zr 进行解压缩
     */
    fun uncompress7z() {
        // /data/user/0/kim.hsl.a7_zip/files/7zr
        var exeFile = File(filesDir, "7zr")
        // 执行前赋予可执行权限
        exeFile.setExecutable(true)

        // 删除解压目录
        var unzip_file = File("${filesDir.absolutePath}/unzip_file")
        if(unzip_file.exists()){
            recursionDeleteFile(unzip_file)
        }

        var cmd = "${exeFile.absolutePath} x ${filesDir.absolutePath}/files.7z -o${filesDir.absolutePath}/unzip_file"
        Log.i(TAG, "解压缩命令 : $cmd")

        var process: Process = Runtime.getRuntime().exec(cmd)

        // 读取命令执行过程数据
        var reader = BufferedReader(InputStreamReader(process.inputStream))
        while (true) {
            val line = reader.readLine()
            if (line != null) {
                Log.i(TAG, "$line")
            }else{
                break
            }
        }
        reader.close()

        val exitValue = process.exitValue()
        Log.i(TAG, "解压缩文件 , 执行完毕 , exitValue = $exitValue")
    }

    /**
     * 递归删除文件
     */
    fun recursionDeleteFile(file: File) {
        if (file.isDirectory) {
            // 如果是目录 , 则递归删除
            file.listFiles().forEach {
                // ForEach 循环删除目录
                recursionDeleteFile(it)
            }
        } else {
            // 如果是文件直接删除
            file.delete()
        }
    }

    /**
     * 使用 7zr 进行压缩
     */
    fun compress7zJni() {
        // /data/user/0/kim.hsl.a7_zip/files/7zr
        var exeFile = File(filesDir, "7zr")
        // 执行前赋予可执行权限
        exeFile.setExecutable(true)

        // 删除原有的压缩文件, 如果存在
        var file_7z = File("${filesDir.absolutePath}/files_jni.7z")
        if(file_7z.exists()){
            file_7z.delete()
        }

        var cmd = "${exeFile.absolutePath} a ${filesDir.absolutePath}/files_jni.7z ${filesDir.absolutePath} -mx=9 -t7z"
        Log.i(TAG, "Jni 压缩命令 : $cmd")

        // 调用 jni 方法处理压缩文件
        executeCmd(cmd)
    }


    external fun executeCmd(cmd: String): Unit
}


3、执行结果


2021-05-07 13:32:12.520 31022-31022/kim.hsl.a7_zip I/MainActivity: 开始拷贝 7zr 文件
2021-05-07 13:32:12.524 31022-31022/kim.hsl.a7_zip I/MainActivity: filesDir = /data/user/0/kim.hsl.a7_zip/files , exeFile = /data/user/0/kim.hsl.a7_zip/files/7zr
2021-05-07 13:32:12.525 31022-31022/kim.hsl.a7_zip I/MainActivity: 内置存储空间存在该 /data/user/0/kim.hsl.a7_zip/files/7zr 文件
2021-05-07 13:32:12.527 31022-31022/kim.hsl.a7_zip I/MainActivity: 压缩命令 : /data/user/0/kim.hsl.a7_zip/files/7zr a /data/user/0以上是关于Android 安装包优化使用 lib7zr.so 动态库处理压缩文件 ( jni 中 main 函数声明 | 命令行处理 | jni 调用 lib7zr.so 函数库处理压缩文件完整代码 )(代码片的主要内容,如果未能解决你的问题,请参考以下文章

Android 安装包优化使用 lib7zr.so 动态库处理压缩文件 ( 拷贝 lib7zr.so 动态库到 Android Studio 工程 | 配置 build.gradle 构建脚本 )(代

Android 安装包优化使用 lib7zr.so 动态库处理压缩文件 ( jni 中 main 函数声明 | 命令行处理 | jni 调用 lib7zr.so 函数库处理压缩文件完整代码 )(代码片

Android 安装包优化使用 lib7zr.so 动态库处理压缩文件 ( 修改 7zr 交叉编译脚本 Android.mk | 交叉编译 lib7zr.so 动态库 )

Android 安装包优化使用 lib7zr.a 静态库处理压缩文件 ( 交叉编译 lib7zr.a 静态库 | 安卓工程导入静态库 | 配置 CMakeLists.txt 构建脚本 )

Android 安装包优化Android 中使用 SVG 图片 ( 批量转换 SVG 格式图片为 Vector Asset 矢量图资源 )

Android 安装包优化开启资源压缩