Android系统加载资源文件源码分析

2019/06/02 Android

Android 如何加载 asset 中的资源文件

在 Android 开发时, 如果想要加载 assets 文件夹下面的文件, 可以使用如下的方式对文 件进行加载

private Bitmap getImageFromAssetsFile(String fileName) {
    Bitmap image = null;
    AssetManager am = getResources().getAssets();
    if (am == null)
    {
        return null;
    }
    try
    {
        InputStream is = am.open(fileName);
        image = BitmapFactory.decodeStream(is);
        is.close();
    }
    catch (IOException e)
    {
        e.printStackTrace();
    }
    return image;
}

首先获取到 AssetManager 对象,然后使用 AssetManager对象对文件进行 open,获取到 InputStream 了,接下来就可以对得到的数据进行读取了.

总结下来 Android 系统对资源文件的处理,主要氛围三步: 1.创建/获取 AssetManager 2.使用 AssetManager 打开需要加载的资源文件 3.读取资源文件的内容,然后按照自己的要求进行处理 在上面的三个步骤中,第二和第二步是最重要的, 这两个操作在源码时如何实现的呢?接下来 我们会分别对上面的两步进行分析

Android 加载资源文件并创建 InputStream 的过程

Android 系统加载资源文件主要使用的系统方法是 AssetManager 类, 这个类定义在* /frameworks/base/core/java/android/content/res/AssetManager.java *这个类中定义 了加载资源文件的操作.其中比较重要的函数 open 的定义如下:

/**
     * Open an asset using ACCESS_STREAMING mode.  This provides access to
     * files that have been bundled with an application as assets -- that is,
     * files placed in to the "assets" directory.
     *
     * @param fileName The name of the asset to open.  This name can be
     *                 hierarchical.
     *
     * @see #open(String, int)
     * @see #list
     */
    public final InputStream open(String fileName) throws IOException {
        return open(fileName, ACCESS_STREAMING);
    }

    /**
     * Open an asset using an explicit access mode, returning an InputStream to
     * read its contents.  This provides access to files that have been bundled
     * with an application as assets -- that is, files placed in to the
     * "assets" directory.
     *
     * @param fileName The name of the asset to open.  This name can be
     *                 hierarchical.
     * @param accessMode Desired access mode for retrieving the data.
     *
     * @see #ACCESS_UNKNOWN
     * @see #ACCESS_STREAMING
     * @see #ACCESS_RANDOM
     * @see #ACCESS_BUFFER
     * @see #open(String)
     * @see #list
     */
    public final InputStream open(String fileName, int accessMode)
        throws IOException {
        synchronized (this) {
            if (!mOpen) {
                throw new RuntimeException("Assetmanager has been closed");
            }
            long asset = openAsset(fileName, accessMode);
            if (asset != 0) {
                AssetInputStream res = new AssetInputStream(asset);
                incRefsLocked(res.hashCode());
                return res;
            }
        }
        throw new FileNotFoundException("Asset file: " + fileName);
    }

其中 openAsset 的源码如下:

private native final long openAsset(String fileName, int accessMode);

通过上面的定义,发现 openAsset 是一个 native 方法,所以需要对这个方法在 native 层 对应的方法进行追踪.通过查找源码发现 openAsset 的定义在*/frameworks/base/core/jni/android_util_AssetManager.cpp*

// location: /frameworks/base/core/jni/android_util_AssetManager.cpp
static jlong android_content_AssetManager_openAsset(JNIEnv* env, jobject clazz,
                                                jstring fileName, jint mode)
{
    AssetManager* am = assetManagerForJavaObject(env, clazz);
    if (am == NULL) {
        return 0;
    }

    ALOGV("openAsset in %p (Java object %p)\n", am, clazz);

    ScopedUtfChars fileName8(env, fileName);
    if (fileName8.c_str() == NULL) {
        jniThrowException(env, "java/lang/IllegalArgumentException", "Empty file name");
        return -1;
    }

    if (mode != Asset::ACCESS_UNKNOWN && mode != Asset::ACCESS_RANDOM
        && mode != Asset::ACCESS_STREAMING && mode != Asset::ACCESS_BUFFER) {
        jniThrowException(env, "java/lang/IllegalArgumentException", "Bad access mode");
        return -1;
    }

    Asset* a = am->open(fileName8.c_str(), (Asset::AccessMode)mode);

    if (a == NULL) {
        jniThrowException(env, "java/io/FileNotFoundException", fileName8.c_str());
        return -1;
    }

    //printf("Created Asset Stream: %p\n", a);

    return reinterpret_cast<jlong>(a);
}

