Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android平台下JNI調用第三方so庫

Android平台下JNI調用第三方so庫

編輯:關於Android編程

在研究了幾天JNI後,在自己生成的so庫中調用第三方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方法。這種方法更加靈活。

一、下圖是我的項目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;

public class DemoMain extends Activity {

	static {
		System.loadLibrary("myhello");
		System.loadLibrary("hello");
		System.loadLibrary("hello-jni");
	}
	TextView textView;

	@Override
	protected void 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);
	}

	public native String getString();

	public native String getJNIString();
}

布局就一個TextView組件,不再介紹。

 

二、源文件的編寫

在使用javah生成頭文件後,要在源文件中使用include“xxxx.h”引入頭文件。如果頭文件不在jni根目錄下,還要在Android.mk中使用

LOCAL_C_INCLUDES:=(相對於jni目錄的)包含頭文件的目錄路徑

來聲明一下,否則報錯找不到頭文件。

然後實現自己聲明的native方法,再在其中調用第三方庫的函數。

具體代碼如下:

#include 
#include 
#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) {

	return Java_com_hello_hello_HelloActivity_sayHello(env, thiz);//調用libhello.so中的函數
}

jstring Java_com_example_jnidemo_DemoMain_getJNIString(JNIEnv* env,
		jobject thiz) {

	return Java_com_example_hellojni_HelloJni_stringFromJNI(env, thiz);}//調用libhello-jni.so中的函數


 

 

三、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為例,如下:

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到了系統文件夾下,其他應用也可以調用喽)。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved