Android集成7z极限压缩

为什么使用7z压缩?

7z和其他压缩库相比拥有最大的压缩率,占用的磁盘空间最小,所以传输占用的带宽也就最小。

7z官方源码:https://sourceforge.net/projects/p7zip/files/

7z、7zr和7za的关系

官方的解析是这样子的:考研英文能力的时候来了

7z uses plugins to handle archives.
7za is a stand-alone executable that handles fewer archive formats than 7z.
7zr is a stand-alone executable.
It is a “light-version” of 7za that only handles 7z archives.
In contrast to 7za, it cannot handle encrypted archives.

如何使用

有两种方式,一种是编译成可执行文件,放在assets目录中打包,
应用启动的时候将assets中的可执行文件拷贝到应用私有目录/data/data/包名下,
然后通过Runtime.getRuntime().exec()运行可执行文件。

还有一种就是编译成静态库或动态库通过jni的方式调用。

可执行文件的方式

值得注意的是:这在Android中并不是官方推荐的。
但这种方式在某些不需要干涉过程的处理需求中是最方便的。美酒虽好,但不要贪杯哦!!!

从 Android 4.1(API 级别 16)开始,Android 的动态链接器支持位置独立的可执行文件 (PIE)。
从 Android 5.0(API 级别 21)开始,可执行文件需要 PIE。要使用 PIE 构建可执行文件,请设置 -fPIE 标志。此标志增大了通过随机化代码位置来利用内存损坏缺陷的难度。

默认情况下,如果项目针对 android-16 或更高版本,ndk-build 会自动将此值设置为 true。
当然您可以手动将其设置为 true 或 false。此标志仅适用于可执行文件。

7z库很贴心,NDK所需的mk文件都给我们写好了,我们只需要跑ndk-build命令即可,这点必须点赞 (当然,你需要配置好你的NDK环境)

首先,我们cd进去7z源码目录的CPP/CPP\ANDROID\7zr\jni下,
mk文件默认的是编译可执行文件,我们直接ndk-build跑起来…

编译成功后是这样子的:

Android集成7z极限压缩_第1张图片

在jni的同级目录下生成了一个libs目录,里面就是我们所需的执行文件。我们拷贝到assets目录中打包,
应用启动的时候拷贝到应用私有目录/data/data/包名下,
然后通过Runtime.getRuntime().exec()执行命令行即可。

动态库/静态库方式

要编译动态库或者静态库,我们需要修改一下Android.mk文件:
在文件的最后,注释掉这两句

#LOCAL_CFLAGS += -fPIE  
#LOCAL_LDFLAGS += -fPIE -pie  

然后将include $(BUILD_EXECUTABLE)修改为include $(BUILD_STATIC_LIBRARY)
或者include $(BUILD_SHARED_LIBRARY),这取决于你要编译成静态库还是动态库。

这里以静态库为例,最终文件如下:

# Needed since ANDROID 5, these programs run on android-16 (Android 4.1+)
# pie是给可执行程序使用的flag
# ndk读取mk文件编译动态库不需要指定pie
# LOCAL_CFLAGS += -fPIE
# LOCAL_LDFLAGS += -fPIE -pie
# 生成可执行文件
#include $(BUILD_EXECUTABLE)
# 生成动态库
include $(BUILD_SHARED_LIBRARY)
# 生成静态库
# include $(BUILD_STATIC_LIBRARY) 

跑一下ndk-build,编译成功后是这样子的,so文件同样在libs目录下。

开始ndk集成

首先将编译出来的lib7zr.so拷贝到项目/main/jniLibs目录下。

然后在cpp目录下创建lib7zr目录

将7z源码目录中的C目录和CPP目录拷贝到项目的lib7zr目录下。

编辑CMakeLists.txt。这里有一个问题,我们要使用编译出来的so库,怎么知道需要哪些头文件呢?

这里有一个方法就是查看Android.mk的LOCAL_CFLAGS所有-I指定的目录就是所需的头文件。