从上面的代码中可以看到,Native 层的 openAsset 函数主要工作就是调用 AssetManager 中的 open 方法,并将生成的 Asset对象的指针返回给 Java层,后面对该文件的操作都是 通过这个 Asset 对象完成的完成的.所以这段代码的核心逻辑是在 native 层的 AssetManager 的 open 函数,也就 是说 Java 层和 Native 层分别都有 AssetManager 类的定义.native 层的 AssetManager 中的 open 的定义如下:

// location: /frameworks/base/libs/androidfw/AssetManager.cpp
/*
 * Open an asset.
 *
 * The data could be;
 *  - In a file on disk (assetBase + fileName).
 *  - In a compressed file on disk (assetBase + fileName.gz).
 *  - In a Zip archive, uncompressed or compressed.
 *
 * It can be in a number of different directories and Zip archives.
 * The search order is:
 *  - [appname]
 *    - locale + vendor
 *    - "default" + vendor
 *    - locale + "default"
 *    - "default + "default"
 *  - "common"
 *    - (same as above)
 *
 * To find a particular file, we have to try up to eight paths with
 * all three forms of data.
 *
 * We should probably reject requests for "illegal" filenames, e.g. those
 * with illegal characters or "../" backward relative paths.
 */
Asset* AssetManager::open(const char* fileName, AccessMode mode)
{
    AutoMutex _l(mLock);

    LOG_FATAL_IF(mAssetPaths.size() == 0, "No assets added to AssetManager");


    if (mCacheMode != CACHE_OFF && !mCacheValid)
        loadFileNameCacheLocked();

    String8 assetName(kAssetsRoot);
    assetName.appendPath(fileName);

    /*
     * For each top-level asset path, search for the asset.
     */

    size_t i = mAssetPaths.size();
    while (i > 0) {
        i--;
        ALOGV("Looking for asset '%s' in '%s'\n",
                assetName.string(), mAssetPaths.itemAt(i).path.string());
        Asset* pAsset = openNonAssetInPathLocked(assetName.string(), mode, mAssetPaths.itemAt(i));
        if (pAsset != NULL) {
            return pAsset != kExcludedAsset ? pAsset : NULL;
        }
    }

    return NULL;
}

接下来的关键的函数是openNonAssetInPathLocked, 其中assetName为需要加载的资源文件 的名称, mode 是加载资源文件的模式, mAssetPaths 是一个 vector,里面包含当前 APK 能 够进行资源加载的路径.

// location: /frameworks/base/libs/androidfw/AssetManager.cpp
/*
 * Open a non-asset file as if it were an asset, searching for it in the
 * specified app.
 *
 * Pass in a NULL values for "appName" if the common app directory should
 * be used.
 */
Asset* AssetManager::openNonAssetInPathLocked(const char* fileName, AccessMode mode,
    const asset_path& ap)
{
    Asset* pAsset = NULL;

    /* look at the filesystem on disk */
    if (ap.type == kFileTypeDirectory) {
        String8 path(ap.path);
        path.appendPath(fileName);

        pAsset = openAssetFromFileLocked(path, mode);

        if (pAsset == NULL) {
            /* try again, this time with ".gz" */
            path.append(".gz");
            pAsset = openAssetFromFileLocked(path, mode);
        }

        if (pAsset != NULL) {
            //printf("FOUND NA '%s' on disk\n", fileName);
            pAsset->setAssetSource(path);
        }

    /* look inside the zip file */
    } else {
        String8 path(fileName);

        /* check the appropriate Zip file */
        ZipFileRO* pZip = getZipFileLocked(ap);
        if (pZip != NULL) {
            //printf("GOT zip, checking NA '%s'\n", (const char*) path);
            ZipEntryRO entry = pZip->findEntryByName(path.string());
            if (entry != NULL) {
                //printf("FOUND NA in Zip file for %s\n", appName ? appName : kAppCommon);
                pAsset = openAssetFromZipLocked(pZip, entry, mode, path);
                pZip->releaseEntry(entry);
            }
        }

        if (pAsset != NULL) {
            /* create a "source" name, for debug/display */
            pAsset->setAssetSource(
                    createZipSourceNameLocked(ZipSet::getPathName(ap.path.string()), String8(""),
                                                String8(fileName)));
        }
    }

    return pAsset;
}

