为什么使用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跑起来…
编译成功后是这样子的:
编译成功在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 <jni.h>
#include <string>
#include <7zTypes.h>
#include <android/log.h>
#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是可以的。
网友评论