使用zlib解压.apk/.zip文件(Windows&Ubuntu)

本文详细介绍了如何使用ZLib库在Windows和Ubuntu环境下解压ZIP文件,包括配置环境、编写代码的具体步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

前面讲过,解压出apk文件的内容是进行apk分析的第一步,而.apk文件其实就是.zip文件。也就是说首先要实现zip文件的解压缩。本文将分别介绍在Windows和Ubuntu下如何使用zlib这一开源库对zip文件进行解压。

Git

现在所有的模块源代码位于github:https://github.com/LeeHDsniper/APKAnalyserModules

ZLIB

zlib is designed to be a free, general-purpose, legally unencumbered – that is, not covered by any patents – lossless data-compression library for use on virtually any computer hardware and operating system. The zlib data format is itself portable across platforms.

zlib是一个C语言编写的、开源的、几乎在所有的计算机和操作系统上都使用的数据压缩库,更多的介绍请查看zlib官方网站;
简而言之,zlib其实是一种数据压缩算法,zip文件压缩只是这种算法的应用之一。
关于zlib压缩算法参考这篇文章:http://blog.jobbole.com/76676/
在zlib的源码中,有一个\contrib\minizip文件夹,这个文件夹中封装了zlib算法,使得其可以用来解压和压缩zip格式的文件。

下载ZLIB

zlib目前的版本是1.2.8,要下载的内容包括源码和编译好的dll文件(也可以自己使用源码编译)。
zlib source code:http://zlib.net/zlib-1.2.8.tar.gz
zlib compiled DLL:http://zlib.net/zlib128-dll.zip
下载完成后将这两个文件解压。

zlib算法和zip文件

zlib算法

网上有很多关于zlib的文章描述的都是如何使用zlib进行压缩和解压字节流,也就是使用

compress (Bytef *dest,   uLongf *destLen, const Bytef *source, uLong sourceLen)

来对字节流进行压缩,使用

 uncompress (Bytef *dest,   uLongf *destLen,const Bytef *source, uLong sourceLen)

来对字节流进行解压缩。
这两个函数的操作对象就是读入内存的字节流。下面给出一个使用这两个函数的例子(暂时不用关心如何正确配置zlib库使得程序顺利编译)

#include <iostream>
#include <zlib.h>
#include <cstring>
using namespace std;
int main()
{
    char str[11] = "HelloWorld";
    Byte *Source ;
    Byte *Destination;
    unsigned long DestLen ;
    unsigned long SourLen = strlen(str)+1;
    DestLen = compressBound(SourLen);
    Source = new Byte[SourLen];
    Destination = new Byte[DestLen];
    Source = (Byte *)str;
    int err = compress(Destination, &DestLen, Source, SourLen);
    if (err == 0)
    {
        cout <<"压缩完成"<< endl;
        cout << "原字符串:" << Source << endl;
        cout << "压缩后字符串:" << Destination << endl;
        cout << "压缩后字符串长度:" << DestLen << endl;
    }
    else
    {
        cout << "压缩失败" << endl;
    }
    Byte *uncompressSource;
    Byte *uncompressDest;
    unsigned long uncompressSourceLen = DestLen;
    unsigned long uncompressDestLen = SourLen;
    uncompressSource = Destination;
    uncompressDest = new Byte[uncompressDestLen];
    err = uncompress(uncompressDest, &uncompressDestLen, uncompressSource, uncompressSourceLen);
    if (err == 0)
    {
        cout << "解压完成" << endl;
        cout << "解压前字符串:" << uncompressSource << endl;
        cout << "解压后字符串:" << uncompressDest << endl;
        cout << "解压后后字符串长度:" << uncompressDestLen << endl;
    }
    else
    {
        cout << "解压失败" << endl;
    }
    system("pause");
    return 0;
}

运行结果:
这里写图片描述
至于为什么压缩后的字节长度比压缩前还长,那是因为我们压缩的字节不够长(11个Byte),如果有足够长的字节,那么压缩效果就可以看出来了。
当然,你也可以把“HelloWorld”这个字符串替换成从外部文件中读取的一段字节流。
但是这个结果并不是我们想要的,我们要做的事解压zip文件。

zip文件

前面提到,zip文件压缩和解压只是对zlib算法的一种应用。事实上,在前面的对字节流的压缩完成后,如果直接将压缩后的数据写入到一个.zip文件中,使用一般的zip解压缩软件是不能打开的。因为zip文件有一定的文件结构。关于zip文件的结构网上有很多文章,就不再赘述了。
要使用zlib库对zip文件进行压缩和解压,就需要我们在zlib库的基础上再做额外的工作,使得其符合zip文件结构。感谢zlib的开发人员,这个工作他们已经做好了。
在zlib的源码中,找到\contrib\minizip文件夹,这个文件夹中有两个头文件zip.h和unzip.h,这就是对zlib库的封装。还有两个例子minizip.c和miniunz.c,描述了压缩和解压zip文件的基本操作。

在VS2013中实现解压zip文件

