JNI简介

作者: dafaycoding | 来源:发表于2017-01-16 11:26 被阅读216次

    JNI和NDK?

    • JNI (Java Native Interface)是一套编程接口,用来实现java代码和其他语言(c、C++或汇编)进行交互。这里需要注意的是JNI是JAVA语言自己的特性,也就是说JNI和Android没有关系。在Windows下面用JAVA做开发也经常会用到JNI,例如:读写系统注册表等。
    • NDK(Native Development Kit)是Google提供的一套工具集,可以让你其他语言(C、C++或汇编)开发 Android的 JNI。NDK可以编译多平台的so,开发人员只需要简单修改 mk 文件说明需要的平台,不需要改动任何代码,NDK就可以帮你编译出所需的so。
      用JNI做应用开发难度要比JAVA难很多,门槛也要高很多,如果你对C/C++把握的不好应用还会出现难以发现的Bug!所以通常在对性能要求比较高才会使用。游戏引擎就是一个对性能要求极高的例子。另外就是如果你想把核心的一些算法或处理逻辑保护起来,选用JNI也是一个不错的方案。

    为什么用JNI?

    • 扩展了jvm的功能, wifi热点共享功能, wifi使用到硬件, 硬件需要驱动程序, c语言开发驱动程序.
    • c/c++代码执行效率高. c可以直接操控内存. c指针可以直接根据某个地址操控内存. 执行的效率和速度提升.
    • c语言的开源库(opengl-es, opencv, ffmpeg, 7zip, coscos2d-x, Unity3d)
    • 车载电脑.

    JNI编译特点

    • java特点: 一处编译, 到处运行. 跨平台

    • windows下可执行的二进制文件格式: exe

    • linux下可执行的二进制文件格式: elf

    • 在windows系统下编译出linux系统下可执行的文件.

    • 在一个平台下编译另一个平台下可执行的文件需要用到: 交叉编译.

    • 交叉编译: cgywin模拟linux操作系统.

    • NDK(native develop kits) 本地开发工具集. 内部包含了交叉工具链


    1、eclipse下jni开发流程简介

    第一个JNI的HelloWorld程序。

        1. 在MainActivity中声明一个native方法:
    
                public native String sayHello();
    
        2. 在工程的根目录下创建一个jni的文件夹, 并在里边创建一个Hello.c的文件.
    
        3. 在Hello.c文件中实现MainActivity中声明的native方法.
    
                
                jstring Java_com_example_jnihelloworld_MainActivity_sayHello(JNIEnv* env, jobject obj) {
                    // jstring     (*NewStringUTF)(JNIEnv*, const char*);
                    char* text = "Hello from c!!!!";
                    return (**env).NewStringUTF(env, text);
                }
    
        4. 在jni的目录下创建一个Android.mk文件.
    
                LOCAL_PATH := $(call my-dir)
                
                include $(CLEAR_VARS)
                
                LOCAL_MODULE    := example
                LOCAL_SRC_FILES := Hello.c
                
                include $(BUILD_SHARED_LIBRARY)
    
        5. 在工程的根目录下执行ndk-build命令, 编译.so文件.
    
        6. 在调用native方法前, 加载.so的库文件.
    
                System.loadLibrary("example");
    
        7. 在java代码中调用native方法, 工程会自动去找.so文件中对应实现的代码.
    
    • native方法名的问题: 当方法中有下划线时, 在c语言中方法的声明需要在_后面加个1: _1

    • javah 生成带有native方法的头文件.

      • 使用方式: javah -jni com.example.jnihelloworld.MainActivity
        • JDK1.7 需要在工程的src目录下执行上面命令.
        • JDK1.6 需要在工程的bin/classes目录下执行上面命令.
    • Android.mk文件

        # $(call )调用工具链中某个方法, 获取当前的目录.
        LOCAL_PATH := $(call my-dir)
        # 清空上一次编译工具的配置, 不会清空LOCAL_PATH的属性.
        include $(CLEAR_VARS)
        # 定义生成出来的链接库文件的名字
        # 前面追加lib关键字, 如果已经加了lib关键字, 就不会在追加.
        # 后面追加.so后缀名. 不允许追加扩展名.
        LOCAL_MODULE    := example
        # 根据指定的源文件, 去编译出.so的库文件. 当多个源文件时, 把名字以空格隔开就可以了.
        LOCAL_SRC_FILES := Hello1.c
        # 指定当前编译出来的链接库文件是什么类型.
        # 动态链接库: BUILD_SHARED_LIBRARY  .so  文件小
        # 静态链接库: BUILD_STATIC_LIBRARY  .a   文件大
        include $(BUILD_SHARED_LIBRARY)
      
    • 简便开发流程 刚开始配置稍微点, 后面不需要在配置, 修改完c文件之后, 直接右键运行。*
    
        1. 在java代码中声明native方法.
    
        2. 在window -> preferences -> Android -> NDK 把ndk的根目录配置进去.
           右键工程 Android Tools -> Add Native Support 写进去一个函数库的名字.
    
        3. 使用javah命令生成.h的头文件, 把头文件拷贝到工程的jni目录下.
    
        4. 实现c代码: 把生成的.h头文件引入进来(使用双引号方式引入).
    
        5. 处理错误和代码提示: 右键工程 -> Properties -> C/C++ General -> Paths and Symbols -> Includes -> Add 把android-ndk-r9\platforms\android-9\arch-arm\usr\include配置进去.
    
        6. 把c代码对应native方法实现了.
    
        7. 在java代码中加载.so库文件, 调用native方法.
    
    • 在c代码中使用android的logcat日志.

      • Android.mk文件增加以下内容

          LOCAL_LDLIBS += -llog
        
      • C代码中增加以下内容

          #include <android/log.h>
          #define LOG_TAG "System.out"
          #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
          #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
        
    • 生成类的方法签名

    javap -s com.example.callbackjava.JNI
    

    jni开发常见错误:

    1. 在jni目录下没有发现Android.mk文件
    
            Android NDK: Your APP_BUILD_SCRIPT points to an unknown file: ./jni/Android.mk
    
    2. c文件中没有导入jni.h的头文件.
    
            jni/CommonError.c:4:1: error: unknown type name 'JNIEXPORT'
            jni/CommonError.c:4:19: error: expected '=', ',', ';', 'asm' or '__attribute__'
            before 'JNICALL'
            jni/CommonError.c:4:19: error: unknown type name 'JNICALL'
    
    3. c代码实现的方法没有写形参的名字.
    
            jni/CommonError.c: In function 'Java_com_example_commonerrordemo_MainActivity_
            sayHelloInC':
            jni/CommonError.c:6:3: error: parameter name omitted
            jni/CommonError.c:6:3: error: parameter name omitted
            jni/CommonError.c:8:13: error: 'env' undeclared (first use in this function)
            jni/CommonError.c:8:13: note: each undeclared identifier is reported only once f
            or each function it appears in
    
    4. 调用native方法, 没有加载.so文件.
    
            No implementation found for native Lcom/example/commonerrordemo/MainActivity;.sayHelloInC ()Ljava/lang/String;
    
    5. 加载.so文件时, 名字写错.
    
            java.lang.UnsatisfiedLinkError: Couldn't load libcommonerror.so: findLibrary returned null
    
    6. 当前生成的arm平台下的.so文件, 运行在了x86的平台模拟器下.
    
            java.lang.UnsatisfiedLinkError: Couldn't load libcommonerror.so: findLibrary returned null
    
        - 解决方案: 在jni的目录下, 创建一个Application.mk, 内容如下:
    
                # 生成所有的机器码.
                APP_ABI := all
    
                # 生成单个平台的机器码
                APP_ABI := x86 armeabi 
    

    2、Elcipse下jni程序迁移到AndroidStudio

    1.  设置NDK路径 
        选择File>Project Structure>SDK Location(快捷键:Cmd+;),指定NDK的路径。
    2.  把jni文件下内容拷贝到AndroidStudio项目下面
    3.  AndroidStudio项目的gradle下添加下面配置
        externalNativeBuild{
            ndkBuild{
                path file("src/main/jni/Android.mk");
            }
        }
    

    如图所示

    图2-1

    3、AndroidStudio下jni开发流程简介

    1. 添加本地方法
    public static native String hello_jni();
    
    1. 加载so文件
    static {
            System.loadLibrary("hello_jni"); // 注意没有前缀lib和后缀.so
        }
    
    1. 利用javah命令生成JAVA所对应的JNI头文件,1、打开终端,2、将目录定位到java目录下,3、通过javah产生头文件。
    图1-1
    1. 将com_example_idea_jnitest_1_HomeActivity.h拷贝一个将扩展名改为.c,在.c中完成业务逻辑处理相关代码:
    JNIEXPORT jstring JNICALL Java_com_example_idea_jnitest_11_HomeActivity_helloJni
    (JNIEnv * env, jobject obj) {
        return (*env)->NewStringUTF(env,"调用c代码  返回 hello!");
    }
    
    1. 修改Module中Build.gradle文件,在defaultConfig段落中加入ndk编译配置。
            ndk {
                moduleName "hello_jni"
            }
    
    图1-2
    1. 此时编译会出错
    Error: NDK integration is deprecated in the current plugin. Consider 
    trying the new experimental plugin. For details, see 
    http://tools.android.com/tech-docs/new-build-system/gradle-
    experimental. Set "android.useDeprecatedNdk=true" in 
    gradle.properties to continue using the current NDK integration.
    
    解决:
    提示已经告诉我们需要在gradle.properties设置android.useDeprecatedNdk=true,设置好后点击同步按钮。
    
    图1-3

    参考:
    Android Studio JNI开发入门教程
    Android JNI编程—NDK编译
    使用Android Studio进行JNI开发 - Mac篇

    相关文章

      网友评论

          本文标题:JNI简介

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