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

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

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\n", 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\n", 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\n", cpuAbi.c_str(), cpuAbi2.c_str(), cpuAbiOffset);        if (cpuAbi.size() == cpuAbiRegionSize
                && *(cpuAbiOffset + cpuAbi.size()) == '/'
                && !strncmp(cpuAbiOffset, cpuAbi.c_str(), cpuAbiRegionSize)) {
            LOGV("Using ABI %s\n", cpuAbi.c_str());
        } else if (cpuAbi2.size() == cpuAbiRegionSize
                && *(cpuAbiOffset + cpuAbi2.size()) == '/'
                && !strncmp(cpuAbiOffset, cpuAbi2.c_str(), cpuAbiRegionSize)) {
            LOGV("Using ABI %s\n", cpuAbi2.c_str());
        } else {
            LOGV("abi didn't match anything: %s (end at %zd)\n", 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\n", 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\n", 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\n", 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\n", 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\n", cpuAbi2.c_str());                continue;
            } else {
                ALOGV("Using secondary ABI %s\n", cpuAbi2.c_str());
            }
        } else {
            ALOGV("abi didn't match anything: %s (end at %zd)\n", 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

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

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

相关文章:
【推荐】 Mysql Innodb 索引原理

安装APK时SO库的选择策略的更多相关文章

  1. 安装apk时出现错误Failure [INSTALL_FAILED_DEXOPT]问题解决的方法

    在android4.0源码里面编译出来apk后,用adb install (或adb install -r 重装)安装时,报错[INSTALL_FAILED_DEXOPT]. xu@xu-PC:~$  ...

  2. 安装APK时引发INSTALL_PARSE_FAILED_MANIFEST_MALFORMED错误的几种可能(申明:来源于网络)

    安装APK时引发INSTALL_PARSE_FAILED_MANIFEST_MALFORMED错误的几种可能(申明:来源于网络) 地址:https://my.oschina.net/freestyle ...

  3. Android系统移植与调试之------->安装apk时出现错误Failure [INSTALL_FAILED_DEXOPT]问题解决的方法

    在android4.0源码里面编译出来apk后,用adb install (或adb install -r 重装)安装时,报错[INSTALL_FAILED_DEXOPT]. xu@xu-PC:~$ ...

  4. 借鉴seisman安装软件时的文件放置选择

    对于大型的软件包的安装来说: 当下载成功一个软件的压缩包后: tar -xvf xxxx.tgz ./configure --prefix=/opt/xxxx make sudo make insta ...

  5. android手机上安装apk时出现解析包错误的一个解决办法

    今天下午在学习安卓开发时,学习开发文档中的gridview时,在模拟器上调试程序一切正常,如下图所示: 但当将bin目录下的HelloGridView.apk拷贝到M8安卓系统后进行安装时,出现了“解 ...

  6. 解决小米手机USB安装apk时AS报错:INSTALL_FAILED_USER_RESTRICTED

    今天,直接用AS在小米手机上运行安装的时候总是报错:INSTALL_FAILED_USER_RESTRICTED,于是乎,通过以下方式解决: 在开发者选项将USB安装打开,然后,哈,解决了.记录一下.

  7. Android模拟器Genymotion安装apk

    一.下载apk 选择你需要安装的apk进行下载,下载完以后放在与adb.exe同一目录下: 看我的 二.安装apk遇到的问题 开启Genymotion模拟器,然后cmd到你的platform-tool ...

  8. Android 8.0+ 更新安装apk失败的问题

    最近做项目发现Android 8.0+ 更新安装apk时 出现安装失败的情况  总结原因是 缺少安装的权限 Android 8.0 (Android O)为了针对一些流氓软件引导用户安装其他无关应用. ...

  9. android 内部存储 安装apk

    在做应用自动更新模块下载apk时遇到了内部存储和sd卡存储两种情况,存在sk卡中存储apk可以正常安装,可是在内部存储中安装apk时出现了parse error的问题. 在网上搜了搜,大致分为两种方案 ...

随机推荐

  1. ubuntu手机识别

    1.sudo gedit /etc/udev/rules.d/51-android.rules 2.将以下内容拷贝保存 SUBSYSTEM=="usb", ATTR{idVendo ...

  2. leetcode题目解答报告(1)

    Remove Element 题目: Given an array and a value, remove all instances of that value in place and retur ...

  3. Jquery跨域调用

    今天在项目中须要做远程数据载入并渲染页面,直到开发阶段才意识到ajax跨域请求的问题,隐约记得Jquery有提过一个ajax跨域请求的解决方式,于是即刻翻出Jquery的API出来研究,发现JQuer ...

  4. 02-线性结构1 两个有序链表序列的合并(15 point(s)) 【链表合并】

    02-线性结构1 两个有序链表序列的合并(15 point(s)) 本题要求实现一个函数,将两个链表表示的递增整数序列合并为一个非递减的整数序列. 函数接口定义: List Merge( List L ...

  5. codeforces 715c

    题目大意:给定一个有N个点的树,问其中有多少条路径满足他们的边权连成的数对M取余为0.其中gcd(M,10)=1. 题解: 很亲民的点分治题目,对每一层点分治,预处理每个点到当前根的数字并对m取余,和 ...

  6. HDU2068 RPG的错排 —— 错排

    题目链接:https://vjudge.net/problem/HDU-2068 RPG的错排 Time Limit: 1000/1000 MS (Java/Others)    Memory Lim ...

  7. C语言中的排序算法--冒泡排序,选择排序,希尔排序

    冒泡排序(Bubble Sort,台湾译为:泡沫排序或气泡排序)是一种简单的排序算法.它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来.走访数列的工作是重复地进行直到没 ...

  8. H3C-交换机端口绑定

    1.端口和MAC地址绑定: (1)使用am命令: [switch]am user-bind mac-address 00e0-fc23-f8d3 interface Ehternet 0/1 (2)使 ...

  9. Keras 可视化 model

    参考:https://keras.io/visualization/ error解决参考:http://blog.csdn.net/wangjian1204/article/details/50346 ...

  10. VC6.0 快捷键

    F1: 帮助 Ctrl+O   :OpenCtrl+P   :PrintCtrl+N   :NewCtrl+Shift+F2 :清除所有书签F2    :上一个书签Shift+F2 :上一个书签Alt ...