配置

  1. 下载前面zlib的源码和编译好的dll文件,解压。
  2. 使用vs2013新建一个C++空项目
  3. 将zlib源码中的minizip文件夹中的
    • ioapi.h
    • ioapi.c
    • unzip.h
    • unzip.c
      复制到项目目录中:
      这里写图片描述
  4. 将zlib的dll文件中/lib文件夹的zdll.lib文件复制到项目目录中
  5. 在VS2013中新建一个源文件
  6. 将ioapi.c和unzip.c添加到项目中
    这里写图片描述
  7. 打开项目属性
    这里写图片描述
  8. 将配置属性->链接器->常规->附加库目录修改为刚才添加zdll.lib文件所在的目录
    这里写图片描述
  9. 在配置属性->链接器->输入->附加依赖项中,添加zdll.lib
    这里写图片描述
  10. 将zlib的DLL文件中zlib1.dll复制到项目调试生成的.exe文件所在的文件夹。

代码

头文件
#include <iostream>
#include <zlib.h>
#include "unzip.h"
#include <cstring>
#include <fstream>
#include <direct.h>
using namespace std;

direct.h头文件用来创建目录
C++创建目录的操作在Windows和Unix下是不同的。
在Windows下,要

#include<direct.h>

使用

int _mkdir(_In_z_ const char * _Path)

函数。
在Unix下,要

#include<sys/stat.h>
#include<sys/types.h>

使用

int mkdir(const char *pathname, mode_t mode)

函数。
参考这两篇文章:

API分析

涉及到的minizip库中的解压缩函数有:

unzFile unzOpen64(const char *path);
int unzClose(unzFile file);
int unzGetGlobalInfo64(unzFile file, unz_global_info *pglobal_info);
int unzGoToNextFile(unzFile file);
int unzGetCurrentFileInfo64(unzFile file, unz_file_info *pfile_info, char *szFileName, uLong fileNameBufferSize, void *extraField, uLong extraFieldBufferSize, char *szComment, uLong commentBufferSize);
int unzOpenCurrentFile(unzFile file);
int unzCloseCurrentFile(unzFile file);
int unzReadCurrentFile(unzFile file, voidp buf, unsigned len);

一个解压缩过程包括:
1. 使用unzOpen64函数打开一个zip文件;
2. 使用unzGetGlobalInfo64函数读取zip文件的全局信息,保存在一个unz_global_info64类型的结构体中;
3. 使用unz_global_info64结构体中的number_entry变量循环读取zip文件中的文件,并进行读取、解压、写入、保存等操作
4. 在unzFile结构中,有一个指针指向当前文件,默认指在第一个文件。操作完一个文件后,使用unzCloseCurrentFile函数关闭这个文件,再使用unzGoToNextFile函数使得指针指在下一个文件上,再对该文件进行操作;
5. 解压完整个zip文件后,使用unzClose函数释放资源。

编写main函数
int main()
{
    unzFile zfile;//定义一个unzFile类型的结构体zfile
    //定义zip文件的路径,可以使用相对路径
    char filepath[]="extract/test.zip";
    //调用unzOpen64()函数打开zip文件
    zfile = unzOpen64(filepath);
    if (zfile == NULL)
    {
        cout << filepath << "[INFO] 打开压缩文件失败" << endl;
        return -1;
    }
    else
    {
        cout << "[INFO] 成功打开压缩文件" << endl;
    }
    unz_global_info64 zGlobalInfo;
    //unz_global_info64是一个结构体
    //其中最重要的是number_entry成员
    //这个变量表示了压缩文件中的所有文件数目(包括文件夹、
    //文件、以及子文件夹和子文件夹中的文件)
    //我们用这个变量来循环读取zip文件中的所有文件
    if (UNZ_OK != unzGetGlobalInfo64(zfile, &zGlobalInfo))
    //使用unzGetGlobalInfo64函数获取zip文件全局信息
    {
        cout << "[ERROR] 获取压缩文件全局信息失败" << endl;
        return -1;
    }
    //循环读取zip包中的文件,
    //在extract_currentfile函数中
    //进行文件解压、创建、写入、保存等操作
    for (int i = 0; i < zGlobalInfo.number_entry; i++)
    {
        //extract_currentfile函数的第二个参数指将文件解压到哪里
        //这里使用extract/,表示将其解压到运行目录下的extract文件夹中
        int err = extract_currentfile(zfile, "extract/");
        //关闭当前文件
        unzCloseCurrentFile(zfile);
        //使指针指向下一个文件
        unzGoToNextFile(zfile);
    }
    //关闭压缩文件
    unzClose(zfile);
    system("pause");
    return 0;
}
编写extract_currentfile函数

输入参数:
1. unzFile zfile——要解压的unzFile类型的变量
2. char * extractdirectory——解压的目标文件夹路径即解压到哪里
Return:
int

这个函数的流程如下:
1. 使用

unzGetCurrentFileInfo64(zfile, &zFileInfo, fileName_WithPath, fileName_BufSize, NULL, 0, NULL, 0)

