头图

如果Android工程有第三方so库,并且jni编译后使用下面的cmake指定输出目录到 jniLibs下,有可能会发生 so库重复的错误。
请采用以下方法避免错误:

android {
             ..........
         packagingOptions {
            //解决AS BUG. 优先选择JNILIBS下的so库
            pickFirst 'lib/arm64-v8a/libDriver.so'
            pickFirst 'lib/armeabi/libDriver.so'
            pickFirst 'lib/armeabi-v7a/libDriver.so'
           }
}

每次生成的so所在的目录不是在 jniLibs下,虽然知道如果打包,会将它打包进去,但就是觉得看不见它,想提供给别人用,还要去某个目录找。设置:

set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/../jniLibs/${ANDROID_ABI})

生成多个so

#指定需要CMAKE的最小版本
cmake_minimum_required(VERSION 3.4.1)


#C 的编译选项是 CMAKE_C_FLAGS
# 指定编译参数,可选
SET(CMAKE_CXX_FLAGS "-Wno-error=format-security -Wno-error=pointer-sign")

#设置生成的so动态库最后输出的路径
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/../jniLibs/${ANDROID_ABI})

#设置头文件搜索路径(和此txt同个路径的头文件无需设置),可选
#INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/common)

#指定用到的系统库或者NDK库或者第三方库的搜索路径,可选。
#LINK_DIRECTORIES(/usr/local/lib)


#添加子目录,将会调用子目录中的CMakeLists.txt
ADD_SUBDIRECTORY(one)
ADD_SUBDIRECTORY(two)

这样就会先去跑到子目录下的 one 和 two 的CmakeLists.txt,执行成功再返回。
此时子目录one下的CmakeLists.txt:

#继承上一层的CMakeLists.txt的变量,也可以在这里重新赋值
#C 的编译选项是 CMAKE_C_FLAGS
# 指定编译参数,可选
#SET(CMAKE_CXX_FLAGS "-Wno-error=format-security -Wno-error=pointer-sign")

#生成so动态库
ADD_LIBRARY(one-lib SHARED one.cpp)

target_link_libraries(one-lib log)

子目录two下的CmakeLists.txt:

#继承上一层的CMakeLists.txt的变量,也可以在这里重新赋值
#C 的编译选项是 CMAKE_C_FLAGS
# 指定编译参数,可选
#SET(CMAKE_CXX_FLAGS "-Wno-error=format-security -Wno-error=pointer-sign")

#生成so动态库
ADD_LIBRARY(two-lib SHARED two.cpp)

target_link_libraries(two-lib log)

最后生成了以下两个so文件,并自动按照abi分别放置在了 jniLibs下

findClass失败

通常,我们在native层想调用Java方法时,我们首先要获取Java中的方法所在的类。我们一般的方法是:

result = env->FindClass(name);

但如果在子线程中获取时,就会出现找不到类的情况。关于这一问题,详见StackOverFlow

简单来讲,当我们在自己创建的子线程想要通过JVM获取Class时,Android会为我们启动系统的ClassLoader而不是我们App的ClassLoader

Google提供了几种解决方法,在这里不一一赘述。本文中采用的方法是:通过缓存一个静态的全局ClassLoader对象,当env->findClass失败时,通过缓存的ClassLoader获取需要的类。

void JniHelper::setJVM(JNIEnv *env) {
    jvmEnv = env;
    jclass randomClass = env->FindClass("com/example/oceanlong/ndkmaintest/MainActivity");
    jclass classClass = env->GetObjectClass(randomClass);
    jclass classLoaderClass = env->FindClass("java/lang/ClassLoader");
    jmethodID getClassLoaderMethod = env->GetMethodID(classClass, "getClassLoader",
                                                      "()Ljava/lang/ClassLoader;");
    jobject localClassLoader = env->CallObjectMethod(randomClass, getClassLoaderMethod);
    gClassLoader = env->NewGlobalRef(localClassLoader);
    //我在Android中用findClass不行,改成loadClass才可以找到class
    gFindClassMethod = env->GetMethodID(classLoaderClass, "findClass",
                                        "(Ljava/lang/String;)Ljava/lang/Class;");
}

jclass JniHelper::findClass(JNIEnv *env, const char* name) {
    jclass result = nullptr;
    if (env)
    {
        result = env->FindClass(name);
        jthrowable exception = env->ExceptionOccurred();
        if (exception)
        {
            env->ExceptionClear();
            return static_cast<jclass>(env->CallObjectMethod(gClassLoader, gFindClassMethod, env->NewStringUTF(name)));
        }
    }
    return result;
}

轻口味
16.9k 声望3.9k 粉丝

移动端十年老人,主要做IM、音视频、AI方向,目前在做鸿蒙化适配,欢迎这些方向的同学交流:wodekouwei