先给结论:可以,只要保证加载同步。
以下分析是以android-4.4.4_r1 Dalvik on ARM代码为例。
1 先看System.loadLibrary流程:
public static void loadLibrary(String libName) {
Runtime.getRuntime().loadLibrary(libName, VMStack.getCallingClassLoader());
}
VMStack.getCallingClassLoader()获取调用loadLibrary函数的classloader,Runtime.loadLibrary会从这个loader里查是否已加载,如果没有,走到
private String doLoad(String name, ClassLoader loader) {
String ldLibraryPath = null;
if (loader != null && loader instanceof BaseDexClassLoader) {
ldLibraryPath = ((BaseDexClassLoader) loader).getLdLibraryPath();
}
synchronized (this) {
return nativeLoad(name, loader, ldLibraryPath);
}
}
所以classloader决定了加载,与是否在子线程无关。比如如果用DexClassLoader动态加载apk,可以维护自己的LibraryPath。
public DexClassLoader(String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent)
2 再看调用基本流程(不感兴趣可以略过)
以InterpAsm-armv7-a-neon.S里L_OP_INVOKE_STATIC为例,
.L_OP_INVOKE_STATIC:
......
if(!method resolved) {
resolve
}
prepare frame......
if(native) {
ldr ip, [r2, #offMethod_nativeFunc] @ pcnativeFunc
blx ip
}
这里r2是Method结构体里DalvikBridgeFunc nativeFunc的偏移量,见Object.h。
nativeFunc初始赋值是在method初始化时设成dvmResolveNativeMethod,如果用户调用了
static bool dvmRegisterJNIMethod(ClassObject* clazz, const char* methodName,
const char* signature, void* fnPtr)
{
......
dvmUseJNIBridge(method, fnPtr);
}谈
dvmUseJNIBridge里会把method->nativeFunc设成fnPtr。
如果没显式注册过,dvmResolveNativeMethod会在已加载的库里找符合JNI mangleString形式(类似Java_com_xxx_xxx)的函数。
所以如果库没加载完成(题主说的子线程里),找不到就是找不到,会抛UnsatisfiedLinkError。
doLoad里的同步只保证多个线程同时加载只有一份生效,在native方法调用时不会先尝试加载的,所以你要自己保证加载线程与调用线程的加载同步。
3 保证加载同步有多种方法,比如:
a 静态块的写法会由虚拟机的类加载机制保证同步。所以题主要做的是分离出有native方法的类,把它的首次加载放在loadLibrary的子线程,否则如果主线程先用到这个类该anr还是anr。
b 手动写加载的同步代码。
4 题主的问题2应该是跟着问题1来的吧?如果泛泛谈Jni多线程,最常见要注意的是自己创建的native线程(比如pthread_create)如果要用Jni函数,必须先attach成java线程,不能与其它线程共享env。