因为我们的资源文件是在 apk 中,所以我们是走下面的分支, 首先创建 ZipEntryRO 对象, 然后通过需要加载的资源文件的名称获取到需要加载的 entry,然后调用 openAssetFromZipLocked 进行加载.

// location: /frameworks/base/libs/androidfw/AssetManager.cpp
/*
 * Given an entry in a Zip archive, create a new Asset object.
 *
 * If the entry is uncompressed, we may want to create or share a
 * slice of shared memory.
 */
Asset* AssetManager::openAssetFromZipLocked(const ZipFileRO* pZipFile,
    const ZipEntryRO entry, AccessMode mode, const String8& entryName)
{
    Asset* pAsset = NULL;

    // TODO: look for previously-created shared memory slice?
    int method;
    size_t uncompressedLen;

    //printf("USING Zip '%s'\n", pEntry->getFileName());

    //pZipFile->getEntryInfo(entry, &method, &uncompressedLen, &compressedLen,
    //    &offset);
    if (!pZipFile->getEntryInfo(entry, &method, &uncompressedLen, NULL, NULL,
            NULL, NULL))
    {
        ALOGW("getEntryInfo failed\n");
        return NULL;
    }

    FileMap* dataMap = pZipFile->createEntryFileMap(entry);
    if (dataMap == NULL) {
        ALOGW("create map from entry failed\n");
        return NULL;
    }

    if (method == ZipFileRO::kCompressStored) {
        //文件以存储方式在 apk 中存在
        pAsset = Asset::createFromUncompressedMap(dataMap, mode);
        ALOGV("Opened uncompressed entry %s in zip %s mode %d: %p", entryName.string(),
                dataMap->getFileName(), mode, pAsset);
    } else {
        // 文件以压缩方式在 apk 中存在
        pAsset = Asset::createFromCompressedMap(dataMap, method,
            uncompressedLen, mode);
        ALOGV("Opened compressed entry %s in zip %s mode %d: %p", entryName.string(),
                dataMap->getFileName(), mode, pAsset);
    }
    if (pAsset == NULL) {
        /* unexpected */
        ALOGW("create from segment failed\n");
    }

    return pAsset;
}

该函数使用 pZipFile->createEntryFileMap 函数将资源文件数据加载到内存中, 并创建了 FileMap 的对象

/*
 * Create a new FileMap object that spans the data in "entry".
 */
FileMap* ZipFileRO::createEntryFileMap(ZipEntryRO entry) const
{
    const _ZipEntryRO *zipEntry = reinterpret_cast<_ZipEntryRO*>(entry);
    const ZipEntry& ze = zipEntry->entry;
    int fd = GetFileDescriptor(mHandle);
    size_t actualLen = 0;

    if (ze.method == kCompressStored) {
        actualLen = ze.uncompressed_length;
    } else {
        actualLen = ze.compressed_length;
    }

    FileMap* newMap = new FileMap();
    if (!newMap->create(mFileName, fd, ze.offset, actualLen, true)) {
        newMap->release();
        return NULL;
    }

    return newMap;
}

从上面的代码中可以看到最终使用 FileMap 的 create 对文件进行创建. 此时需要注意的 是 create 的参数 mFileName是 apk 的名称( /data/app/example.com.jstest-2/base.apk),并不是 APK 中需要解压的文件的名称.接下 来了解一下FileMap类.FileMap 是什么呢? 我们直接看源码中的解释:

/system/core/include/utils/FileMap.h
/*
 * This represents a memory-mapped file.  It might be the entire file or
 * only part of it.  This requires a little bookkeeping because the mapping
 * needs to be aligned on page boundaries, and in some cases we'd like to
 * have multiple references to the mapped area without creating additional
 * maps.
 *
 * This always uses MAP_SHARED.
 *
 * TODO: we should be able to create a new FileMap that is a subset of
 * an existing FileMap and shares the underlying mapped pages.  Requires
 * completing the refcounting stuff and possibly introducing the notion
 * of a FileMap hierarchy.
 */

接下来我们看 FileMap 中的 create 方法的定义:

// Create a new mapping on an open file.
//
// Closing the file descriptor does not unmap the pages, so we don't
// claim ownership of the fd.
//
// Returns "false" on failure.
bool FileMap::create(const char* origFileName, int fd, off64_t offset, size_t length,
        bool readOnly)
{
#ifdef HAVE_WIN32_FILEMAP
    int     adjust;
    off64_t adjOffset;
    size_t  adjLength;

    if (mPageSize == -1) {
        SYSTEM_INFO  si;

        GetSystemInfo( &si );
        mPageSize = si.dwAllocationGranularity;
    }

    DWORD  protect = readOnly ? PAGE_READONLY : PAGE_READWRITE;

    mFileHandle  = (HANDLE) _get_osfhandle(fd);
    mFileMapping = CreateFileMapping( mFileHandle, NULL, protect, 0, 0, NULL);
    if (mFileMapping == NULL) {
        ALOGE("CreateFileMapping(%p, %" PRIx32 ") failed with error %" PRId32 "\n",
              mFileHandle, protect, GetLastError() );
        return false;
    }

    adjust    = offset % mPageSize;
    adjOffset = offset - adjust;
    adjLength = length + adjust;

    mBasePtr = MapViewOfFile( mFileMapping,
                              readOnly ? FILE_MAP_READ : FILE_MAP_ALL_ACCESS,
                              0,
                              (DWORD)(adjOffset),
                              adjLength );
    if (mBasePtr == NULL) {
        ALOGE("MapViewOfFile(%" PRId64 ", %zu) failed with error %" PRId32 "\n",
              adjOffset, adjLength, GetLastError() );
        CloseHandle(mFileMapping);
        mFileMapping = INVALID_HANDLE_VALUE;
        return false;
    }
#endif

// 因为不是 window 系统,所以走下面的流程
#ifdef HAVE_POSIX_FILEMAP
    int     prot, flags, adjust;
    off64_t adjOffset;
    size_t  adjLength;

    void* ptr;

    assert(mRefCount == 1);
    assert(fd >= 0);
    assert(offset >= 0);
    assert(length > 0);

    // init on first use
    if (mPageSize == -1) {
#if NOT_USING_KLIBC
        mPageSize = sysconf(_SC_PAGESIZE);
        if (mPageSize == -1) {
            ALOGE("could not get _SC_PAGESIZE\n");
            return false;
        }
#else
        // this holds for Linux, Darwin, Cygwin, and doesn't pain the ARM
        mPageSize = 4096;
#endif
    }

    adjust   = offset % mPageSize;
try_again:
    adjOffset = offset - adjust;
    adjLength = length + adjust;

    flags = MAP_SHARED;
    prot = PROT_READ;
    if (!readOnly)
        prot |= PROT_WRITE;

    ptr = mmap(NULL, adjLength, prot, flags, fd, adjOffset);
    if (ptr == MAP_FAILED) {
        // Cygwin does not seem to like file mapping files from an offset.
        // So if we fail, try again with offset zero
        if (adjOffset > 0) {
            adjust = offset;
            goto try_again;
        }

        ALOGE("mmap(%lld,%zu) failed: %s\n",
            (long long)adjOffset, adjLength, strerror(errno));
        return false;
    }
    mBasePtr = ptr;
#endif // HAVE_POSIX_FILEMAP

    mFileName = origFileName != NULL ? strdup(origFileName) : NULL;
    mBaseLength = adjLength;
    mDataOffset = offset;
    mDataPtr = (char*) mBasePtr + adjust;
    mDataLength = length;

    assert(mBasePtr != NULL);

    ALOGV("MAP: base %p/%zu data %p/%zu\n",
        mBasePtr, mBaseLength, mDataPtr, mDataLength);

    return true;
}

通过上面的代码创建完成 FileMap 对象之后,就要返回到openAssetFromZipLocked 方法中, 使用创建的 FileMap 对象创建 Asset 对象,在 openAssetFromFileLocked 中创建 Asset 对象的方法是: Asset::createFromCompressedMap

/*
/frameworks/base/libs/androidfw/Asset.cpp
*/
/*
 * Create a new Asset from compressed data in a memory mapping.
 */