函数获取当前zfile文件中指针指向的文件的文件信息,保存在unz_file_info64类型的结构体zFileInfo变量中,获取当前文件相对于zip文件的相对路径和文件名,保存在字符串fileName_WithPath中;
2. 修改fileName_WithPath变量,使得其包含解压的目标文件夹
3. 判断当前文件是目录还是文件,如果是目录则创建目录,如果是文件则解压文件
4. 如果是文件,则使用 unzOpenCurrentFile函数打开当前文件
5. 使用fstream库以二进制流的形式创建当前文件
6. 使用unzReadCurrentFile读取当前文件内容到一个字符串中
7. 将字符串写入创建好的文件

int extract_currentfile(unzFile zfile,char * extractdirectory)
{
    unsigned int fileName_BufSize = 512;
    char *fileName_WithPath=new char[fileName_BufSize];
    char *p,*fileName_WithoutPath;
    unz_file_info64 zFileInfo;
    p = fileName_WithoutPath = fileName_WithPath;
    if (UNZ_OK != unzGetCurrentFileInfo64(zfile,
     &zFileInfo, fileName_WithPath,
      fileName_BufSize, NULL, 0, NULL, 0))
    {
        cout << "[ERROR] 获取当前文件信息失败" << endl;
        return -1;
    }
    char *temp = new char[fileName_BufSize];
    //修改fileName_WithPath,使得extractdirectory加在其前面
    strcpy_s(temp, 512, extractdirectory);
    strcat_s(temp, 512, fileName_WithPath);
    fileName_WithPath = temp;
    //判断当前文件是目录还是文件
    while ((*p) != '\0')
    {
        if (((*p) == '/') || ((*p) == '\\'))
            fileName_WithoutPath = p + 1;
        p++;
    }
    if (*fileName_WithoutPath == '\0')
    {
        cout << "[INFO] " << "成功读取当前目录:" << fileName_WithPath << endl;
        cout << "[INFO] " << "开始创建目录:" << fileName_WithPath << endl;
        //创建目录
        int err = _mkdir(fileName_WithPath);
        if (err != 0)
            cout << "[ERROR] " << "创建目录" 
            << fileName_WithPath << "失败" << endl;
        else
            cout << "[INFO] " << "成功创建目录" 
            << fileName_WithPath << endl;
    }
    else
    {
        cout << "[INFO] " << "成功读取当前文件:" 
        << fileName_WithoutPath << endl;
        cout << "[INFO] " << "开始解压当前文件:" 
        << fileName_WithoutPath << endl;
        //打开当前文件
        if (UNZ_OK != unzOpenCurrentFile(zfile))
        {
            //错误处理信息  
            cout <<"[ERROR] "<< "打开当前文件" << 
            fileName_WithoutPath << "失败!" << endl;
        }
        //定义一个fstream对象,用来写入文件
        fstream file;
        file.open(fileName_WithPath, ios_base::out | ios_base::binary);
        ZPOS64_T fileLength = zFileInfo.uncompressed_size;
        //定义一个字符串变量fileData,读取到的文件内容将保存在该变量中
        char *fileData = new char[fileLength];
        //解压缩文件  
        ZPOS64_T err = unzReadCurrentFile(zfile, (voidp)fileData, fileLength);
        if (err<0)
            cout << "[ERROR] " << "解压当前文件" 
            << fileName_WithoutPath << "失败!" << endl;
        else
            cout << "[INFO] " << "解压当前文件"  
            << fileName_WithoutPath << "成功!" << endl;
        file.write(fileData, fileLength);
        file.close();
        free(fileData);
    }
    return 0;
}
运行前

需要在运行目录下创建一个extract文件夹,将要解压的文件复制到该文件夹下,并对代码中的压缩文件夹路径进行修改
这里写图片描述
这里写图片描述

运行

这里写图片描述
在/extract文件夹中查看解压好的文件:
这里写图片描述

在Ubuntu中实现解压zip文件

配置

  1. 安装gcc&g++
  2. 下载zlib源码并解压
  3. 进入zlib源码所在文件夹,打开终端
  4. 运行./configure
    这里写图片描述
  5. 运行make
    这里写图片描述
  6. 运行sudo make install
    这里写图片描述
  7. 关于安装zlib的更多信息,参见:http://www.linuxidc.com/Linux/2012-06/61982.htm

代码

代码和windows下唯一的区别在于前面提到的创建文件夹要包含的头文件和使用的函数不同。
创建一个zlibtest.cpp文件,输入下列代码

