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

转载:http://www.2cto.com/kf/201608/535654.html

在研究了几天JNI后,在自己生成的so库中调用第三方so库时遇到问题,解决之后特意整理、记录一下。

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

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

比如,jni方法名为:jstringJNICALLJava_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方法。这种方法更加灵活。

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

\

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

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

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

package com.example.jnidemo;
 
import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.TextView;
 
publicclass DemoMain extends Activity {
 
     static {
        System.loadLibrary( "myhello" );
        System.loadLibrary( "hello" );
        System.loadLibrary( "hello-jni" );
    }
    TextView textView;
 
     @Override
     protectedvoid onCreate(Bundle savedInstanceState) {
         super .onCreate(savedInstanceState);
        setContentView(R.layout.activity_demo_main);
 
        textView = (TextView) findViewById(R.id.text);
        String str = getString() + "." + getJNIString();
        textView.setText(str);
    }
 
     publicnative String getString();
 
     publicnative String getJNIString();
}

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

二、源文件的编写

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

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

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

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

具体代码如下:

#include <string.h>
#include <jni.h>
#include "com_example_jnidemo_DemoMain.h"
#include "com_hello_hello_HelloActivity.h"
#include "com_example_hellojni_HelloJni.h"
 
jstring Java_com_example_jnidemo_DemoMain_getString(JNIEnv* env, jobject thiz) {
    //调用libhello.so中的函数
     return Java_com_hello_hello_HelloActivity_sayHello(env, thiz);
}
 
jstring Java_com_example_jnidemo_DemoMain_getJNIString(JNIEnv* env,
        jobject thiz) {
         //调用libhello-jni.so中的函数
     return Java_com_example_hellojni_HelloJni_stringFromJNI(env, thiz);
}
?

三、Android.mk,Application.mk配置

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

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

Application.mk用来配置目标编译ABI(应用二进制接口),如arm64-v8a、armeabi、armeabi-v7a、mips、mips64、x86、x86_64。

以armeabi-v7a为例,如下:


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_LIBRARY的so库,但不被链接还是找不到库中API的。

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

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

collect2:error: ld returned 1 exit status

(ld是链接操作)

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

解决方法:在Application.mk(如果没有,创建)中,把APP-ABI:=xxx的目标编译版本降低点,如armeabi-v7a、armeabi这些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-v8a,Application.mk中目标编译版本APP-ABI:= armeabi-v7a

报错:Fileformat not recognized

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

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

报错:erroradding symbols: File in wrong format

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

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

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

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

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

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

报错:erroradding symbols: File in wrong format

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

总结:

调用第三方so库时要先查看文件ABI版本,根据32或64位的相应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到了系统文件夹下,其他应用也可以调用喽)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值