在项目中遇到了加载so库比较诡异的一个现象,现记录下来,以做总结。
以下以举例的方式讲述:
项目中有两个so库,一个是libhellojni.so,一个是libhellojni2.so,这两个库的都有相同函数,一个是动态注册的,一个是静态注册的,如下:
编译libhellojni.so的代码如下:
jni_self.cpp
1
#include <jni.h>
2
#include <stdio.h>
3
#include "jni_self.h"
4
5
jstring com_example_fun_HelloJni_getString(JNIEnv* env, jobject thiz)
6
{
7
char *c = "HelloJni";
8
jstring s = (*env).NewStringUTF(c);
9
return s;
10
}
11
12
jstring com_example_fun_HelloJni_getFirst(JNIEnv* env, jobject thiz)
13
{
14
char *c = "first";
15
jstring s = (*env).NewStringUTF(c);
16
return s;
17
}
18
19
jstring com_example_fun_HelloJni_getSecond(JNIEnv* env, jobject thiz)
20
{
21
char *c = "second";
22
jstring s = (*env).NewStringUTF(c);
23
return s;
24
}
25
26
static JNINativeMethod nativeMethods[] =
27
{
28
{"getString", "()Ljava/lang/String;", (void*)com_example_fun_HelloJni_getString},
29
{"getSecond", "()Ljava/lang/String;", (void*)com_example_fun_HelloJni_getSecond},
30
{"getFirst", "()Ljava/lang/String;", (void*)com_example_fun_HelloJni_getFirst}
31
};
32
33
static int registerNativeMethods(JNIEnv* env)
34
{
35
int result = -1;
36
jclass clazz = env->FindClass("com/example/fun/HelloJni");
37
if (NULL != clazz)
38
{
39
if (env->RegisterNatives(clazz, nativeMethods, sizeof(nativeMethods)
40
/ sizeof(nativeMethods[0])) == JNI_OK)
41
{
42
result = 0;
43
}
44
}
45
return result;
46
}
47
48
jint JNI_OnLoad(JavaVM* vm, void* reserved)
49
{
50
JNIEnv* env = NULL;
51
jint result = -1;
52
if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) == JNI_OK)
53
{
54
if (NULL != env && registerNativeMethods(env) == 0)
55
{
56
result = JNI_VERSION_1_4;
57
}
58
}
59
return result;
60
}
Android.mk
1
LOCAL_PATH:= $(call my-dir)
2
3
include $(CLEAR_VARS)
4
5
LOCAL_MODULE := libhellojni
6
LOCAL_SRC_FILES := \
7
jni_self.cpp
8
9
LOCAL_LDLIBS := -llog
10
11
LOCAL_SHARED_LIBRARIES := \
12
libstlport \
13
libcutils \
14
libutils
15
16
include $(BUILD_SHARED_LIBRARY)
编译libhellojni2.so的代码如下:
com_example_fun_HelloJni.h
1
#include <jni.h>
2
3
#ifndef _Included_com_example_fun_HelloJni
4
#define _Included_com_example_fun_HelloJni
5
#ifdef __cplusplus
6
extern "C" {
7
#endif
8
9
JNIEXPORT jstring JNICALL Java_com_example_fun_HelloJni_getString
10
(JNIEnv *, jobject);
11
12
JNIEXPORT jstring JNICALL Java_com_example_fun_HelloJni_getFirst
13
(JNIEnv *, jobject);
14
15
JNIEXPORT jstring JNICALL Java_com_example_fun_HelloJni_getSecond
16
(JNIEnv *, jobject);
17
#ifdef __cplusplus
18
}
19
#endif
20
#endif
com_example_fun_HelloJni.cpp
1
#include "com_example_fun_HelloJni.h"
2
3
JNIEXPORT jstring JNICALL Java_com_example_fun_HelloJni_getString(JNIEnv * env, jobject jobj)
4
{
5
char *c = "HelloJni2";
6
jstring s = (*env).NewStringUTF(c);
7
return s;
8
}
9
10
JNIEXPORT jstring JNICALL Java_com_example_fun_HelloJni_getFirst(JNIEnv * env, jobject jobj)
11
{
12
char *c = "first2";
13
jstring s = (*env).NewStringUTF(c);
14
return s;
15
}
16
17
JNIEXPORT jstring JNICALL Java_com_example_fun_HelloJni_getSecond(JNIEnv * env, jobject jobj)
18
{
19
char *c = "second2";
20
jstring s = (*env).NewStringUTF(c);
21
return s;
22
}
Android.mk
1
LOCAL_PATH:= $(call my-dir)
2
3
include $(CLEAR_VARS)
4
5
LOCAL_MODULE := libhellojni2
6
LOCAL_SRC_FILES := \
7
com_example_fun_HelloJni.cpp
8
9
LOCAL_LDLIBS := -llog
10
11
LOCAL_SHARED_LIBRARIES := \
12
libstlport \
13
libcutils \
14
libutils
15
16
include $(BUILD_SHARED_LIBRARY)
而对应的com.example.fun.HelloJni如下:
1
public class HelloJni {
2
public native String getString();
3
public native String getFirst();
4
//相比 本地层方法 少了一个 getSecond();
5
}
在代码中调用HelloJni,如下所示:
1
static {
2
System.loadLibrary("hellojni2");
3
try{
4
System.loadLibrary("hellojni");
5
}catch (Throwable t){
6
t.printStackTrace();
7
}
8
}
9
10
private void testLib(){
11
HelloJni helloJni = new HelloJni();
12
Log.v("testLib",helloJni.getString());
13
Log.v("testLib",helloJni.getFirst());
14
}
在程序启动时,会有看到如下日志:
1
E/art: ----- class 'Lcom/example/fun/HelloJni;' cl=0x12c6ba00 -----
2
E/art: objectSize=420 (412 from super)
3
E/art: access=0x0000.0001
4
E/art: super='java.lang.Class<java.lang.Object>' (cl=0x0)
5
E/art: vtable (2 entries, 11 in super):
6
E/art: 0: java.lang.String com.example.fun.HelloJni.getFirst()
7
E/art: 1: java.lang.String com.example.fun.HelloJni.getString()
8
E/art: direct methods (1 entries):
9
E/art: 0: void com.example.fun.HelloJni.<init>()
10
E/art: Failed to register native method com.example.fun.HelloJni.getSecond()Ljava/lang/String; in /data/app/com.zxl.test-1/base.apk
11
W/System.err: java.lang.UnsatisfiedLinkError: JNI_ERR returned from JNI_OnLoad in "/data/app/com.zxl.test-1/lib/arm64/libhellojni.so"
12
W/System.err: at java.lang.Runtime.loadLibrary(Runtime.java:371)
13
W/System.err: at java.lang.System.loadLibrary(System.java:989)
14
W/System.err: at com.zxl.test.TestLib.TestLibActivity.<clinit>(TestLibActivity.java:64)
15
W/System.err: at java.lang.reflect.Constructor.newInstance(Native Method)
16
W/System.err: at java.lang.Class.newInstance(Class.java:1572)
17
W/System.err: at android.app.Instrumentation.newActivity(Instrumentation.java:1065)
18
W/System.err: at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2199)
19
W/System.err: at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2360)
20
W/System.err: at android.app.ActivityThread.access$800(ActivityThread.java:144)
21
W/System.err: at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1278)
22
W/System.err: at android.os.Handler.dispatchMessage(Handler.java:102)
23
W/System.err: at android.os.Looper.loop(Looper.java:135)
24
W/System.err: at android.app.ActivityThread.main(ActivityThread.java:5221)
25
W/System.err: at java.lang.reflect.Method.invoke(Native Method)
26
W/System.err: at java.lang.reflect.Method.invoke(Method.java:372)
27
W/System.err: at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:898)
28
W/System.err: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:693)
29
W/System.err: at de.robv.android.xposed.XposedBridge.main(XposedBridge.java:133)
其中“Failed to register native method com.example.fun.HelloJni.getSecond()Ljava/lang/String;” 和
“java.lang.UnsatisfiedLinkError: JNI_ERR returned from JNI_OnLoad in "/data/app/com.zxl.test-1/lib/arm64/libhellojni.so"”
但是,我们看maps表(cat /proc/进程名/maps |grep hellojni)如下:
1
7f76231000-7f76232000 r-xp 00000000 b3:1e 1708893 /data/app/com.zxl.test-1/lib/arm64/libhellojni2.so
2
7f76241000-7f76242000 r--p 00000000 b3:1e 1708893 /data/app/com.zxl.test-1/lib/arm64/libhellojni2.so
3
7f76242000-7f76243000 rw-p 00001000 b3:1e 1708893 /data/app/com.zxl.test-1/lib/arm64/libhellojni2.so
4
7f76243000-7f76244000 r-xp 00000000 b3:1e 1708894 /data/app/com.zxl.test-1/lib/arm64/libhellojni.so
5
7f76253000-7f76254000 r--p 00000000 b3:1e 1708894 /data/app/com.zxl.test-1/lib/arm64/libhellojni.so
6
7f76254000-7f76255000 rw-p 00001000 b3:1e 1708894 /data/app/com.zxl.test-1/lib/arm64/libhellojni.so
发现虽然libhellojni.so加载出错了,但是还是被加载到内存中了。
执行前面的方法testLib(),日志如下:
1
V/testLib1: HelloJni
2
V/testLib1: first2
发现第一个方法getString()调用到的是libhellojni.so库,而第二个方法getFirst()调用到的竟然是libhellojni2.so中的方法。
出现此种情况的原因,应该是动态注册 和 静态注册 的缘故。当加载libhellojni.so时,动态注册了getString()方法,而由于动态注册getSecond()方法时,HelloJni.java中没有声明此方法导致动态注册中断,而我们又在java代码中catch了这个异常,因此会继续加载libhellojni2.so。
在调用方法时,我们调用getString()方法,由于这个方法动态注册成功了,因此会调用到libhellojni.so中,而再调用getFirst()方法,这个方法动态注册失败了,因此系统不会去libhellojni.so中查找了,转而内存中加载的其他的so库中查找看有没有能够匹配getFirst()方法的函数,结果在libhellojni2.so中查找到了,因此调用到libhellojni2.so库中。
总结:
出现这个现象的原因在于不了解机制和疏忽大意:
1. 只在cpp中增加了对应的函数,并且增加的函数注册方法也不是放在nativeMethods[]声明的最后,而是放到了中间。
2. 忘记在HelloJni.java中增加对应的方法。
之后,要多思考和细心。