• 安装APK时SO库的选择策略


    此文已由作者尹彬彬授权网易云社区发布。

    欢迎访问网易云社区,了解更多网易技术产品运营经验。


    0X0 前言

    在Android系统中,当我们安装apk文件的时候,lib目录下的so文件会被解压到app的原生库目录,一般来说是放到/data/data/<package-name>/lib目录下,而根据系统和CPU架构的不同,其拷贝策略也是不一样的,在我们测试过程中发现不正确地配置了so文件,比如某些app使用第三方的so时,只配置了其中某一种CPU架构的so,可能会造成app在某些机型上的适配问题。所以这篇文章主要介绍一下在不同版本的Android系统中,安装apk时,PackageManagerService选择解压so库的策略,并给出一些so文件配置的建议。


    0x1 Android4.0以前

    当apk被安装时,执行路径虽然有差别,但最终要调用到的一个核心函数是copyApk,负责拷贝apk中的资源。

    参考2.3.6的android源码,它的copyApk其内部函数一段选取原生库so逻辑:

     public static int listPackageNativeBinariesLI(ZipFile zipFile,
                List> nativeFiles) throws ZipException, IOException {
            String cpuAbi = Build.CPU_ABI;        int result = listPackageSharedLibsForAbiLI(zipFile, cpuAbi, nativeFiles);        /*
             * Some architectures are capable of supporting several CPU ABIs
             * for example, 'armeabi-v7a' also supports 'armeabi' native code
             * this is indicated by the definition of the ro.product.cpu.abi2
             * system property.
             *
             * only scan the package twice in case of ABI mismatch
             */
            if (result == PACKAGE_INSTALL_NATIVE_ABI_MISMATCH) {            final String cpuAbi2 = SystemProperties.get("ro.product.cpu.abi2", null);            if (cpuAbi2 != null) {
                    result = listPackageSharedLibsForAbiLI(zipFile, cpuAbi2, nativeFiles);
                }            if (result == PACKAGE_INSTALL_NATIVE_ABI_MISMATCH) {
                    Slog.w(TAG, "Native ABI mismatch from package file");                return PackageManager.INSTALL_FAILED_INVALID_APK;
                }            if (result == PACKAGE_INSTALL_NATIVE_FOUND_LIBRARIES) {
                    cpuAbi = cpuAbi2;
                }
            }        /*
             * Debuggable packages may have gdbserver embedded, so add it to
             * the list to the list of items to be extracted (as lib/gdbserver)
             * into the application's native library directory later.
             */
            if (result == PACKAGE_INSTALL_NATIVE_FOUND_LIBRARIES) {
                listPackageGdbServerLI(zipFile, cpuAbi, nativeFiles);
            }        return PackageManager.INSTALL_SUCCEEDED;
        }

    这段代码中的Build.CPU_ABI和“ro.product.cpu.abi2”分别为手机支持的主abi和次abi属性字符串,abi为手机支持的指令集所代表的字符串,比如armeabi-v7a、armeabi、x86、mips等,而主abi和次abi分别表示手机支持的第一指令集和第二指令集。代码首先调用listPackageSharedLibsForAbiLI来遍历主abi目录。当主abi目录不存在时,才会接着调用listPackageSharedLibsForAbiLI遍历次abi目录。

    /*
         * Find all files of the form lib//lib.so in the .apk
         * and add them to a list to be installed later.
         *
         * NOTE: this method may throw an IOException if the library cannot
         * be copied to its final destination, e.g. if there isn't enough
         * room left on the data partition, or a ZipException if the package
         * file is malformed.
         */
        private static int listPackageSharedLibsForAbiLI(ZipFile zipFile,
                String cpuAbi, List> libEntries) throws IOException,
                ZipException {        final int cpuAbiLen = cpuAbi.length();        boolean hasNativeLibraries = false;        boolean installedNativeLibraries = false;        if (DEBUG_NATIVE) {
                Slog.d(TAG, "Checking " + zipFile.getName() + " for shared libraries of CPU ABI type "
                        + cpuAbi);
            }
            Enumeration entries = zipFile.entries();        while (entries.hasMoreElements()) {
                ZipEntry entry = entries.nextElement();            // skip directories
                if (entry.isDirectory()) {                continue;
                }
                String entryName = entry.getName();            /*
                 * Check that the entry looks like lib//lib.so
                 * here, but don't check the ABI just yet.
                 *
                 * - must be sufficiently long
                 * - must end with LIB_SUFFIX, i.e. ".so"
                 * - must start with APK_LIB, i.e. "lib/"
                 */
                if (entryName.length() < MIN_ENTRY_LENGTH || !entryName.endsWith(LIB_SUFFIX)
                        || !entryName.startsWith(APK_LIB)) {                continue;
                }            // file name must start with LIB_PREFIX, i.e. "lib"
                int lastSlash = entryName.lastIndexOf('/');            if (lastSlash < 0
                        || !entryName.regionMatches(lastSlash + 1, LIB_PREFIX, 0, LIB_PREFIX_LENGTH)) {                continue;
                }
                hasNativeLibraries = true;            // check the cpuAbi now, between lib/ and /lib.so
                if (lastSlash != APK_LIB_LENGTH + cpuAbiLen
                        || !entryName.regionMatches(APK_LIB_LENGTH, cpuAbi, 0, cpuAbiLen))                continue;            /*
                 * Extract the library file name, ensure it doesn't contain
                 * weird characters. we're guaranteed here that it doesn't contain
                 * a directory separator though.
                 */
                String libFileName = entryName.substring(lastSlash+1);            if (!FileUtils.isFilenameSafe(new File(libFileName))) {                continue;
                }
                installedNativeLibraries = true;            if (DEBUG_NATIVE) {
                    Log.d(TAG, "Caching shared lib " + entry.getName());
                }
                libEntries.add(Pair.create(entry, libFileName));
            }        if (!hasNativeLibraries)            return PACKAGE_INSTALL_NATIVE_NO_LIBRARIES;        if (!installedNativeLibraries)            return PACKAGE_INSTALL_NATIVE_ABI_MISMATCH;        return PACKAGE_INSTALL_NATIVE_FOUND_LIBRARIES;
        }

    listPackageSharedLibsForAbiLI中判断当前遍历的apk中文件的entry名是否符合so命名的规范且包含相应abi字符串名。如果符合则规则则将so的entry名加入list,如果遍历失败或者规则不匹配则返回相应错误码。


    拷贝so策略:

    遍历apk中文件,当apk中lib目录下主abi子目录中有so文件存在时,则全部拷贝主abi子目录下的so;只有当主abi子目录下没有so文件的时候即PACKAGE_INSTALL_NATIVE_ABI_MISMATCH的情况,才会拷贝次ABI子目录下的so文件。


    策略问题:

    当so放置不当时,安装apk时会导致拷贝不全。比如apk的lib目录下存在armeabi/libx.so,armeabi/liby.so,armeabi-v7a/libx.so这3个so文件,那么在主ABI为armeabi-v7a且系统版本小于4.0的手机上,apk安装后,按照拷贝策略,只会拷贝主abi目录下的文件即armeabi-v7a/libx.so,当加载liby.so时就会报找不到so的异常。另外如果主abi目录不存在,这个策略会遍历2次apk,效率偏低。


    0x2 Android 4.0-Android 4.0.3

    参考4.0.3的android源码,同理,找到处理so拷贝的核心逻辑(native层):

    static install_status_titerateOverNativeFiles(JNIEnv *env, jstring javaFilePath, jstring javaCpuAbi, jstring javaCpuAbi2,
            iterFunc callFunc, void* callArg) {    ScopedUtfChars filePath(env, javaFilePath);    ScopedUtfChars cpuAbi(env, javaCpuAbi);    ScopedUtfChars cpuAbi2(env, javaCpuAbi2);
        ZipFileRO zipFile;    if (zipFile.open(filePath.c_str()) != NO_ERROR) {
            LOGI("Couldn't open APK %s
    ", filePath.c_str());        return INSTALL_FAILED_INVALID_APK;
        }    const int N = zipFile.getNumEntries();    char fileName[PATH_MAX];    for (int i = 0; i < N; i++) {        const ZipEntryRO entry = zipFile.findEntryByIndex(i);        if (entry == NULL) {            continue;
            }        // Make sure this entry has a filename.
            if (zipFile.getEntryFileName(entry, fileName, sizeof(fileName))) {            continue;
            }        // Make sure we're in the lib directory of the ZIP.
            if (strncmp(fileName, APK_LIB, APK_LIB_LEN)) {            continue;
            }        // Make sure the filename is at least to the minimum library name size.
            const size_t fileNameLen = strlen(fileName);        static const size_t minLength = APK_LIB_LEN + 2 + LIB_PREFIX_LEN + 1 + LIB_SUFFIX_LEN;        if (fileNameLen < minLength) {            continue;
            }        const char* lastSlash = strrchr(fileName, '/');
            LOG_ASSERT(lastSlash != NULL, "last slash was null somehow for %s
    ", fileName);        // Check to make sure the CPU ABI of this file is one we support.
            const char* cpuAbiOffset = fileName + APK_LIB_LEN;        const size_t cpuAbiRegionSize = lastSlash - cpuAbiOffset;
            LOGV("Comparing ABIs %s and %s versus %s
    ", cpuAbi.c_str(), cpuAbi2.c_str(), cpuAbiOffset);        if (cpuAbi.size() == cpuAbiRegionSize
                    && *(cpuAbiOffset + cpuAbi.size()) == '/'
                    && !strncmp(cpuAbiOffset, cpuAbi.c_str(), cpuAbiRegionSize)) {
                LOGV("Using ABI %s
    ", cpuAbi.c_str());
            } else if (cpuAbi2.size() == cpuAbiRegionSize
                    && *(cpuAbiOffset + cpuAbi2.size()) == '/'
                    && !strncmp(cpuAbiOffset, cpuAbi2.c_str(), cpuAbiRegionSize)) {
                LOGV("Using ABI %s
    ", cpuAbi2.c_str());
            } else {
                LOGV("abi didn't match anything: %s (end at %zd)
    ", cpuAbiOffset, cpuAbiRegionSize);            continue;
            }        // If this is a .so file, check to see if we need to copy it.
            if ((!strncmp(fileName + fileNameLen - LIB_SUFFIX_LEN, LIB_SUFFIX, LIB_SUFFIX_LEN)
                        && !strncmp(lastSlash, LIB_PREFIX, LIB_PREFIX_LEN)
                        && isFilenameSafe(lastSlash + 1))
                    || !strncmp(lastSlash + 1, GDBSERVER, GDBSERVER_LEN)) {
                install_status_t ret = callFunc(env, callArg, &zipFile, entry, lastSlash + 1);            if (ret != INSTALL_SUCCEEDED) {
                    LOGV("Failure for entry %s", lastSlash + 1);                return ret;
                }
            }
        }    return INSTALL_SUCCEEDED;
    }

    拷贝so策略:

    遍历apk中所有文件,如果符合so文件的规则,且为主ABI目录或者次ABI目录下的so,就解压拷贝到相应目录。

    策略问题:

    存在同名so覆盖,比如一个app的armeabi和armeabi-v7a目录下都包含同名的so,那么就会发生覆盖现象,覆盖的先后顺序根据so文件对应ZipFileR0中的hash值而定,考虑这样一个例子,假设一个apk同时有armeabi/libx.so和armeabi-v7a/libx.so,安装到主ABI为armeabi-v7a的手机上,拷贝so时根据遍历顺序,存在一种可能即armeab-v7a/libx.so优先遍历并被拷贝,随后armeabi/libx.so被遍历拷贝,覆盖了前者。本来应该加载armeabi-v7a目录下的so,结果按照这个策略拷贝了armeabi目录下的so。

    apk中文件entry的散列计算函数如下:

    /*
     * Simple string hash function for non-null-terminated strings.
     *//*static*/ unsigned int ZipFileRO::computeHash(const char* str, int len)
    {
        unsigned int hash = 0;    while (len--)
            hash = hash * 31 + *str++;    return hash;
    }/*
     * Add a new entry to the hash table.
     */void ZipFileRO::addToHash(const char* str, int strLen, unsigned int hash)
    {    int ent = hash & (mHashTableSize-1);    /*
         * We over-allocate the table, so we're guaranteed to find an empty slot.
         */
        while (mHashTable[ent].name != NULL)
            ent = (ent + 1) & (mHashTableSize-1);
        mHashTable[ent].name = str;
        mHashTable[ent].nameLen = strLen;
    }

    0x3 Android 4.0.4以后

    以4.1.2系统为例,遍历选择so逻辑如下:

    static install_status_titerateOverNativeFiles(JNIEnv *env, jstring javaFilePath, jstring javaCpuAbi, jstring javaCpuAbi2,
            iterFunc callFunc, void* callArg) {    ScopedUtfChars filePath(env, javaFilePath);    ScopedUtfChars cpuAbi(env, javaCpuAbi);    ScopedUtfChars cpuAbi2(env, javaCpuAbi2);
        ZipFileRO zipFile;    if (zipFile.open(filePath.c_str()) != NO_ERROR) {
            ALOGI("Couldn't open APK %s
    ", filePath.c_str());        return INSTALL_FAILED_INVALID_APK;
        }    const int N = zipFile.getNumEntries();    char fileName[PATH_MAX];
        bool hasPrimaryAbi = false;    for (int i = 0; i < N; i++) {        const ZipEntryRO entry = zipFile.findEntryByIndex(i);        if (entry == NULL) {            continue;
            }        // Make sure this entry has a filename.
            if (zipFile.getEntryFileName(entry, fileName, sizeof(fileName))) {            continue;
            }        // Make sure we're in the lib directory of the ZIP.
            if (strncmp(fileName, APK_LIB, APK_LIB_LEN)) {            continue;
            }        // Make sure the filename is at least to the minimum library name size.
            const size_t fileNameLen = strlen(fileName);        static const size_t minLength = APK_LIB_LEN + 2 + LIB_PREFIX_LEN + 1 + LIB_SUFFIX_LEN;        if (fileNameLen < minLength) {            continue;
            }        const char* lastSlash = strrchr(fileName, '/');
            ALOG_ASSERT(lastSlash != NULL, "last slash was null somehow for %s
    ", fileName);        // Check to make sure the CPU ABI of this file is one we support.
            const char* cpuAbiOffset = fileName + APK_LIB_LEN;        const size_t cpuAbiRegionSize = lastSlash - cpuAbiOffset;
            ALOGV("Comparing ABIs %s and %s versus %s
    ", cpuAbi.c_str(), cpuAbi2.c_str(), cpuAbiOffset);        if (cpuAbi.size() == cpuAbiRegionSize
                    && *(cpuAbiOffset + cpuAbi.size()) == '/'
                    && !strncmp(cpuAbiOffset, cpuAbi.c_str(), cpuAbiRegionSize)) {
                ALOGV("Using primary ABI %s
    ", cpuAbi.c_str());
                hasPrimaryAbi = true;
            } else if (cpuAbi2.size() == cpuAbiRegionSize
                    && *(cpuAbiOffset + cpuAbi2.size()) == '/'
                    && !strncmp(cpuAbiOffset, cpuAbi2.c_str(), cpuAbiRegionSize)) {            /*
                 * If this library matches both the primary and secondary ABIs,
                 * only use the primary ABI.
                 */
                if (hasPrimaryAbi) {
                    ALOGV("Already saw primary ABI, skipping secondary ABI %s
    ", cpuAbi2.c_str());                continue;
                } else {
                    ALOGV("Using secondary ABI %s
    ", cpuAbi2.c_str());
                }
            } else {
                ALOGV("abi didn't match anything: %s (end at %zd)
    ", cpuAbiOffset, cpuAbiRegionSize);            continue;
            }        // If this is a .so file, check to see if we need to copy it.
            if ((!strncmp(fileName + fileNameLen - LIB_SUFFIX_LEN, LIB_SUFFIX, LIB_SUFFIX_LEN)
                        && !strncmp(lastSlash, LIB_PREFIX, LIB_PREFIX_LEN)
                        && isFilenameSafe(lastSlash + 1))
                    || !strncmp(lastSlash + 1, GDBSERVER, GDBSERVER_LEN)) {
                install_status_t ret = callFunc(env, callArg, &zipFile, entry, lastSlash + 1);            if (ret != INSTALL_SUCCEEDED) {
                    ALOGV("Failure for entry %s", lastSlash + 1);                return ret;
                }
            }
        }    return INSTALL_SUCCEEDED;
    }


    拷贝so策略:

    遍历apk中文件,当遍历到有主Abi目录的so时,拷贝并设置标记hasPrimaryAbi为真,以后遍历则只拷贝主Abi目录下的so,当标记为假的时候,如果遍历的so的entry名包含次abi字符串,则拷贝该so。


    策略问题:

    经过实际测试,so放置不当时,安装apk时存在so拷贝不全的情况。这个策略想解决的问题是在4.0~4.0.3系统中的so随意覆盖的问题,即如果有主abi目录的so则拷贝,如果主abi目录不存在这个so则拷贝次abi目录的so,但代码逻辑是根据ZipFileR0的遍历顺序来决定是否拷贝so,假设存在这样的apk,lib目录下存在armeabi/libx.so,armeabi/liby.so,armeabi-v7a/libx.so这三个so文件,且hash的顺序为armeabi-v7a/libx.so在armeabi/liby.so之前,则apk安装的时候liby.so根本不会被拷贝,因为按照拷贝策略,armeabi-v7a/libx.so会优先遍历到,由于它是主abi目录的so文件,所以标记被设置了,当遍历到armeabi/liby.so时,由于标记被设置为真,liby.so的拷贝就被忽略了,从而在加载liby.so的时候会报异常。


    0x4 64位系统支持

    Android在5.0之后支持64位ABI,以5.1.0系统为例:

    public static int copyNativeBinariesWithOverride(Handle handle, File libraryRoot,
                String abiOverride) {        try {            if (handle.multiArch) {                // Warn if we've set an abiOverride for multi-lib packages..
                    // By definition, we need to copy both 32 and 64 bit libraries for
                    // such packages.
                    if (abiOverride != null && !CLEAR_ABI_OVERRIDE.equals(abiOverride)) {
                        Slog.w(TAG, "Ignoring abiOverride for multi arch application.");
                    }                int copyRet = PackageManager.NO_NATIVE_LIBRARIES;                if (Build.SUPPORTED_32_BIT_ABIS.length > 0) {
                        copyRet = copyNativeBinariesForSupportedAbi(handle, libraryRoot,
                                Build.SUPPORTED_32_BIT_ABIS, true /* use isa specific subdirs */);                    if (copyRet < 0 && copyRet != PackageManager.NO_NATIVE_LIBRARIES &&
                                copyRet != PackageManager.INSTALL_FAILED_NO_MATCHING_ABIS) {
                            Slog.w(TAG, "Failure copying 32 bit native libraries; copyRet=" +copyRet);                        return copyRet;
                        }
                    }                if (Build.SUPPORTED_64_BIT_ABIS.length > 0) {
                        copyRet = copyNativeBinariesForSupportedAbi(handle, libraryRoot,
                                Build.SUPPORTED_64_BIT_ABIS, true /* use isa specific subdirs */);                    if (copyRet < 0 && copyRet != PackageManager.NO_NATIVE_LIBRARIES &&
                                copyRet != PackageManager.INSTALL_FAILED_NO_MATCHING_ABIS) {
                            Slog.w(TAG, "Failure copying 64 bit native libraries; copyRet=" +copyRet);                        return copyRet;
                        }
                    }
                } else {
                    String cpuAbiOverride = null;                if (CLEAR_ABI_OVERRIDE.equals(abiOverride)) {
                        cpuAbiOverride = null;
                    } else if (abiOverride != null) {
                        cpuAbiOverride = abiOverride;
                    }
                    String[] abiList = (cpuAbiOverride != null) ?                        new String[] { cpuAbiOverride } : Build.SUPPORTED_ABIS;                if (Build.SUPPORTED_64_BIT_ABIS.length > 0 && cpuAbiOverride == null &&
                            hasRenderscriptBitcode(handle)) {
                        abiList = Build.SUPPORTED_32_BIT_ABIS;
                    }                int copyRet = copyNativeBinariesForSupportedAbi(handle, libraryRoot, abiList,                        true /* use isa specific subdirs */);                if (copyRet < 0 && copyRet != PackageManager.NO_NATIVE_LIBRARIES) {
                        Slog.w(TAG, "Failure copying native libraries [errorCode=" + copyRet + "]");                    return copyRet;
                    }
                }            return PackageManager.INSTALL_SUCCEEDED;
            } catch (IOException e) {
                Slog.e(TAG, "Copying native libraries failed", e);            return PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
            }
        }

    copyNativeBinariesWithOverride分别处理32位和64位so的拷贝,内部函数copyNativeBinariesForSupportedAbi首先会根据abilist去找对应的abi。

     public static int copyNativeBinariesForSupportedAbi(Handle handle, File libraryRoot,
                String[] abiList, boolean useIsaSubdir) throws IOException {
            createNativeLibrarySubdir(libraryRoot);        /*
             * If this is an internal application or our nativeLibraryPath points to
             * the app-lib directory, unpack the libraries if necessary.
             */
            int abi = findSupportedAbi(handle, abiList);        if (abi >= 0) {            /*
                 * If we have a matching instruction set, construct a subdir under the native
                 * library root that corresponds to this instruction set.
                 */
                final String instructionSet = VMRuntime.getInstructionSet(abiList[abi]);            final File subDir;            if (useIsaSubdir) {                final File isaSubdir = new File(libraryRoot, instructionSet);
                    createNativeLibrarySubdir(isaSubdir);
                    subDir = isaSubdir;
                } else {
                    subDir = libraryRoot;
                }            int copyRet = copyNativeBinaries(handle, subDir, abiList[abi]);            if (copyRet != PackageManager.INSTALL_SUCCEEDED) {                return copyRet;
                }
            }        return abi;
        }

    findSupportedAbi内部实现是native函数,首先遍历apk,如果so的全路径中包含abilist中的abi字符串,则记录该abi字符串的索引,最终返回所有记录索引中最靠前的,即排在abilist中最前面的索引。

    static int findSupportedAbi(JNIEnv *env, jlong apkHandle, jobjectArray supportedAbisArray) {    const int numAbis = env->GetArrayLength(supportedAbisArray);
        VectorsupportedAbis;    for (int i = 0; i < numAbis; ++i) {
            supportedAbis.add(new ScopedUtfChars(env,
                (jstring) env->GetObjectArrayElement(supportedAbisArray, i)));
        }
        ZipFileRO* zipFile = reinterpret_cast(apkHandle);    if (zipFile == NULL) {        return INSTALL_FAILED_INVALID_APK;
        }    UniquePtr it(NativeLibrariesIterator::create(zipFile));    if (it.get() == NULL) {        return INSTALL_FAILED_INVALID_APK;
        }
        ZipEntryRO entry = NULL;    char fileName[PATH_MAX];    int status = NO_NATIVE_LIBRARIES;    while ((entry = it->next()) != NULL) {        // We're currently in the lib/ directory of the APK, so it does have some native
            // code. We should return INSTALL_FAILED_NO_MATCHING_ABIS if none of the
            // libraries match.
            if (status == NO_NATIVE_LIBRARIES) {
                status = INSTALL_FAILED_NO_MATCHING_ABIS;
            }        const char* fileName = it->currentEntry();        const char* lastSlash = it->lastSlash();        // Check to see if this CPU ABI matches what we are looking for.
            const char* abiOffset = fileName + APK_LIB_LEN;        const size_t abiSize = lastSlash - abiOffset;        for (int i = 0; i < numAbis; i++) {            const ScopedUtfChars* abi = supportedAbis[i];            if (abi->size() == abiSize && !strncmp(abiOffset, abi->c_str(), abiSize)) {                // The entry that comes in first (i.e. with a lower index) has the higher priority.
                    if (((i < status) && (status >= 0)) || (status < 0) ) {
                        status = i;
                    }
                }
            }
        }    for (int i = 0; i < numAbis; ++i) {
            delete supportedAbis[i];
        }    return status;
    }

    举例说明,在某64位测试手机上的abi属性显示如下,它有2个abilist,分别对应该手机支持的32位和64位abi的字符串组。

    当处理32位so拷贝时,findSupportedAbi索引返回之后,若返回为0,则拷贝armeabi-v7a目录下的so,如果为1,则拷贝armeabi目录下so。

    拷贝so策略:

    分别处理32位和64位abi目录的so拷贝,abi由遍历apk结果的所有so中符合abilist列表的最靠前的序号决定,然后拷贝该abi目录下的so文件。

    策略问题:

    策略假定每个abi目录下的so都放置完全的,这是和2.3.6一样的处理逻辑,存在遗漏拷贝so的可能。


    0x5 建议

    针对android系统的这些拷贝策略的问题,我们给出了一些配置so的建议:

    1)针对armeabi和armeabi-v7a两种ABI

    方法1:由于armeabi-v7a指令集兼容armeabi指令集,所以如果损失一些应用的性能是可以接受的,同时不希望保留库的两份拷贝,可以移除armeabi-v7a目录和其下的库文件,只保留armeabi目录;比如apk使用第三方的so只有armeabi这一种abi时,可以考虑去掉apk中lib目录下armeabi-v7a目录。

    方法2:在armeabi和armeabi-v7a目录下各放入一份so;

    2)针对x86

    目前市面上的x86机型,为了兼容arm指令,基本都内置了libhoudini模块,即二进制转码支持,该模块负责把ARM指令转换为X86指令,所以如果是出于apk包大小的考虑,并且可以接受一些性能损失,可以选择删掉x86库目录,x86下配置的armeabi目录的so库一样可以正常加载使用;

    3)针对64位ABI

    如果app开发者打算支持64位,那么64位的so要放全,否则可以选择不单独编译64位的so,全部使用32位的so,64位机型默认支持32位so的加载。比如apk使用第三方的so只有32位abi的so,可以考虑去掉apk中lib目录下的64位abi子目录,保证apk安装后正常使用。


    0x6 备注

    其实本文是因为在Android的so加载上遇到很多坑,相信很多朋友都遇到过UnsatisfiedLinkError这个错误,反应在用户的机型上也是千差万别,但是有没有想过,可能不是apk逻辑的问题,而是Android系统在安装APK的时候,由于PackageManager的问题,并没有拷贝相应的SO呢?可以参考下面第4个链接,作者给出了解决方案,就是当出现UnsatisfiedLinkError错误时,手动拷贝so来解决的。

    参考文章:

    android源码:https://android.googlesource.com/platform/frameworks/base

    apk安装过程及原理说明:http://blog.csdn.net/hdhd588/article/details/6739281

    UnsatisfiedLinkError的错误及解决方案:

    https://medium.com/keepsafe-engineering/the-perils-of-loading-native-libraries-on-android-befa49dce2db#.hell8vvdm


    免费体验云安全(易盾)内容安全、验证码等服务

    更多网易技术、产品、运营经验分享请点击




    相关文章:
    【推荐】 SVN迁移到GIT
    【推荐】 什么鬼!基于备份恢复的实例数据还能变多?

  • 相关阅读:
    mvc:三
    mvc:二
    Linq分组,linq方法分组
    Linq 中按照多个值进行分组(GroupBy)
    Photoshop 字体
    报表Reporting S而vice是 错误的解决
    1*书籍装帧
    photoshop 魔术橡皮擦
    Photoshop 钢笔 双窗口显示
    数字格式化
  • 原文地址:https://www.cnblogs.com/zyfd/p/9881288.html
Copyright © 2020-2023  润新知