Android NDK JNI开发(工具:CMake)

Posted 长城下一颗小石头

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android NDK JNI开发(工具:CMake)相关的知识,希望对你有一定的参考价值。

为什么要使用NDK开发:

实际的开发过程中会有业务需要使用Java与C/C++两者之间进行数据交互。
很多的业务里都需要C/C+ +层拿到数据去调用Java层的方法,这两层之间的相互调用显得如此的重要,两层之间的相互调用使得程序更具有高效性、安全性。下面主要讲解一下C/C+ +与Java的相互调用。

如何建立一个JNI工程:

新建工程示例:


next,

next

这样一个新的JNI工程就创建完成

已有工程上 创建JNI

步骤:
第一步:新建一个native调用java类。
第二步:得到native调用java类的.class文件和 .h头文件。
第三步:编写CMakeLists.txt文件。
第四步:定义实现头文件的函数。
第五步:java层调用通过native的java方法 调用 JNI当中的函数。实现Java与C/C++交互。

首先新建一个空白工程:空白窗体的布局
第一步:然后在Activity目录下新建一个JavaNativeUtils类:

JavaNativeUtils.java具体代码:

public class JavaNativeUtils {
	public static final String LOG_TAG = "==JavaNativeUtils.LOG==";
	public static final String RETURN_TAG = "==JavaNativeUtils.RETURN==";

	/***********************无参数无返回****************************/
	public void noParams(){
		Log.e(LOG_TAG,"JavaNativeUtils describe() Called!");
	}
	public static void staticNoParams(){
		Log.e(LOG_TAG,"JavaNativeUtils.staticNoParams() Called!");
	}
	/***********************有参数有返回****************************/
	public String hasParams(String param1){
		Log.e(LOG_TAG,"JavaNativeUtils javaMethodHasParams(String msg) is Called!");
		return LOG_TAG+ " javaMethodHasParams Method return!";
	}
	public static String staticHasParams(String param1s, int params2){
		Log.e(LOG_TAG,"JavaNativeUtils javaStaticHasParams(String msg) is Called!");
		return LOG_TAG+ "javaStaticHasParams Method return!!!";
	}

	/****************************native 方法区********************************/
	//java对象 调用Jni无参无返回
	public native void JavaCallJniVoid();
	//java对象 调用Jni有参无返回
	public native void JavaCallJniVoidHasParams(String p1, int p2);

	//Java类调用 Jni无参有返回
	public static native String JavaStaticCallJniVoid();
	//Java类调用 Jni有参有返回
	public static native String JavaStaticCallJniVoidHasParams(String p1 ,int[] p2);
}

第二步:写好native方法之后,就需要我们生成对应的.class和.h头文件
生成.class文件:
如果我们直接在当前目录下运行javac JavaNativeUtils.java 肯定会报错:

提示的是编码错误:修改方案如下
javac -encoding UTF-8 JavaNativeUtils.java
这样就可以成功生成.class文件
生成.h文件
javac -h cpp\\jni java\\com\\wcc\\jnitest\\JavaNativeUtils.java -encoding UTF-8

此时有红色报错,无需吃惊。此文件我们是不需要用来编译的。
在此文件同目录下随便新建一个XXX.cpp文件 引用这个头文件。

第三步:编写CMakeLists.txt代码如下:

cmake_minimum_required(VERSION 3.4.1)       #支持最小的版本号3.4.1  /3.10.2
add_library(
        # Sets the name of the library.
        JavaNUtils  #生成的library名称

        # Sets the library as a shared library.
        SHARED         #

        # Provides a relative path to your source file(s).
        demo.cpp     #通过目标cpp文件,或者是文件列表,下放有具体列表方法
        #src/main/cpp/nativec.cpp
)

find_library(  #查找lib
        # Sets the name of the path variable.
        log-lib
        # Specifies the name of the NDK library that
        # you want CMake to locate.
        log)

#指定用到的系统库或者NDK库或者第三方库的搜索路径,可选。
#LINK_DIRECTORIES(/usr/local/lib)
target_link_libraries(  #连接${}里面的 library到 上方的 library
        # Specifies the target library.
        JavaNUtils
        # Links the target library to the log library
        # included in the NDK.
        ${log-lib})

第四步
XXX.cpp这里命名为demo.cpp。代码如下:

#include "com_dywcc_jnitest_JavaNativeUtils.h"
#include <jni.h>
#include "stdio.h"
using namespace std;

#include "android/log.h"
#define LOG_TAG "demo.cpp"
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG ,__VA_ARGS__) // //调用Android打印