#include <iostream>
#include <zlib.h>
#include "unzip.h"
#include <cstring>
#include <fstream>
#include <sys/stat.h>
#include <sys/types.h>
using namespace std;
int extract_currentfile( unzFile zfile, char *extractdirectory )
{
    unsigned int    fileName_BufSize    = 512;
    char        *fileName_WithPath  = new char[fileName_BufSize];
    char        *p, *fileName_WithoutPath;
    unz_file_info64 zFileInfo;
    p = fileName_WithoutPath = fileName_WithPath;
    if ( UNZ_OK != unzGetCurrentFileInfo64( zfile,
                        &zFileInfo, fileName_WithPath, fileName_BufSize, NULL, 0, NULL, 0 ) )
    {
        cout << "[ERROR] 获取当前文件信息失败" << endl;
        return(-1);
    }
    char *temp = new char[fileName_BufSize];
    strcpy( temp, extractdirectory );
    strcat( temp, fileName_WithPath );
    fileName_WithPath = temp;
    while ( (*p) != '\0' )
    {
        if ( ( (*p) == '/') || ( (*p) == '\\') )
            fileName_WithoutPath = p + 1;
        p++;
    }
    if ( *fileName_WithoutPath == '\0' )
    {
        cout << "[INFO] " << "成功读取当前目录:" << fileName_WithPath << endl;
        cout << "[INFO] " << "开始创建目录:" << fileName_WithPath << endl;
        int err = mkdir( fileName_WithPath, S_IRWXU | S_IRWXG | S_IROTH );
        if ( err != 0 )
            cout << "[ERROR] " << "创建目录" << fileName_WithPath << "失败" << endl;
        else
            cout << "[INFO] " << "成功创建目录" << fileName_WithPath << endl;
    }else  {
        cout << "[INFO] " << "成功读取当前文件:" << fileName_WithoutPath << endl;
        cout << "[INFO] " << "开始解压当前文件:" << fileName_WithoutPath << endl;
        if ( UNZ_OK != unzOpenCurrentFile( zfile ) )
        {
/* 错误处理信息 */
            cout << "[ERROR] " << "打开当前文件" << fileName_WithoutPath << "失败!" << endl;
        }
        fstream file;
        file.open( fileName_WithPath, ios_base::out | ios_base::binary );
        ZPOS64_T    fileLength  = zFileInfo.uncompressed_size;
        char        *fileData   = new char[fileLength];
/* 解压缩文件 */
        ZPOS64_T err = unzReadCurrentFile( zfile, (voidp) fileData, fileLength );
        if ( err < 0 )
            cout << "[ERROR] " << "解压当前文件" << fileName_WithoutPath << "失败!" << endl;
        else
            cout << "[INFO] " << "解压当前文件" << fileName_WithoutPath << "成功!" << endl;
        file.write( fileData, fileLength );
        file.close();
        free( fileData );
    }
    return(0);
}


int main()
{
    unzFile zfile;
    char    filepath[] = "./extract/test.zip";
    zfile = unzOpen64( filepath );
    if ( zfile == NULL )
    {
        cout << "[ERROR] 文件不存在" << endl;
        return(-1);
    }else  {
        cout << "[INFO] 成功打开压缩文件" << endl;
    }
    unz_global_info64 zGlobalInfo;
    if ( UNZ_OK != unzGetGlobalInfo64( zfile, &zGlobalInfo ) )
    {
        cout << "[ERROR] 获取压缩文件全局信息失败" << endl;
        return(-1);
    }
    for ( int i = 0; i < zGlobalInfo.number_entry; i++ )
    {
        int err = extract_currentfile( zfile, "extract/" );
        unzCloseCurrentFile( zfile );
        unzGoToNextFile( zfile );
    }
    return(0);
}

编译

  1. 将zlib源码中/contrib/minizip文件夹下的文件:
    • ioapi.c
    • ioapi.h
    • unzip.c
    • unzip.h
      复制到zlibtest.cpp所在的文件夹下
  2. 由于ioapi.c和unzip.c都是C语言文件,不能直接和编写的zlibtest.cpp文件一起编译。所以首先编译出它们的.o目标文件
  3. 在项目所在文件夹中打开终端
  4. 运行gcc ./ioapi.c -c得到ioapi.o文件
  5. 运行gcc ./unzip.c -c得到unzip,o文件
  6. 运行
g++ ./ioapi.o ./unzip.o ./zlibtest.cpp -o zlibtest -L /usr/local/lib/libz.a /usr/local/lib/libz.so

     得到zlibtest可执行文件
     7. 在zlibtest.cpp所在文件夹中建立一个extract文件夹,将要解压的test.zip复制到里面
     8. 运行./zlibtest,zip文件就会解压到extract文件中
这里写图片描述

凡事多问为什么

  1. 首先,ioapi,c和unzip.c必须和zlibtest.cpp联合编译才能成功,因为解压的函数都是在这两个文件中实现的
  2. 在上面的第6步的命令中:
g++ ./ioapi.o ./unzip.o ./zlibtest.cpp -o zlibtest -L /usr/local/lib/libz.a /usr/local/lib/libz.so

-L参数后面链接了一个静态库libz.a,一个动态库libz.so
我是怎么知道要链接这两个库的呢(不链接会报错)?我是天才
查看刚才安装zlib的过程就可以看到,在sudo make install这一步,
这里写图片描述
将一些库文件复制到了/usr/local/lib文件夹中,其中就包含了两个文件:libz.a和libz.so.1.2.8,但是在编译命令中我使用了libz.so而并没有使用libz.so.1.2.8.,为什么编译还是通过了呢?
那么我改用libz.so.1.2.8来编译,输入

g++ ./ioapi.o ./unzip.o ./zlibtest.cpp -o zlibtest -L /usr/local/lib/libz.a /usr/local/lib/libz.so.1.2.8

来编译,还是通过了。这就很奇怪了,libz.so是从哪里来的?
首先找到libz.so所在的文件夹也就是/usr/local/lib下:
这里写图片描述
这里竟然有三个文件:libz.so,libz.so.1,libz.so.1.2.8!打开它们的属性,这三个文件竟然是同一时间创建的!而且具有相同的文件大小!
这里写图片描述
这就表示,在创建libz.so.1.2.8的时候,其它两个文件就一起创建了。但是在sudo make install这一步并没有出现创建其它两个文件的命令,只有将libz.so.1.2.8复制到/usr/local/lib下的命令。
那么很可能就在前一步也就是运行make命令的时候创建的。
果然在make这一条命令运行的终端输出中发现了这个:
这里写图片描述
这两条命令:
ln -s libz.so.1.2.8 libz.so
ln -s libz.so.1.2.8 libz.so.1
就是创建libz.so和libz.so.1的命令。ln的含义就是创建一个链接,可以理解为快捷方式。
但是为什么要创建这两个链接呢?
在gcc的命令参数中,-l和-L是用来链接库文件的,关于这两个参数的用法,参见文章:
http://www.cnblogs.com/benio/archive/2010/10/25/1860394.html
那么,我就可以使用-lz来替代-L参数了,如下:

g++ ./ioapi.o ./unzip.o ./zlibtest.cpp -o zlibtest -lz

这里写图片描述
编译成功。
知道了-l的用法,创建libz.so.1.2.8的链接libz.so和libz.so.1的原因就很清楚了,就是为了-lz命令的使用。

在Ubuntu中实现解压apk文件(Update-2016.5.11)

当我使用上面的程序解压apk文件的时候出现了问题,解压不出文件夹。
经过调试,发现apk文件在解压过程中,无法拿到apk文件中的目录信息,导致目录无法创建,目录中的文件也就无法解压了。
经过排查,我将判断apk文件中的当前项目是否是文件夹的逻辑修改了:使用当前的fileName_WithPath,解析出当前文件夹的路径,然后创建。但是这样做文件夹倒是创建出来了,不过全部是乱码,而且很多信息是错误的,创建出的文件夹的名称中包含无效的编码等信息。
我将apk文件和能正常解压缩出文件夹的zip文件的十六进制做了比较,发现在文件头部的压缩版本字段,apk是0x0800,也就是8,表示是Deflate,而zip是0x0000,也就是0,表示是uncompressed。
但是经过查看zlib的源代码,并没有发现能够解决这个问题的答案。
最后,我发现,当我将目录结构手动创建好,就能够成功解压了。
于是我对代码进行了修改:

#include <iostream>
#include <cstring>
#include <fstream>
#include <dirent.h>
#include <sys/stat.h>
#include <sys/types.h>
#include "unzip.h"
#include <zlib.h>
using namespace std;
void mymkdir(char * file_Path)
{
    DIR *dp;
    if ((dp = opendir(file_Path)) == NULL)
    {
        int err = mkdir(file_Path,S_IRWXU|S_IRWXG|S_IROTH);
        if(err!=0)
        {
            int end=strlen(file_Path);
            char *p=&file_Path[end-1];
            while ((*p) != '/')
            {
                p--;
            }
            int length=(int)strlen(file_Path)-(int)strlen(p);
            char *temp=new char[length];
            memcpy(temp,file_Path,length);
            mymkdir(temp);
            err = mkdir(file_Path,S_IRWXU|S_IRWXG|S_IROTH);
        }
    }
    closedir(dp);
}
int makedirectory(unzFile zfile,char *extractdirectory)
{
    unsigned int fileName_BufSize=516;
    char *fileName_WithPath=new char[fileName_BufSize];
    char *file_Path=new char[fileName_BufSize];
    char *p,*fileName_WithoutPath;
    unz_file_info64 zFileInfo;
    if (UNZ_OK != unzGetCurrentFileInfo64(zfile, &zFileInfo, fileName_WithPath, fileName_BufSize, NULL, 0, NULL, 0))
    {
        return -1;
    }
    char *temp = new char[fileName_BufSize];
    strcpy(temp,extractdirectory);
    strcat(temp,fileName_WithPath);
    fileName_WithPath = temp;
    p = fileName_WithoutPath = fileName_WithPath;
    while ((*p) != '\0')
    {
        if (((*p) == '/') || ((*p) == '\\'))
        fileName_WithoutPath = p + 1;
        p++;
    }
    int length=(int)strlen(fileName_WithPath)-(int)strlen(fileName_WithoutPath);
    memcpy(file_Path,fileName_WithPath,length);
    mymkdir(file_Path);
}

int extract_currentfile(unzFile zfile,char *extractdirectory)
{
unsigned int fileName_BufSize=516;
char *fileName_WithPath=new char[fileName_BufSize];
char *p,*fileName_WithoutPath;
unz_file_info64 zFileInfo;

if (UNZ_OK != unzGetCurrentFileInfo64(zfile, &zFileInfo, fileName_WithPath, fileName_BufSize, NULL, 0, NULL, 0))
{
return -1;
}
char *temp = new char[fileName_BufSize];
strcpy(temp,extractdirectory);
strcat(temp,fileName_WithPath);
fileName_WithPath = temp;
p = fileName_WithoutPath = fileName_WithPath;
while ((*p) != '\0')
{
if (((*p) == '/') || ((*p) == '\\'))
fileName_WithoutPath = p + 1;
p++;
}
if (UNZ_OK != unzOpenCurrentFile(zfile))
{
    return -2;
}
fstream file;
file.open(fileName_WithPath, ios_base::out | ios_base::binary);
ZPOS64_T fileLength = zFileInfo.uncompressed_size;
char *fileData = new char[fileLength];
//解压缩文件  
ZPOS64_T err = unzReadCurrentFile(zfile, (voidp)fileData, fileLength);
if (err<0)
{
    return -3;
}
file.write(fileData, fileLength);
file.close();
free(fileData);
return 0;
}

int main()
{
unzFile zfile;
char filepath[]="./test.apk";
zfile=unzOpen64(filepath);
if(zfile==NULL)
{
cout<<"[ERROR] 文件不存在"<<endl;
return -1;
}
else
{
cout << "[INFO] 成功打开压缩文件:" <<filepath<< endl;
}
unz_global_info64 zGlobalInfo;
if (UNZ_OK != unzGetGlobalInfo64(zfile, &zGlobalInfo))
{
cout << "[ERROR] 获取压缩文件全局信息失败" << endl;
return -1;
}
for (int i = 0; i < zGlobalInfo.number_entry; i++)
{
int err = makedirectory(zfile, (char *)"extract/");
unzCloseCurrentFile(zfile);
unzGoToNextFile(zfile);
}
unzGoToFirstFile (zfile);
for (int i = 0; i < zGlobalInfo.number_entry; i++)
{
int err = extract_currentfile(zfile, (char *)"extract/");
unzCloseCurrentFile(zfile);
unzGoToNextFile(zfile);
}
cout<<"[INFO] 解压完成" << endl;
return 0;
}

简单的说就是实现了一个创建文件夹的函数makedirectory,首先调用该函数创建好目录结构,然后再次解压。不过这样做导致解压时间稍微有点增加,但是时间还是比较短的。

结语

既然能够用gcc编译成功,那么在linux下使用QtCreator编写解压的程序就很简单了,因为QtCreator用的也是gcc嘛。
至于如何压缩文件,基本操作也可以在网上找到,重点就在如何配置编译环境。