/*static*/ Asset* Asset::createFromCompressedMap(FileMap* dataMap,
    int method, size_t uncompressedLen, AccessMode mode)
{
    _CompressedAsset* pAsset;
    status_t result;

    pAsset = new _CompressedAsset;
    result = pAsset->openChunk(dataMap, method, uncompressedLen);
    if (result != NO_ERROR)
        return NULL;

    pAsset->mAccessMode = mode;
    return pAsset;
}
/*
/frameworks/base/libs/androidfw/Asset.cpp
*/
status_t _CompressedAsset::openChunk(FileMap* dataMap, int compressionMethod,
    size_t uncompressedLen)
{
    assert(mFd < 0);        // no re-open
    assert(mMap == NULL);
    assert(dataMap != NULL);

    if (compressionMethod != ZipFileRO::kCompressDeflated) {
        assert(false);
        return UNKNOWN_ERROR;
    }

    mMap = dataMap;
    mStart = -1;        // not used
    mCompressedLen = dataMap->getDataLength();
    mUncompressedLen = uncompressedLen;
    assert(mOffset == 0);

    // 关键点:
    // 如果长度大于64*1024, 则初始化 StreamingZipInflater
    if (uncompressedLen > StreamingZipInflater::OUTPUT_CHUNK_SIZE) {
        mZipInflater = new StreamingZipInflater(dataMap, uncompressedLen);
    }
    return NO_ERROR;
}

截止到这里,Android 系统已经完成了对资源文件的加载的过程,总结下来就是根据需要加载 的文件的名称,在 APK 中进行创建 zipEntry 进行记载,并构建 Asset 对象.注意如果加载 的文件是以压缩格式存储的(ps:大部分在 apk 中的文件,都是以压缩文件格式存储的), Asset 对象指向的只是内存中 APK 中的相应的压缩文件的偏移量,长度,压缩后大小以及压 缩前大小等信息,此时*并没有对压缩的数据进行解压缩操作*,这一点尤其需要注意.

Android 读取资源文件过程分析

Android 系统在 AssetManager 类中的 open 等函数返回的是 InputStream,这个 InputStream 其实是 AssetManager 中定义的一个内部类AssetInputStream,这个子类继承了 InputStream 类,并实现了其中的reade 等方法.AssetInputStream中定义的 read 方法的 定义如下:

// frameworks/base/core/java/android/content/res/AssetManager.java
public final int read(byte[] b) throws IOException {
    return readAsset(mAsset, b, 0, b.length);
}
public final int read(byte[] b, int off, int len) throws IOException {
    return readAsset(mAsset, b, off, len);

Java 层对readAsset 的定义如下:

// frameworks/base/core/java/android/content/res/AssetManager.java
private native final int readAsset(long asset, byte[] b, int off, int len);

与 Java 层对应的 Native 层的函数的名称是*andorid_content_AssetManager_readAsset*。这个 函数的具体的实现如下:

// frameworks/base/core/jni/android_util_AssetManager.cpp
static jint android_content_AssetManager_readAsset(JNIEnv* env, jobject clazz,
                                                jlong assetHandle, jbyteArray bArray,
                                                jint off, jint len)
{
    // assetHandle 对应于 native 层的 Asset 类型。是否每个文件都对应于一个 assetHandle 呢?
    Asset* a = reinterpret_cast<Asset*>(assetHandle);

    if (a == NULL || bArray == NULL) {
        jniThrowNullPointerException(env, "asset");
        return -1;
    }

    if (len == 0) {
        return 0;
    }

    jsize bLen = env->GetArrayLength(bArray);
    if (off < 0 || off >= bLen || len < 0 || len > bLen || (off+len) > bLen) {
        jniThrowException(env, "java/lang/IndexOutOfBoundsException", "");
        return -1;
    }

    jbyte* b = env->GetByteArrayElements(bArray, NULL);
    // 关键代码,调用 Asset 的 read 函数对APK 中的资源进行读取
    ssize_t res = a->read(b+off, len);
    env->ReleaseByteArrayElements(bArray, b, 0);

    if (res > 0) return static_cast<jint>(res);

    if (res < 0) {
        jniThrowException(env, "java/io/IOException", "");
    }
    return -1;
}

Asset类的 read 函数的实现如下:

// frameworks/base/libs/androidfw/Asset.cpp
/*
 * Read data from a chunk of compressed data.
 *
 * [For now, that's just copying data out of a buffer.]
 */
ssize_t _CompressedAsset::read(void* buf, size_t count)
{
    size_t maxLen;
    size_t actual;

    assert(mOffset >= 0 && mOffset <= mUncompressedLen);

    /* If we're relying on a streaming inflater, go through that */
    // 如果文件解压缩后的长度大于StreamingZipInflater::OUTPUT_CHUNK_SIZE(64*1024),mZipInflaget 的值就不会为空。
    if (mZipInflater) {
        // 关键函数: read
        actual = mZipInflater->read(buf, count);
    } else {
    // 如果文件解压缩后的长度小于 64*1024,就会走下面的流程
        if (mBuf == NULL) {
            // 关键函数: getBuffer
            if (getBuffer(false) == NULL)
                return -1;
        }
        assert(mBuf != NULL);

        /* adjust count if we're near EOF */
        maxLen = mUncompressedLen - mOffset;
        if (count > maxLen)
            count = maxLen;

        if (!count)
            return 0;

        /* copy from buffer */
        //printf("comp buf read\n");
        memcpy(buf, (char*)mBuf + mOffset, count);
        actual = count;
    }

    mOffset += actual;
    return actual;
}

mZipInflater 的类型为 StreamingZipInflater,StreamZipInflater 中的 read 的定义如 下:

// frameworks/base/include/androidfw/StreamingZipInflater.h
namespace android {

class StreamingZipInflater {
public:
    static const size_t INPUT_CHUNK_SIZE = 64 * 1024;
    static const size_t OUTPUT_CHUNK_SIZE = 64 * 1024;

    // Flavor that pages in the compressed data from a fd
    StreamingZipInflater(int fd, off64_t compDataStart, size_t uncompSize, size_t compSize);

    // Flavor that gets the compressed data from an in-memory buffer
    StreamingZipInflater(class FileMap* dataMap, size_t uncompSize);

    ~StreamingZipInflater();

    // read 'count' bytes of uncompressed data from the current position.  outBuf may
    // be NULL, in which case the data is consumed and discarded.
    ssize_t read(void* outBuf, size_t count);

    // seeking backwards requires uncompressing fom the beginning, so is very
    // expensive.  seeking forwards only requires uncompressing from the current
    // position to the destination.
    off64_t seekAbsolute(off64_t absoluteInputPosition);
private:
    //  StreamingZipInflater中定义的数据结构,这些数据结构用来
    void initInflateState();
    int readNextChunk();

    // where to find the uncompressed data
    int mFd;
    off64_t mInFileStart;         // where the compressed data lives in the file
    class FileMap* mDataMap;

    z_stream mInflateState;
    bool mStreamNeedsInit;

    // output invariants for this asset
    uint8_t* mOutBuf;           // output buf for decompressed bytes
    size_t mOutBufSize;         // allocated size of mOutBuf
    size_t mOutTotalSize;       // total uncompressed size of the blob

    // current output state bookkeeping
    off64_t mOutCurPosition;      // current position in total offset
    size_t mOutLastDecoded;     // last decoded byte + 1 in mOutbuf
    size_t mOutDeliverable;     // next undelivered byte of decoded output in mOutBuf

    // input invariants
    uint8_t* mInBuf;
    size_t mInBufSize;          // allocated size of mInBuf;
    size_t mInTotalSize;        // total size of compressed data for this blob

    // input state bookkeeping
    size_t mInNextChunkOffset;  // offset from start of blob at which the next input chunk lies
    // the z_stream contains state about input block consumption
};


// frameworks/base/libs/androidfw/StreamingZipInflater.cpp
/*
 * Basic approach:
 *
 * 1. If we have undelivered uncompressed data, send it.  At this point
 *    either we've satisfied the request, or we've exhausted the available
 *    output data in mOutBuf.
 *
 * 2. While we haven't sent enough data to satisfy the request:
 *    0. if the request is for more data than exists, bail.
 *    a. if there is no input data to decode, read some into the input buffer
 *       and readjust the z_stream input pointers
 *    b. point the output to the start of the output buffer and decode what we can
 *    c. deliver whatever output data we can
 */
ssize_t StreamingZipInflater::read(void* outBuf, size_t count) {
    uint8_t* dest = (uint8_t*) outBuf;
    size_t bytesRead = 0;
    size_t toRead = min_of(count, size_t(mOutTotalSize - mOutCurPosition));
    while (toRead > 0) {
        // First, write from whatever we already have decoded and ready to go
        size_t deliverable = min_of(toRead, mOutLastDecoded - mOutDeliverable);
        if (deliverable > 0) {
            // 第一次不执行,等到后面在执行,要先等后面 inflate 方法执行之后才会执行
            if (outBuf != NULL) memcpy(dest, mOutBuf + mOutDeliverable, deliverable);
            mOutDeliverable += deliverable;
            mOutCurPosition += deliverable;
            dest += deliverable;
            bytesRead += deliverable;
            toRead -= deliverable;
        }

        // need more data?  time to decode some.
        if (toRead > 0) {
            // if we don't have any data to decode, read some in.  If we're working
            // from mmapped data this won't happen, because the clipping to total size
            // will prevent reading off the end of the mapped input chunk.
            if ((mInflateState.avail_in == 0) && (mDataMap == NULL)) {
                int err = readNextChunk();
                if (err < 0) {
                    ALOGE("Unable to access asset data: %d", err);
                    if (!mStreamNeedsInit) {
                        ::inflateEnd(&mInflateState);
                        initInflateState();
                    }
                    return -1;
                }
            }
            // we know we've drained whatever is in the out buffer now, so just
            // start from scratch there, reading all the input we have at present.
            mInflateState.next_out = (Bytef*) mOutBuf;
            mInflateState.avail_out = mOutBufSize;

            /*
            ALOGV("Inflating to outbuf: avail_in=%u avail_out=%u next_in=%p next_out=%p",
                    mInflateState.avail_in, mInflateState.avail_out,
                    mInflateState.next_in, mInflateState.next_out);
            */
            int result = Z_OK;
            if (mStreamNeedsInit) {
                ALOGV("Initializing zlib to inflate");
                result = inflateInit2(&mInflateState, -MAX_WBITS);
                mStreamNeedsInit = false;
            }
            // 关键代码, Zlib 库中的函数,用来从 APK 中解压缩内存数据
            // 第一次执行的时候,都会优先走到这一步中来
            if (result == Z_OK) result = ::inflate(&mInflateState, Z_SYNC_FLUSH);
            if (result < 0) {
                // Whoops, inflation failed
                ALOGE("Error inflating asset: %d", result);
                ::inflateEnd(&mInflateState);
                initInflateState();
                return -1;
            } else {
                if (result == Z_STREAM_END) {
                    // we know we have to have reached the target size here and will
                    // not try to read any further, so just wind things up.
                    ::inflateEnd(&mInflateState);
                }

                // Note how much data we got, and off we go
                mOutDeliverable = 0;
                mOutLastDecoded = mOutBufSize - mInflateState.avail_out;
            }
        }
    }
    return bytesRead;
}

上面 read 方法实现了对资源文件的解压缩操作.以上的操作只是针对解压缩长度大于 64*1024的文件,如果文件的解压缩的长度小于64 *1024, 则在_CompressedAsset::read 方 法中,会调用 getBuffer 操作, getBuffer 源码如下所示:

// location: /frameworks/base/libs/androidfw/Asset.cpp
/*
 * Get a pointer to a read-only buffer of data.
 *
 * The first time this is called, we expand the compressed data into a
 * buffer.
 */
const void* _CompressedAsset::getBuffer(bool)
{
    unsigned char* buf = NULL;

    if (mBuf != NULL)
        return mBuf;

    /*
     * Allocate a buffer and read the file into it.
     */
    buf = new unsigned char[mUncompressedLen];
    if (buf == NULL) {
        ALOGW("alloc %ld bytes failed\n", (long) mUncompressedLen);
        goto bail;
    }

    if (mMap != NULL) {
        // 关键函数: 调用 zipUtils 中的操作进行解压操作
        if (!ZipUtils::inflateToBuffer(mMap->getDataPtr(), buf,
                mUncompressedLen, mCompressedLen))
            goto bail;
    } else {
        assert(mFd >= 0);

        /*
         * Seek to the start of the compressed data.
         */
        if (lseek(mFd, mStart, SEEK_SET) != mStart)
            goto bail;

        /*
         * Expand the data into it.
         */
        if (!ZipUtils::inflateToBuffer(mFd, buf, mUncompressedLen,
                mCompressedLen))
            goto bail;
    }

    /*
     * Success - now that we have the full asset in RAM we
     * no longer need the streaming inflater
     */
    delete mZipInflater;
    mZipInflater = NULL;

    mBuf = buf;
    buf = NULL;

bail:
    delete[] buf;
    return mBuf;
}

Search

    Table of Contents