Android Studio开发JNI示例

JNI和NDK介绍

JNI(Java Native Interface),是方便Java调用C、C++等Native代码所封装的一层接口,相当于一座桥梁。通过JNI可以操作一些Java无法完成的与系统相关的特性,尤其在图像和视频处理中大量用到。

NDK(Native Development Kit)是Google提供的一套工具,其中一个特性是提供了交叉编译,即C或者C++不是跨平台的,但通过NDK配置生成的动态库却可以兼容各个平台。比如C在Windows平台编译后生成.exe文件,那么源码通过NDK编译后可以生成在安卓手机上运行的二进制文件.so

在AS中使用ndk-build开发JNI示例

Android Studio2.2之前对于JNI开发的支持不是很好,开发一般使用Eclipse+插件编写本地动态库。后面Google官方全面增强了对JNI的支持,包括内置NDK。

1.在AS中新建一个项目

2.声明一个native方法

package com.mercury.jnidemo;

public class JNITest {
   

    public native static String getStrFromJNI();

}

3.通过javah命令生成头文件

在AS的Terminal中,先进入要调用本地代码的类所在的目录,也就是在项目中的具体路径,比如这里是cd app\src\main\java。然后通过javah命令生成该类的头文件,注意包名+类名.这里是javah -jni com.mercury.jnidemo.JNITest,生成头文件com_mercury_jnidemo_JNITest.h

实际项目最终可以不包含此头文件,不熟悉C的语法的开发人员,借助于该头文件可以知道JNI的相关语法:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_mercury_jnidemo_JNITest */

#ifndef _Included_com_mercury_jnidemo_JNITest
#define _Included_com_mercury_jnidemo_JNITest
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_mercury_jnidemo_JNITest
 * Method:    getStrFromJNI
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_mercury_jnidemo_JNITest_getStrFromJNI
  (JNIEnv *, jclass);

#ifdef __cplusplus
}
#endif
#endif

首先引入jni.h,里面包含了很多宏定义及调用本地方法的结构体。重点是方法名的格式。这里的JNIEXPORT和JNICALL都是jni.h中所定义的宏。JNIEnv *表示一个指向JNI环境的指针,可通过它来访问JNI提供的接口方法。jclass也是jni.h中定义好的,类型是jobject,实际上是一个不确定类型的指针,这里用来接收Java中的this。实际编写中一般只要遵循Java_包名_类名_方法名就好了。

4.实现JNI方法

像上面的头文件只是定义了方法,并没有实现,就像一个接口一样。这里就用C写一个简单的无参的JNI方法。
先创建一个jni目录,我直接在src的父目录下创建的,也可以在其他目录创建,因为最终只需要编译好的动态库。在jni目录下创建Android.mk和demo.c文件。

Android.mk是一个makefile配置文件,安卓大量采用makefile进行自动化编译。LOCAL_MODULE定义的名称就是编译好的so库名称,比如这里是jni-demo最终生成的动态库名称就叫libjni-demo.so。 LOCAL_SRC_FILES表示参与编译的源文件名称,这里就是demo.c

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE := jni-demo
LOCAL_SRC_FILES := demo.c

include $(BUILD_SHARED_LIBRARY)

这里的demo.c实现了一个很简单的方法,返回String类型。

#include<jni.h>

jstring Java_com_mercury_jnidemo_JNITest_getStrFromJNI(JNIEnv *env,jobject thiz){
    return (*env)->NewStringUTF(env,"I am Str from jni libs!");
}

这时候NDK编译生成的动态库会有四个CPU平台:arm64-v8a、armeabi-v7a、x86、x86_64。如果创建Application.mk就可以指定要生成的CPU平台,语法也很简单:

APP_ABI := all

这样就会生成各个CPU平台下的动态库。

5.使用ndk-build编程生成.so库

切回到jni目录的父目录下,在Terminal中运行ndk-build指令,就可以在和jni目录同级生成一个libs文件夹,里面存放相对应的平台的.so库。同时生成的还有一个中间临时的obj文件夹,和jni文件夹可以一起删除。
需要注意,使用NDK一定要先在build.gradle下要配置ndk-build的相关路径,这样在编写本地代码时才会有相关的提示功能,并且可以关联到相关的头文件

externalNativeBuild {
        ndkBuild {
            path 'jni/Android.mk'
        }
    }

还有一点,网上很多资料都在build.gradle中加入以下代码:

sourceSets{
        main{
            jniLibs.srcDirs=['libs']
        }
    }

这样就指定了目标.so库的存放位置。但在实际使用中,就算不指定,运行时仍然可以加载正确的.so库文件,并且如果添加该代码后有时会报出以下错误:

 Error:Execution failed for task ':usejava:transformNativeLibsWithMergeJniLibsForDebug'.
	> More than one file was found with OS independent path 'lib/x86/libjni-calljava.so'
	> 
6.加载.so库并调用方法

在类初始化的时候要加载该.so库,一般会写在静态代码块里。名称就是前面的LOCAL_MODULE。

    static {
   
        System.loadLibrary("jni-demo");
    }


需要注意的是如果是有参的JNI方法,那么直接在参数列表里补充在jni.h预先typedef好的数据类型就可以了。

JNI调用Java

不同于JNI调用C,JNI调用Java的过程不是单独存在的。而是编写native方法,Java先通过JNI调用该方法,在方法内部再去回调类中对应的Java方法。步骤有些类似于Java中的反射。这里写定义三个点击事件,三个Native方法,三种Java的方法类型,根据相关的Log判断是否成功。

public class MainActivity extends AppCompatActivity {
   

    public static final String TAG = "MainActivity"<
  • 12
    点赞
  • 96
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值