美文网首页JNI/JNA
在Android中使用JNA

在Android中使用JNA

作者: Solang | 来源:发表于2018-04-11 13:43 被阅读822次

    一.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)  # 执行编译的工作路径在当前路径
    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的工作路径也切换到这里:


    image

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

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

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

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


    image

    三.JNA依赖的准备

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

    image

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


    image

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


    image

    四.在AndroidStudio中集成so的形式

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


    image

    1.libs方式

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


    image

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


    image

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


    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.<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里配置一下:


    image

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

    2.jniLibs方式

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


    image

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


    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<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静态的、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

        本文链接:https://www.haomeiwen.com/subject/znpahftx.html