#根据Android.mk引入头文件
#设置头文件查找目录
include_directories(
        lib7zr/CPP/7zip/Archive
        lib7zr/CPP/7zip/Archive/7z
        lib7zr/CPP/7zip/Archive/BZip2
        lib7zr/CPP/7zip/Archive/Common
        lib7zr/CPP/7zip/Archive/GZip
        lib7zr/CPP/7zip/Archive/Cab
        lib7zr/CPP/7zip/Archive/Lzma
        lib7zr/CPP/7zip/Archive/Tar
        lib7zr/CPP/7zip/Archive/Zip
        lib7zr/CPP/7zip/Archive/Split
        lib7zr/CPP/7zip/Archive/Z
        lib7zr/CPP/7zip/Compress
        lib7zr/CPP/7zip/Crypto
        lib7zr/CPP/7zip/UI/Console
        lib7zr/CPP/7zip/UI/Common
        lib7zr/CPP/Windows
        lib7zr/CPP/Common
        lib7zr/CPP/7zip/Common
        lib7zr/C
        lib7zr/CPP/myWindows
        lib7zr/CPP
        lib7zr/CPP/include_windows
)

然后设置查找库的路径:

#Android 6.0以后使用这种方式,设置库查找目录
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -L${CMAKE_SOURCE_DIR}/../jniLibs/${CMAKE_ANDROID_ARCH_ABI}")
安卓6.0以前,我们可能会用以下这种方式链接动态库,但是6.0之后动态库可能不行了,静态库库可以

#6.0及以后 动态库不行,静态库可以
#add_library(
#        7zr
#        SHARED
#        IMPORTED
#)
#set_target_properties()

然后就是NDK层编码实现:主要是

1、声明main函数
2、解析指令
3、调用main函数(int MY_CDECL main)

主要代码如下:

#include 
#include 
#include <7zTypes.h>
#include 

#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, "7zr",__VA_ARGS__);

//表示这个函数在别的地方实现
extern int MY_CDECL main
        (
#ifndef _WIN32
        int numArgs, char *args[]
#endif
);

void strArgs(const char *cmd, int &args, char pString[66][1024]);

extern "C"
JNIEXPORT jint JNICALL
Java_com_flyer_compress7z_Jni7zr_excuteCmd(JNIEnv *env, jclass clazz, jstring cmd_) {
    const char *cmd = env->GetStringUTFChars(cmd_, 0);
    //7zr a /sdcard/7-Zip.7z /sdcard/7-Zip -mx=9
    int numArgs;
    char temp[66][1024] = {0};
    //分割字符串 将值填入变量
    strArgs(cmd, numArgs, temp);
    char *args[] = {0};
    for (int i = 0; i < numArgs; ++i) {
        args[i] = temp[i];
        LOGE("%s", args[i]);
    }
    env->ReleaseStringUTFChars(cmd_, cmd);
    return main(numArgs, args);
}

void strArgs(const char *cmd, int &numArgs, char argv[66][1024]) {
    //获得字符串长度
    int size = strlen(cmd);
    //argv的两个下标
    int a = 0, b = 0;
    //0 = false
    //记录是否进入空格
    //7zr         a /sdcard/7-Zip.7z /sdcard/7-Zip -mx=9
    // argv[0]="7zr\0"
    //argv[1]="a\0"
    //7zr\0
    int inspace = 0;
    for (int i = 0; i < size; ++i) {
        char c = cmd[i];
        switch (c) {
            case ' ':
            case '\t':
                if (inspace) {
                    //字符串结束符号
                    argv[a][b++] = '\0';
                    a++;
                    //加入下一个有效字符前 复原
                    b = 0;
                    inspace = 0;
                }
                break;
            default:
                //如果是字符
                inspace = 1;
                argv[a][b++] = c;
                break;
        }
    }
    //7zr a /sdcard/7-Zip.7z /sdcard/7-Zip -mx=9
    //如果最末尾不是空格 就不会进入  case ' ': case '\t': 补上最后一个结束符
//    if(inspace){}
    if (cmd[size - 1] != ' ' && cmd[size - 1] != '\t') {
        argv[a][b] = '\0';
        a++;
    }
    numArgs = a;
}

遇到的问题

1.尝试使用最新版本的NDK编译,但是编译不通过,demo中的库是使用ndkr14编译通过的;
2.尝试编译7z,但是在安卓中运行命令行的时候报错,时间问题没有查找原因,7zr是可以的。

更多Android进阶技术请扫码关注公众号
微信扫码关注

你可能感兴趣的:(Android集成7z极限压缩)