Android平台下JNI调用第三方so库

 首先说一下在网上查找资料时,对于调用第三方so库,有人说有两种方法:

1.    对于so库的API符合JNI格式(即使用javah指令生成的头文件中那种格式),可以在Java代码中声明它对应的native方法,直接调  用。

    比如,jni方法名为: jstringJNICALL Java_com_example_hellojni_HelloJni_stringFromJNI(JNIEnv *,jobject);    (即前缀Java+包名+类名+方法名)

    那么这个方法名就是在java中声明的native方法名:publicnative String  stringFromJNI();


2.    对于so库的API不符合JNI格式,需要自己编写c/c++源文件,在该源文件实现自己的JNI格式native函数,在JNI函数中调用第三方so库的函数,再在java中调用自己实现的JNI格式的native方法。这种方法更加灵活。



jni函数的调用请参考我的另一篇博客http://blog.csdn.NET/u013403478/article/details/52068095,这里主要介绍第三方so库的配置、加载。




一、下图是我的项目JniDemo目录:


   

导入的两个第三方库是:libhello.solibhello-jni.so

自己从源文件myhello.c编译生成的库是:libmyhello.so


java中调用native方法代码如下:

[java]  view plain  copy
 print ?
  1. package com.example.jnidemo;  
  2.   
  3. import android.app.Activity;  
  4. import android.os.Bundle;  
  5. import android.view.Menu;  
  6. import android.view.MenuItem;  
  7. import android.widget.TextView;  
  8.   
  9. public class DemoMain extends Activity {  
  10.   
  11.     static {  
  12.         System.loadLibrary("myhello");  
  13.         System.loadLibrary("hello");  
  14.         System.loadLibrary("hello-jni");  
  15.     }  
  16.     TextView textView;  
  17.   
  18.     @Override  
  19.     protected void onCreate(Bundle savedInstanceState) {  
  20.         super.onCreate(savedInstanceState);  
  21.         setContentView(R.layout.activity_demo_main);  
  22.   
  23.         textView = (TextView) findViewById(R.id.text);  
  24.         String str = getString() + "." + getJNIString();  
  25.         textView.setText(str);  
  26.     }  
  27.   
  28.     public native String getString();  
  29.   
  30.     public native String getJNIString();  
  31. }  

布局就一个TextView组件,不再介绍。




二、源文件的编写

在使用javah生成头文件后,要在源文件中使用include“xxxx.h”引入头文件。如果头文件不在jni根目录下,还要在Android.mk中使用

       LOCAL_C_INCLUDES:=(相对于jni目录的)包含头文件的目录路径

来声明一下,否则报错找不到头文件。

然后实现自己声明的native方法,再在其中调用第三方库的函数。


具体代码如下:

[cpp]  view plain  copy
 print ?
  1. #include <string.h>  
  2. #include <jni.h>  
  3. #include "com_example_jnidemo_DemoMain.h"  
  4. #include "com_hello_hello_HelloActivity.h"  
  5. #include "com_example_hellojni_HelloJni.h"  
  6.   
  7. jstring Java_com_example_jnidemo_DemoMain_getString(JNIEnv* env, jobject thiz) {  
  8.   
  9.     return Java_com_hello_hello_HelloActivity_sayHello(env, thiz);//调用libhello.so中的函数  
  10. }  
  11.   
  12. jstring Java_com_example_jnidemo_DemoMain_getJNIString(JNIEnv* env,  
  13.         jobject thiz) {  
  14.   
  15.     return Java_com_example_hellojni_HelloJni_stringFromJNI(env, thiz);}//调用libhello-jni.so中的函数  





三、android.mkApplication.mk配置

Android.mk用来配置各个模块如何编译,如下:


[plain]  view plain  copy
 print ?
  1. LOCAL_PATH := $(call my-dir) #my-dir就是该Android.mk所在目录,本项目中即jni目录  
  2. include $(CLEAR_VARS)   #清楚此行之前除了LOCAL_PATH外所有的变量,因为定义的多个模块中会有相同名称的变量,目的是避免变量赋值冲突  
  3.   
  4. LOCAL_MODULE    := hello-jni    #指定一个当前模块名  
  5. LOCAL_SRC_FILES := libhello-jni.so  #要编译的源文件  
  6. include $(PREBUILT_SHARED_LIBRARY)  #编译目标,PREBUILT_表示已经编译好的,在使用NDK编译时不会再次编译,而是直接拷贝到libs目录  
  7.                                                                                 #预编译.a静态库使用 PREBUILT_STATIC_LIBRARY  
  8.   
  9. include $(CLEAR_VARS)  
  10.   
  11. LOCAL_MODULE    := hello  
  12. LOCAL_SRC_FILES := libhello.so  
  13. include $(PREBUILT_SHARED_LIBRARY)  
  14.   
  15. include $(CLEAR_VARS)  
  16.   
  17. LOCAL_MODULE    := myhello  #自己由源文件编译库的模块名  
  18. LOCAL_SRC_FILES := myhello.c    #将被编译的源文件  
  19.   
  20. #【重要关键点】引入依赖的第三方so库(使用模块名引入),使用\可以引入多个(注意:\符号后没有空格或其他字符)  
  21. LOCAL_SHARED_LIBRARIES := \  
  22. hello-jni\  
  23. hello  
  24. include $(BUILD_SHARED_LIBRARY) #表示编译成.so共享库,即动态库  

