一.JNA簡述
略。
二.so文件的編譯
本文以C語言為例。
1.C源文件
1. #include<stdio.h>
2. int add(int a,int b);
3. int add(int a,int b){
4. int c = a + b ;
5. return c ;
6. }
2.Android.mk文件
1. LOCAL_PATH := $(call my-dir) # 執(zhí)行編譯的工作路徑在當前路徑
2. include $(CLEAR_VARS) # 固定寫法 :>
4. LOCAL_MODULE := jnatest # 自定義的編譯成的so文件名
5. CODE_PATH := ./ # 源碼文件目錄
7. LOCAL_SRC_FILES := $(CODE_PATH)/jnatest.c # 要編譯的c或cpp源碼文件,多個文件用空格分開
8. LOCAL_C_INCLUDES := $(LOCAL_PATH)/$(CODE_PATH) # 頭文件所在目錄
10. include $(BUILD_SHARED_LIBRARY) # 生成so文件
12. ########################## 或者以下簡單方式 #########################
14. LOCAL_PATH := $(call my-dir)
15. include $(CLEAR_VARS)
17. LOCAL_MODULE := 自定義so文件名
18. LOCAL_SRC_FILES := 源碼.c
20. include $(BUILD_SHARED_LIBRARY)
3.Application.mk文件
1. APP_BUILD_SCRIPT := Android.mk # 指明同目錄下的Android.mk
2. APP_STL := gnustl_shared # 運行庫颇蜡,一般安卓使用stlport_static
3. APP_ABI := armeabi armeabi-v7a x86 # 目標平臺,多個用空格
4.使用NDK編譯
有些網(wǎng)絡(luò)文章中講到的,可以不用App.mk文件哼绑。
這里只使用NDK進行編譯。即 你電腦上沒有安裝AndroidStudio和Eclipse也無所謂碉咆。
建議將NDK的根路徑配置到系統(tǒng)的環(huán)境變量抖韩,在cmd中輸入ndk-build能看到如下信息:
這里以csource文件夾為例,將源碼和mk文件放入疫铜,然后cmd的工作路徑也切換到這里:
執(zhí)行命令 ndk-build 進行編譯茂浮,如果這時你還看到上圖所示的2行信息,說明編譯失敗壳咕,Could not find application project directory !
此時可以直接指定編譯入口:
1. ndk-build NDK_PROJECT_PATH=./ NDK_APPLICATION_MK=Application.mk
當前文件夾里生成新的目錄席揽,libs,其中就是我們的目標so文件谓厘。
三.JNA依賴的準備
前往 https://github.com/java-native-access/jna/releases 幌羞,下載最新的zip包。
將zip文件解碼竟稳,打開 dist 目錄属桦,找到7個android-*.jar文件熊痴,解壓得到其中的so庫,并對應(yīng)的放到7個平臺目錄中聂宾。當然這7個并非都需要愁拭,armeabi、armeabi-v7a是最常用的亏吝。
除了這些so文件岭埠,還需要2個jar。jna-min.jar 和 jna-platform.jar 蔚鸥。
四.在AndroidStudio中集成so的形式
按照平常的路子創(chuàng)意幾個普通的AS項目惜论。
1.libs方式
常用的方式,就是將so止喷、jar馆类、arr等依賴一股腦兒放到項目默認的libs目錄里。直接強硬干脆利落弹谁。通常集成第三方的地圖乾巧、推送、一些功能框架時這么做预愤。
將第三方依賴加入libs后不用做其它過多配置沟于,就可以在java代碼中直接使用了。因為gradle里默認加載此目錄中的依賴:
現(xiàn)在植康,在libs下放入我們需要的JNA依賴和之前編譯好的so文件:
往常就可以直接java開黑旷太,沒有任何問題。但JNA的特殊性會導(dǎo)致一個異常:
com.cuiweiyou.jnaprj E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.cuiweiyou.jnaprj, PID: 31846
java.lang.UnsatisfiedLinkError: Native library (com/sun/jna/android-arm/libjnidispatch.so) not found in resource path (.)
at com.sun.jna.Native.loadNativeDispatchLibraryFromClasspath(Native.java:962)
at com.sun.jna.Native.loadNativeDispatchLibrary(Native.java:922)
at com.sun.jna.Native.<clinit style="margin: 0px; padding: 0px; outline: 0px; border: 0px; vertical-align: baseline; word-wrap: break-word; -webkit-appearance: none; box-sizing: border-box; background: transparent;">(Native.java:190)
at com.sun.jna.Native.loadLibrary(Native.java:544)</clinit>
此時销睁,需要在Module的gradle里配置一下:
這樣供璧,無論是JNA的還是我們自己的so都能比較統(tǒng)一的管理。
2.jniLibs方式
相較于上面的方式1冻记,這個多了一個目錄睡毒,但gradle里不用過多配置。
jar包仍然放在默認的libs里冗栗。
然后在 src/main/ 下新建“jniLibs”目錄演顾,將so庫文件放進去。
Module的gradle按照默認配置贞瞒,無修改偶房。
五.在Java中使用JNA
相較于JNI省事多了趁曼,JNA直接api調(diào)用即可军浆。
import com.sun.jna.Library;
import com.sun.jna.Native;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
int c = JNATest.INSTANCE.add(22, 33);
Log.e("ard", "JNA返回:" + c);
return null;
}
}.execute();
}
interface JNATest extends Library {
JNATest INSTANCE = (JNATest) Native.loadLibrary("jnatest", JNATest.class);
public int add(int a, int b);
}
}
接口的屬性是public公共的、static靜態(tài)的挡闰、final最終的乒融,相當于全局常量掰盘。
| 1. 接口JNATest繼承自sun的Library,這個Library也是個接口赞季。 |
| 2. JNATest內(nèi)部通過sun的Native調(diào)用了loadLibrary方法愧捕,傳入的第一個參數(shù)就是我們自己編譯的so文件名(去掉‘lib’和后綴)。方法內(nèi)部調(diào)用了第2個參數(shù)JNATest.class的類加載器申钩,并為這個class創(chuàng)建了一個InvocationHandler次绘,這個Handler去加載我們的自己的so。最終使用Proxy將準備好的種種生成一個代理使用撒遣。 |
| 3. INSTANCE這個代理就是實現(xiàn)了“add”方法的一個JNATest的實例邮偎。JNATest的add方法對應(yīng)c代碼中的add函數(shù)。須注意java的數(shù)據(jù)類型和c的數(shù)據(jù)類型的差異义黎。本文為了簡便而僅涉及int類型禾进。 |
如此,當java調(diào)用INSTANCE的add時廉涕,最終通過代理反射去執(zhí)行C定義的原生代碼