extern "C"
JNIEXPORT void
JNICALL Java_com_dywcc_testjni_JavaNativeUtils_JavaCallJniVoid(JNIEnv* env,jobject thiz) {
    // TODO: 调用JAVA层 无参数无返回值noParams()方法
    //1.通过java对象获取所在的类名
    jclass  j_clz = env->GetObjectClass( thiz);
    //2.通过方法名和 类名,以及标记号 获得方法的所在的内存地址
    jmethodID  j_methodId = env->GetMethodID(j_clz,"noParams","()V");
    //3.通过AllocObject(j_clz)函数得到对象. C++没有GC ,所以之后需要手动释放资源
    jobject j_obj = env->AllocObject(j_clz);
    LOGE("===========JavaCallJniVoid======call noParams ");
    //4.通过对象和方法名 调用java层方法
    env->CallVoidMethod(j_obj,j_methodId);
}
extern "C"
JNIEXPORT void
JNICALL Java_com_dywcc_testjni_JavaNativeUtils_JavaCallJniVoidHasParams(JNIEnv* env,jobject thiz, jstring p1,jint p2) {
// TODO: 调用Java 无参无返回静态方法staticNoParams()
    //1.通过java对象获取其类名
    jclass j_clz = env->GetObjectClass( thiz);
    //2.通过方法名和 类名,以及标记号 获得方法的所在的内存地址
    jmethodID  j_methodId = env->GetStaticMethodID(j_clz,"staticNoParams", "()V");

    char * pp = (char *)env->GetStringUTFChars(p1,0);

    LOGE("===========JavaCallJniVoidHasParams====== call staticNoParams===  %s  %d",pp, p2);
    //3.通过对象和方法名 调用java层方法
    env->CallStaticVoidMethod(j_clz,j_methodId);
}
extern "C"
JNIEXPORT jstring
JNICALL Java_com_dywcc_testjni_JavaNativeUtils_JavaStaticCallJniVoid(JNIEnv * env, jclass clazz) {
// TODO: JNI层调用 java层对象的有参hasParams方法
    //1.通过方法名和 类名,以及标记号 获得方法的所在的内存地址
    jmethodID  j_methodId = env->GetMethodID(clazz,"hasParams","(Ljava/lang/String;)Ljava/lang/String;");
    //2.通过类名,分配一个java对象
    jobject j_obj = env->AllocObject(clazz);
    //定义一个传入的参数变量
    char * jni_params = "===jni_prams ===";
    jstring  params = env->NewStringUTF(jni_params);

    LOGE("===========JavaStaticCallJniVoid====== call  hasParams");
    //3.通过对象和方法名及其参数 调用java层方法
    jstring result = (jstring)env->CallObjectMethod(j_obj,j_methodId,params);
    return result;
}

extern "C"
JNIEXPORT jstring
JNICALL Java_com_dywcc_testjni_JavaNativeUtils_JavaStaticCallJniVoidHasParams(JNIEnv * env, jclass clazz,jstring p1, jintArray p2) {
// TODO: JNI层调用 java层   有参静态 staticHasParams 方法
    //1.通过方法名和 类名,以及标记号 获得方法的所在的内存地址
    jmethodID  j_methodId = env->GetStaticMethodID(clazz,"staticHasParams","(Ljava/lang/String;I)Ljava/lang/String;");
    //定义两个个传入的参数变量
    jint params =100;
    LOGE("===========JavaStaticCallJniVoidHasParams======call staticHasParams");
    //2.通过对象和方法名 调用java层方法
    jstring result = (jstring)env->CallStaticObjectMethod(clazz,j_methodId,p1,params);
    return result;
}

第五步
Java层调用代码如下

public class MainActivity extends AppCompatActivity {

	private ActivityMainBinding binding;
	private JavaNativeUtils utils;

	@Override
	protected void onCreate (Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		binding = DataBindingUtil.setContentView(this,R.layout.activity_main);
		utils = new JavaNativeUtils();

		binding.JavaCallJniVoid.setOnClickListener(this::testClick);
		binding.JavaCallJniVoidHasParams.setOnClickListener(this::testClick);
		binding.JavaStaticCallJniVoid.setOnClickListener(this::testClick);
		binding.JavaStaticCallJniVoidHasParams.setOnClickListener(this::testClick);
	}
	public void testClick(View view){
		switch (view.getId()){
			case R.id.JavaCallJniVoid:
				utils.JavaCallJniVoid();
				break;
			case R.id.JavaCallJniVoidHasParams:
				utils.JavaCallJniVoidHasParams("lixiaolong",288888);
				break;
			case R.id.JavaStaticCallJniVoid:
				String result= JavaNativeUtils.JavaStaticCallJniVoid();
				binding.tvStaticNativeInfo.setText(result);
				break;
			case R.id.JavaStaticCallJniVoidHasParams:
				String re= JavaNativeUtils.JavaStaticCallJniVoidHasParams("zhangsan ",new int[]{2021});
				binding.tvStaticNativeInfo.setText(re);
				break;
		}
	}
}

activity_main.xml文件

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">
    <data>
    </data>
    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context="com.dywcc.testjni.MainActivity">

        <TextView
            android:id="@+id/tv_native_info"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="非静态native:Jni调用无参函数"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <Button
            android:id="@+id/JavaCallJniVoid"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="CallJniVoid"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/tv_native_info" />
        <Button
            android:id="@+id/JavaCallJniVoidHasParams"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="CallJniVoidHasParams"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/JavaCallJniVoid" />
        <TextView
            android:id="@+id/tv_static_native_info"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="静态native:Jni调用有参函数"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/JavaCallJniVoidHasParams" />
        <Button
            android:id="@+id/JavaStaticCallJniVoid"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="CallJniVoid"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/tv_static_native_info" />
        <Button
            android:id="@+id/JavaStaticCallJniVoidHasParams"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="CallJniVoidHasParams"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/JavaStaticCallJniVoid" />


    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

以上是关于Android NDK JNI开发(工具:CMake)的主要内容,如果未能解决你的问题,请参考以下文章

JNI & NDK

android JNI开发

超全Android JNI&NDK编程总结

JNI和NDK编程

Android JNI编程——使用AndroidStudio编写第一个JNI程序

转 Android的NDK开发————Android JNI简介与调用流程