在Android中使用JNA

一.JNA简述

略。

二.so文件的编译

本文以C语言为例。

1.C源文件

1.  #include
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)  # 执行编译的工作路径在当前路径
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编译

有些网络文章中讲到的,可以不用App.mk文件。
这里只使用NDK进行编译。即 你电脑上没有安装AndroidStudio和Eclipse也无所谓。
建议将NDK的根路径配置到系统的环境变量,在cmd中输入ndk-build能看到如下信息:


image

这里以csource文件夹为例,将源码和mk文件放入,然后cmd的工作路径也切换到这里:


在Android中使用JNA_第1张图片
image

执行命令 ndk-build 进行编译,如果这时你还看到上图所示的2行信息,说明编译失败,Could not find application project directory !

此时可以直接指定编译入口:


1.  ndk-build NDK_PROJECT_PATH=./ NDK_APPLICATION_MK=Application.mk 

在Android中使用JNA_第2张图片
image

当前文件夹里生成新的目录,libs,其中就是我们的目标so文件。


在Android中使用JNA_第3张图片
image

三.JNA依赖的准备

前往 https://github.com/java-native-access/jna/releases ,下载最新的zip包。

在Android中使用JNA_第4张图片
image

将zip文件解码,打开 dist 目录,找到7个android-*.jar文件,解压得到其中的so库,并对应的放到7个平台目录中。当然这7个并非都需要,armeabi、armeabi-v7a是最常用的。


在Android中使用JNA_第5张图片
image

除了这些so文件,还需要2个jar。jna-min.jar 和 jna-platform.jar 。


在Android中使用JNA_第6张图片
image

四.在AndroidStudio中集成so的形式

按照平常的路子创意几个普通的AS项目。


在Android中使用JNA_第7张图片
image

1.libs方式

常用的方式,就是将so、jar、arr等依赖一股脑儿放到项目默认的libs目录里。直接强硬干脆利落。通常集成第三方的地图、推送、一些功能框架时这么做。


在Android中使用JNA_第8张图片
image

将第三方依赖加入libs后不用做其它过多配置,就可以在java代码中直接使用了。因为gradle里默认加载此目录中的依赖:


在Android中使用JNA_第9张图片
image

现在,在libs下放入我们需要的JNA依赖和之前编译好的so文件:


在Android中使用JNA_第10张图片
image

往常就可以直接java开黑,没有任何问题。但JNA的特殊性会导致一个异常:

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.(Native.java:190)
at com.sun.jna.Native.loadLibrary(Native.java:544)

此时,需要在Module的gradle里配置一下:


在Android中使用JNA_第11张图片
image

这样,无论是JNA的还是我们自己的so都能比较统一的管理。

2.jniLibs方式

相较于上面的方式1,这个多了一个目录,但gradle里不用过多配置。
jar包仍然放在默认的libs里。
然后在 src/main/ 下新建“jniLibs”目录,将so库文件放进去。


在Android中使用JNA_第12张图片
image

Module的gradle按照默认配置,无修改。


在Android中使用JNA_第13张图片
image

五.在Java中使用JNA

相较于JNI省事多了,JNA直接api调用即可。


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() {
            @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静态的、final最终的,相当于全局常量。

| 1. 接口JNATest继承自sun的Library,这个Library也是个接口。 |
| 2. JNATest内部通过sun的Native调用了loadLibrary方法,传入的第一个参数就是我们自己编译的so文件名(去掉‘lib’和后缀)。方法内部调用了第2个参数JNATest.class的类加载器,并为这个class创建了一个InvocationHandler,这个Handler去加载我们的自己的so。最终使用Proxy将准备好的种种生成一个代理使用。 |
| 3. INSTANCE这个代理就是实现了“add”方法的一个JNATest的实例。JNATest的add方法对应c代码中的add函数。须注意java的数据类型和c的数据类型的差异。本文为了简便而仅涉及int类型。 |

如此,当java调用INSTANCE的add时,最终通过代理反射去执行C定义的原生代码

你可能感兴趣的:(在Android中使用JNA)