Application.mk用来配置目标编译ABI(应用二进制接口),如arm64-v8aarmeabiarmeabi-v7amipsmips64x86x86_64。以armeabi-v7a为例,如下:

[plain]  view plain  copy
 print ?
  1. APP_ABI := armeabi-v7a  #表示 编译目标 ABI(应用二进制接口)  


四、在终端使用NDK编译jni目录


如果看到所有库都install到了libs目录,没有报错,就编译成功了.


    补充:下面结合我遇到过的编译错误,解析一下原因及解决手段:


        前提说明:在自己的编译生成的动态库中依赖了第三方so库(编译时第三方库不会再次编译,而是直接拷贝到libs中,所以一般是在编译依赖了第三方库的自己的动态库时报的错)

(每次用NDK重新编译,最好删除之前生成的编译结果so库和obj目录)


(1)报错error:undefined reference to'Java_com_example_hellojni_HelloJni_stringFromJNI'

collect2:error: ld returned 1 exit status

    网上有人说LOCAL_ALLOW_UNDEFINED_SYMBOLS:= true就可以编译过,但这是治标不治本,运行时依然报错。


错误原因:so库在生成时,如果Application.mk声明一个变量APP-ABI:=xxx,会生成不同平台下的so库,而且编译时64位平台的so库无法在32位平台上被链接,这才报了这个解决依赖链接时找不到库中方法的问题,所以虽然Android.mk中指明了是PREBUILT_SHARED_LIBRARYso库,但不被链接还是找不到库中API


解决方法:获取so库时最好要取得相应版本的库(armeabi-v7aarmeabi都是32位,一般情况下应该互相兼容,但不兼容64位的arm64-v8a)。

(2)报错 error adding symbols:File in wrong format

collect2:error: ld returned 1 exit status

(ld是链接操作)


错误原因:如果Application.mkAPP-ABI:=的目标编译平台版本为64位,而实际导入的so库版本是32位,就会不识别该so库(wrong format)。


解决方法:Application.mk(如果没有,创建)中,把APP-ABI:=xxx的目标编译版本降低点,如armeabi-v7aarmeabi这些32位等等,使之与实际导入so库匹配



整理思路:(可以在终端中,使用$file xxx.so指令查看动态库是32位还是64位。)


接下来我通过对比不同so库与编译目标ABI来进行解析:


     前提准备:在自己由源文件编译的动态库中,假设依赖调用了两个第三方so a.so(准备了各个ABI版本)和b.so(只有版本为armeabi的)。


1. 第三方a.so库版本arm64-v8a,(不创建Application.mk)默认目标编译版本(默认是armeabi版本):

    

    报错:Fileformat not recognized

    原因:默认的目标编译版本为32位,比第三方a.so库的64位低,识别不了a.so库。


2. 第三方a.so库版本arm64-v8aApplication.mk中目标编译版本APP-ABI:= armeabi-v7a


    报错:Fileformat not recognized

    原因:目标编译版本是32位,比第三方64位的a.so库低,识别不了64a.so库。


3.第三方a.so库版本armeabi-v7aApplication.mk中目标编译版本APP-ABI:= arm64-v8a


    报错:erroradding symbols: File in wrong format

    原因:目标编译版本是64位比32位的第三方so库高,a.sob.so被认为文件格式错误。


4. 第三方a.so库版本armeabi-v7aApplication.mk中目标编译版本APP-ABI:= armeabi-v7a


    结果:版本匹配,NDK编译正常,armeabi-v7a兼容armeabi版本的b.soapp运行正常


5. 第三方a.so库版本armeabiApplication.mk中目标编译版本APP-ABI:= armeabi-v7a


    结果:版本匹配,NDK编译正常,armeabi-v7aarmeabi互相兼容,app运行正常


6. 第三方a.so库版本arm64-v8aApplication.mk中目标编译版本APP-ABI:= arm64-v8a


    报错:erroradding symbols: File in wrong format

    原因:目标编译版本高于b.so,所以在解决32位的b.so的依赖时报错,但64位的a.so编译正常。



总结:

    调用第三方so库时要先查看文件ABI版本,根据3264位的相应ABI版本去定义Application.mk中目标编译版本APP-ABI。当然最好都是同一种版本,避免出现不识别、不兼容。




五、运行APP



补充:

我的demo是可以直接运行并调用第三方so库的,但在实际项目中还是遇到了loadLibrary()找不到so库的问题,报了下面的异常:


Couldn't load CloudService from loader dalvik.system.PathClassLoader[

DexPathList[[zip file "/data/app/com.example.demo-1.apk"],

nativeLibraryDirectories=[/data/app-lib/com.example.cameraframedatademo-1,

/vendor/lib,

/system/lib]]]: findLibrary returned null


解决方法: 既然是从DexPathList、nativeLibraryDirectories路径中找不到so库文件,那就手动push到对应目录下。我这里是把所有第三方库都adb push 到了设备的system/lib

目录下(如果需要且支持64位so库,请push到system/lib64目录下),这样项目调用最终会在system/lib目录下找到so库(由于push到了系统文件夹下,其他应用也可以调用

喽)。




demo项目源码下载地址:http://download.csdn.net/detail/u013403478/9596669



转载出处:http://blog.csdn.net/u013403478/article/details/52134870

猜你喜欢

转载自blog.csdn.net/qq_28641023/article/details/77531835