安卓编译时出现报错,报错如下,情分析原因。报错信息:[ 18% 707/3795] //prebuilts/sdk/current/support:android-support-compat-nodeps aapt2 link [common] FAILED: out/soong/.intermediates/prebuilts/sdk/current/support/android-support-compat-nodeps/android_common/package-res.apk out/soong/.intermediates/prebuilts/sdk/current/support/android-support-compat-nodeps/android_common/gen/proguard.options out/soong/.intermediates/prebuilts/sdk/current/support/android-support-compat-nodeps/android_common/gen/R.jar out/soong/.intermediates/prebuilts/sdk/current/support/android-support-compat-nodeps/android_common/R.txt out/soong/.intermediates/prebuilts/sdk/current/support/android-support-compat-nodeps/android_common/extra_packages rm -rf out/soong/.intermediates/prebuilts/sdk/current/support/android-support-compat-nodeps/android_common/gen/aapt2/R && out/soong/host/linux-x86/bin/aapt2 link -o out/soong/.intermediates/prebuilts/sdk/current/support/android-support-compat-nodeps/android_common/package-res.apk --static-lib --no-static-lib-packages --auto-add-overlay --manifest out/soong/.intermediates/prebuilts/sdk/current/support/android-support-compat-nodeps/android_common/aar/AndroidManifest.xml --auto-add-overlay -I out/soong/.intermediates/frameworks/base/core/res/framework-res/android_common/package-res.apk --java out/soong/.intermediates/prebuilts/sdk/current/support/android-support-compat-nodeps/android_common/gen/aapt2/R --proguard out/soong/.intermediates/prebuilts/sdk/current/support/android-support-compat-nodeps/android_common/gen/proguard.options --output-text-symbols out/soong/.intermediates/prebuilts/sdk/current/support/android-support-compat-nodeps/android_common/R.txt -R @out/soong/.intermediates/prebuilts/sdk/current/support/android-support-compat-nodeps/android_common/aapt2/overlay.list && out/soong/host/linux-x86/bin/soong_zip -write_if_changed -jar -o out/soong/.intermediates/prebuilts/sdk/current/support/android-support-compat-nodeps/android_common/gen/R.jar -C out/soong/.intermediates/prebuilts/sdk/current/support/android-support-compat-nodeps/android_common/gen/aapt2/R -D out/soong/.intermediates/prebuilts/sdk/current/support/android-support-compat-nodeps/android_common/gen/aapt2/R &&out/soong/host/linux-x86/bin/extract_jar_packages -i out/soong/.intermediates/prebuilts/sdk/current/support/android-support-compat-nodeps/android_common/gen/R.jar -o out/soong/.intermediates/prebuilts/sdk/current/support/android-support-compat-nodeps/android_common/extra_packages --prefix '--extra-packages ' runtime: failed to create new OS thread (have 2 already; errno=11) runtime: may need to increase max user processes (ulimit -u) fatal error: newosproc
最新发布
07-19
<think>首先,用户的问题是关于安卓编译时出现的错误:aapt2 link 失败和 'failed to create new OS thread' 错误。我需要分析可能原因及解决方案。 用户提供了引用内容,我需要参考这些引用: - 引用[1]: 提到aapt执行失败,因为缺少GLIBC_2.14版本。错误是/lib64/libc.so.6: version `GLIBC_2.14' not found。 - 引用[2]: 是关于Android源码编译错误的解决方案汇总,提供了一些链接。 - 引用[3]: 错误是/usr/bin/ld: cannot find -lz,导致aapt编译失败。 - 引用[4]: 描述了Aapt的编译方法,涉及资源编译。 用户的问题焦点是aapt2 link失败和线程创建失败错误。用户说:"我想分析安卓编译时报错的原因,特别是与 aapt2 link 和线程创建失败相关的问题"。 关键错误消息: 1. aapt2 link 失败:可能涉及资源链接问题。 2. 'failed to create new OS thread':这表示操作系统无法创建新线程,通常是由于资源限制,如内存不足或进程限制。 我需要结合系统级指令来回答: - 所有行内数学表达式用$...$格式,但这里可能不需要数学表达式,因为主题是安卓编译错误。 - 独立公式用$$...$$格式,但同样,可能不适用。 - LaTeX语法正确:在回答中,如果有数学,要用LaTeX,但这里可能不需要。 - 使用中文回答:整个回答必须用中文。 - 生成相关问题:在回答结束后,添加相关问题部分。 - 引用标识:在回答中引用的段落末尾添加引用标识,如[^1]。 回答结构:帮助用户逐步解决问题,保证真实可靠。 分析可能原因: 1. aapt2 link失败: - 可能原因:资源文件问题、路径错误、权限问题、依赖库缺失。 - 参考引用[1]:GLIBC版本过低,导致aapt无法运行。类似地,aapt2可能需要更新的GLIBC。 - 引用[3]:缺少-lz库,表示zlib库缺失。 - 引用[4]:涉及Aapt的编译过程,可能资源编译错误。 2. 'failed to create new OS thread': - 可能原因:系统资源不足,如内存耗尽、进程数限制(ulimit)、或系统配置问题。 - 在编译安卓时,资源密集型操作可能导致此错误。 综合原因: - aapt2 link失败可能由库依赖或资源问题引起。 - 线程创建失败可能由系统资源限制引起。 解决方案: 1. 检查GLIBC版本:运行`ldd --version`或检查/lib64/libc.so.6。如果版本低,升级系统或安装兼容库。 2. 安装缺失库:如zlib使用包管理器安装,例如`sudo apt-get install zlib1g-dev`。 3. 检查系统资源:使用`free -m`检查内存,`ulimit -a`检查用户限制。增加内存或调整ulimit设置。 4. 优化编译环境:减少并行编译任务(如make -j参数),释放内存。 5. 更新工具:确保Android SDK、build-tools等是最新版本。 逐步解决: 1. 确认错误细节:查看完整编译日志,定位错误发生的位置。 2. 处理aapt2 link失败: - 确保build-tools版本正确。 - 检查资源文件是否有误。 - 安装缺失依赖,如zlib。 3. 处理线程创建失败: - 增加系统资源:如添加swap空间、关闭其他程序。 - 调整ulimit:增加进程数和文件描述符限制。 - 减少并行编译:使用`make -j1`或更低的并行度。 参考引用: - 引用[1]提到GLIBC问题,所以解决方案可能涉及升级系统或使用兼容层[^1]。 - 引用[3]提到-lz错误,安装zlib-dev可以解决[^3]。 生成相关问题:在回答结束后添加。 回答结构: - 引言:简述问题。 - 分析可能原因:分点列出。 - 解决方案:逐步建议。 - 结尾:总结。 添加引用标识:在引用参考内容时添加。 最后,相关问题部分。</think>### 分析安卓编译错误:aapt2 link 失败和 'failed to create new OS thread' 错误 在安卓编译过程中,遇到 `aapt2 link` 失败和 `failed to create new OS thread` 错误,通常表示资源链接问题和系统资源限制。这些错误可能导致编译中断,影响开发效率。下面我将逐步分析可能原因,并提供针对性的解决方案。分析基于常见编译错误模式和参考信息(如引用内容),力求真实可靠。 #### 错误原因分析 1. **aapt2 link 失败**: - **资源文件或路径问题**:aapt2(Android Asset Packaging Tool 2)负责链接资源文件(如 XML、图片)。如果资源文件损坏、路径错误或权限不足,aapt2 无法完成链接操作。例如,资源文件包含无效字符或缺失时,会触发此错误。 - **依赖库缺失或版本不兼容**:aapt2 依赖于系统库(如 GLIBC、zlib)。如果这些库缺失或版本过低(如 GLIBC 低于 2.14),aapt2 无法运行。参考引用[1]中,aapt 因缺少 GLIBC_2.14 而失败,类似问题可能影响 aapt2[^1]。引用[3]显示缺少 `-lz`(zlib 库),同样会导致链接失败[^3]。 - **工具链问题**:Android SDK 的 build-tools 版本过旧或损坏,或编译环境配置错误(如环境变量未设置),可能引起 aapt2 异常。 2. **'failed to create new OS thread' 错误**: - **系统资源不足**:此错误表示操作系统无法创建新线程,通常由以下原因引起: - **内存耗尽**:安卓编译是资源密集型操作,尤其在高并行任务(如 `make -j8`)时,可能导致内存(RAM 或 swap)不足。如果系统内存不足,线程创建会失败。 - **进程/线程限制**:Linux 系统有用户级限制(通过 `ulimit` 设置),如最大进程数(`nproc`)或虚拟内存(`as`)。如果编译任务超出限制,会触发此错误。 - **系统配置问题**:内核参数(如 `vm.max_map_count`)设置不当,或运行在虚拟机/容器中资源分配不足,也可能导致线程创建失败。 3. **综合因素**: - aapt2 link 失败可能先发生,导致编译进程堆积,进而加剧资源消耗,引发线程创建失败。例如,aapt2 因库缺失而反复重试,占用大量内存和线程资源。 - 环境问题:如编译环境(如 Docker 容器)资源分配不足,或系统未更新(如旧版 Linux 发行版缺少必要库)。 #### 解决方案 以下是逐步解决建议。请先检查完整编译日志,确认错误发生位置(如运行 `make` 时的输出)。操作前备份重要数据。 1. **解决 aapt2 link 失败**: - **步骤 1:检查并修复依赖库**: - 运行 `ldd --version` 检查 GLIBC 版本。如果低于 2.14(常见于旧系统如 CentOS 6),需升级系统或安装兼容包。例如: ```bash # 对于 Ubuntu/Debian,更新系统并安装开发库 sudo apt-get update sudo apt-get upgrade sudo apt-get install build-essential zlib1g-dev libc6-dev # 安装 zlib 等依赖[^3] ``` 如果无法升级系统,考虑使用较旧 Android SDK 版本(兼容当前 GLIBC)。 - 验证 zlib 库:运行 `ldconfig -p | grep libz.so`。如果缺失,安装 `zlib1g-dev`(Debian/Ubuntu)或 `zlib-devel`(CentOS)。 - **步骤 2:更新 Android 工具链**: - 确保 Android SDK 和 build-tools 是最新版。使用 SDK Manager 更新: ```bash sdkmanager --update sdkmanager --install "build-tools;30.0.3" # 安装最新稳定版 ``` - 检查环境变量:设置 `ANDROID_HOME` 和 `PATH`,确保 aapt2 路径正确(如 `$ANDROID_HOME/build-tools/<version>/aapt2`)。 - **步骤 3:清理并重建项目**: - 删除构建缓存并重新编译: ```bash make clean # 或 ./gradlew clean for Gradle 项目 make -j1 # 先使用单线程编译,避免并发问题 ``` 如果资源文件有问题,检查 `res/` 目录下的 XML 或图片文件,修复格式错误。 2. **解决 'failed to create new OS thread' 错误**: - **步骤 1:增加系统资源**: - 检查内存使用:运行 `free -m`。如果内存不足,添加 swap 空间: ```bash sudo fallocate -l 4G /swapfile # 创建 4GB swap 文件 sudo chmod 600 /swapfile sudo mkswap /swapfile sudo swapon /swapfile ``` - 关闭非必要程序,释放内存。 - **步骤 2:调整系统限制**: - 检查当前限制:运行 `ulimit -a`,关注 `max user processes` 和 `virtual memory`。 - 增加限制:临时修改(会话内有效): ```bash ulimit -u unlimited # 解除进程数限制 ulimit -v unlimited # 解除虚拟内存限制 ``` 永久修改:编辑 `/etc/security/limits.conf`,添加: ``` * soft nproc 65535 * hard nproc 65535 * soft as unlimited * hard as unlimited ``` 然后重启系统。 - 调整内核参数:运行 `sudo sysctl -w vm.max_map_count=262144`,并添加到 `/etc/sysctl.conf` 永久生效。 - **步骤 3:优化编译参数**: - 减少并行任务:避免高并发编译。例如,将 `make -j8` 改为 `make -j2` 或 `make -j1`,以降低资源压力。 - 使用更轻量环境:如果在虚拟机或 Docker 中编译,增加资源分配(如 CPU 核心和内存)。 3. **综合验证**: - 运行编译命令(如 `make`)并监控资源:使用 `top` 或 `htop` 观察内存和 CPU 使用。 - 如果问题持续,检查日志细节:aapt2 错误通常输出到 `build/outputs/logs/` 或控制台。针对错误信息搜索解决方案(如引用[2]提供的资源)[^2]。 - 参考已知问题:引用[4]提到 aapt2 的编译过程涉及资源处理,确保项目配置正确(如 `aaptOptions` in `build.gradle`)[^4]。 #### 总结 aapt2 link 失败主要源于库依赖或资源问题,而线程创建失败通常由系统资源瓶颈引起。优先解决依赖库(如 GLIBC 或 zlib),然后优化系统资源设置。多数情况下,更新工具链、增加内存和调整 ulimit 可解决问题。如果环境受限(如企业服务器),考虑使用云编译或专用构建服务器。预防性维护,如定期更新系统和 Android SDK,能减少类似错误。
评论 20
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值