前言:
前几篇我主要介绍了jni先关的基础知识和常用API,相信看过的童靴对JNI已经有了一定的了解,如果不了解也没关系,下面我给出了链接,可以点进去学习。接下来我将实战一个完整案例,案例很简单,就是一个简单的计算器。
- Android JNI(一)——NDK与JNI基础
- Android JNI(二)——实战JNI入门之Hello World
- Android JNI(三)——JNI数据结构之JNINativeMethod
- Android JNI学习(四)——JNI的常用方法的API
- Android JNI学习(五)——Java与Native之间如何实现相互调用
实战效果:
讲解之前我们先看一下实战效果,因为接下来也是围绕这个实现效果一一讲解。

好了,接下来我们就围绕这个效果图开启实战演练之旅吧。
开启实战:
整个开发过程很简单,大致可以分成三步,分别是如下三步:
1. 编写Android代码
包括了xml布局,以及activity的逻辑处理方法.
2. 编写Java代码用于生成头文件
主要用于生成对应得.h头文件。
3. 实现jni代码(native代码)
native具体的实现方法。
1.编写Android代码
Android的代码包括俩部分,分别是布局和activity,这里不过多叙述,直接上对应得代码。
1.1 xml 对应得ui布局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".jni.CalculationActivity">
<TextView
android:layout_marginTop="5dp"
android:textSize="32sp"
android:textStyle="bold"
android:textColor="@color/colorAccent"
android:layout_margin="10dp"
android:gravity="center"
android:text="c/c++和Java,Kotlin相互传值调用"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:id="@+id/inputOne"
android:hint="请输入第一个数字"
android:inputType="number"
android:layout_weight="1.0"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<EditText
android:inputType="number"
android:id="@+id/inputTwo"
android:hint="请输入第二个数字"
android:layout_weight="1.0"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
<TextView
android:layout_marginTop="5dp"
android:textSize="22sp"
android:textStyle="bold"
android:text="请选择计算类型:"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<LinearLayout
android:layout_marginTop="5dp"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
android:gravity="center"
android:layout_weight="1.0"
android:text="加法"
android:id="@+id/add"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
android:gravity="center"
android:layout_weight="1.0"
android:text="减法"
android:id="@+id/sub"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
android:gravity="center"
android:layout_weight="1.0"
android:text="乘法"
android:id="@+id/mul"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
android:gravity="center"
android:layout_weight="1.0"
android:text="除法"
android:id="@+id/div"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
<TextView
android:layout_marginTop="5dp"
android:textSize="22sp"
android:textStyle="bold"
android:id="@+id/cal_result"
android:text="计算结果:"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
1.2 activity的代码
package com.bnd.multimedialearning.jni
import android.os.Bundle
import android.text.TextUtils
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import com.bnd.multimedialearning.R
import kotlinx.android.synthetic.main.activity_calculation.*
import org.jetbrains.anko.toast
class CalculationActivity : AppCompatActivity(),View.OnClickListener {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_calculation)
add.setOnClickListener(this)
sub.setOnClickListener(this)
mul.setOnClickListener(this)
div.setOnClickListener(this)
}
override fun onClick(v: View?) {
val viewId=v?.id
val strOne: String = inputOne.text.trim().toString()
val strTwo: String = inputTwo.text.trim().toString()
if (TextUtils.isEmpty(strOne)||TextUtils.isEmpty(strTwo)){
toast("请输入要计算的俩个数字!")
return
}
val one = strOne.toInt()
val two = strTwo.toInt()
when(viewId){
R.id.add -> calculationAdd(one,two)
R.id.sub -> calculationSub(one,two)
R.id.mul -> calculationMul(one,two)
R.id.div -> calculationDiv(one,two)
else -> calculationAdd(one,two)
}
}
private fun calculationAdd(one: Int, two: Int) {
val result = JNICalculationTools.addition(one,two)
cal_result.text="计算结果:${result}"
}
private fun calculationSub(one: Int, two: Int) {
val result = JNICalculationTools.subtraction(one,two)
cal_result.text="计算结果:${result}"
}
private fun calculationMul(one: Int, two: Int) {
val result = JNICalculationTools.multiplication(one,two)
cal_result.text="计算结果:${result}"
}
private fun calculationDiv(one: Int, two: Int) {
val result = JNICalculationTools.division(one,two)
cal_result.text="计算结果:${result}"
}
}
2. 编写Java代码用于生成头文件
2. 1编写逻辑java类
我们把运算的逻辑抽象出来,用一个类
来表示。代码如下:
package com.bnd.multimedialearning.jni
object JNICalculationTools {
//加法
external fun addition(a: Int, b: Int): Int
//减法
external fun subtraction(a: Int, b: Int): Int
//乘法
external fun multiplication(a: Int, b: Int): Int
//除法
external fun division(a: Int, b: Int): Int
init {
System.loadLibrary("JNICalculationTools")
}
}
好了,接下来就是要生成头文件了。
2. 2 生成头文件
在上一篇我们讲过如何生成.h头文件,这里不做过多描述,直接通过如下命令一步生成:
javac -encoding utf8 -h . 类名.java
执行命令,生成后缀是.h的文件如下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_bnd_multimedialearning_jni_JNICalculationTools */
#ifndef _Included_com_bnd_multimedialearning_jni_JNICalculationTools
#define _Included_com_bnd_multimedialearning_jni_JNICalculationTools
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_bnd_multimedialearning_jni_JNICalculationTools
* Method: addition
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_com_bnd_multimedialearning_jni_JNICalculationTools_addition
(JNIEnv *, jclass, jint, jint);
/*
* Class: com_bnd_multimedialearning_jni_JNICalculationTools
* Method: subtraction
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_com_bnd_multimedialearning_jni_JNICalculationTools_subtraction
(JNIEnv *, jclass, jint, jint);
/*
* Class: com_bnd_multimedialearning_jni_JNICalculationTools
* Method: multiplication
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_com_bnd_multimedialearning_jni_JNICalculationTools_multiplication
(JNIEnv *, jclass, jint, jint);
/*
* Class: com_bnd_multimedialearning_jni_JNICalculationTools
* Method: division
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_com_bnd_multimedialearning_jni_JNICalculationTools_division
(JNIEnv *, jclass, jint, jint);
#ifdef __cplusplus
}
#endif
#endif
3. 实现jni代码(native代码)
到了这里,准备工作准备的差不多了,回想一下如何编写native代码了。
由于我们不是通过javah
来生成.c文件,所以我们要创建一个jni
的文件夹,然后创建一个JNICalculationTools.c
文件。这时候JNICalculationTools.c
文件里面应该什么都没有,我们看到JNICalculationTools
这类里有4个native方法,所以我们要也要在JNICalculationTools.c
里面声明这4个方法。
3.1 声明与之对应得native方法
jint addition(JNIEnv *env,jclass clazz,jint a,jint b);
jint subtraction(JNIEnv *env,jclass clazz,jint a,jint b);
jint multiplication(JNIEnv *env,jclass clazz,jint a,jint b);
jint division(JNIEnv *env,jclass clazz,jint a,jint b);
然后就是实现Java类与之对应得native方法。
3.2 实现对应Java层的native方法。
jint addition(JNIEnv *env,jclass clazz,jint a,jint b){
return a+b;
}
jint subtraction(JNIEnv *env,jclass clazz,jint a,jint b){
return a-b;
}
jint multiplication(JNIEnv *env,jclass clazz,jint a,jint b){
return a*b;
}
jint division(JNIEnv *env,jclass clazz,jint a,jint b){
return a/b;
}
到了这里改实现的方法均已经实现,这样是否就已经完成了功能,答案是肯定不能的,通过之前的讲解,我们发现,到目前为止,虽然功能实现了,但是我们尚未实现jni的注册,接下来就是改实现注册了,这也在最重要的一个环节。
通过以前的讲解,我们知道jni的注册有俩种方式,一种是静态注册,一种是动态注册,今天,我就以动态注册方式实现。
3.3 动态注册jni
首先我们要引入生成的头文件。
3.3.1 引入头文件
//引入头文件
#include "JNICalculationTools.h"
3.3.2 重写JNI_OnLoad(JavaVM* vm, void* reserved)
函数
依照前面动态注册方法的步骤,我们要重写JNI_OnLoad(JavaVM* vm, void* reserved)
函数。所以我们在JNICalculationTools.c
中重写函数这个函数,如下:
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved){
return JNI_VERSION_1_6;
}
为了方便打印日志,来帮助我们识别是否进入这个方法,所以我们要配置一个log,这时候,我们创建一个Android.mk
文件,然后进行如下的编辑:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
#解决 error: undefined reference to '__android_log_print'
LOCAL_LDLIBS := -lm -llog
LOCAL_MODULE := JNICalculationTools
LOCAL_SRC_FILES := JNICalculationTools.c
include $(BUILD_SHARED_LIBRARY)
然后在引入然后在JNICalculationTools.c
添加#include <android/log.h>
代码,引入日志:
#include <jni.h>
#include <android/log.h>
#include <stdio.h>
//引入日志功能
#define LOG_TAG "NATIVE_LOG"
#define LOGD(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
注意:
我们在编译的时候可能经常会遇到error: undefined reference to '__android_log_print'这个异常,你可能很纳闷,自己明明引入了
<android/log.h>文件,但是还报错,难道是用法错了,其实不是,这需要我们在Android.mk
文件里添加这样一句话:LOCAL_LDLIBS := -lm -llog;详细配置可以看上面的Android.mk配置文件,有特别注释。
3.3.3 开始编写注册代码
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved){
LOGD("enter jni_onload");
JNIEnv* env = NULL;
jint result = -1;
if((*vm)->GetEnv(vm,(void**)&env,JNI_VERSION_1_6)!= JNI_OK){
return result;
}
const JNINativeMethod method[]={
{"addition","(II)I",(void*)addition},
{"subtraction","(II)I",(void*)subtraction},
{"multiplication","(II)I",(void*)multiplication},
{"division","(II)I",(void*)division}
};
jclass jClassName=(*env)->FindClass(env,"com/bnd/multimedialearning/jni/JNICalculationTools");
jint ret = (*env)->RegisterNatives(env,jClassName,method, 4);
if (ret != JNI_OK) {
LOGD("jni_register is Error!");
return -1;
}
return JNI_VERSION_1_6;
}
补充一点,有的人到了这里可能会失败,失败的原因可能有多种,最后失败,大多数都是签名的问题。下面我们通过Javap命令查看一下签名。
3.3.4 查看签名是否正确
生成的JNICalculationTools.class文件如下:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package com.bnd.multimedialearning.jni;
public class JNICalculationTools {
public JNICalculationTools() {
}
public static native int addition(int var0, int var1);
public static native int subtraction(int var0, int var1);
public static native int multiplication(int var0, int var1);
public static native int division(int var0, int var1);
static {
System.loadLibrary("JNICalculationTools");
}
}
得到了class文件下面我们就可以通过javap命令查看签名了:
javap -s -p JNICalculationTools.class
签名信息如下:
Compiled from "JNICalculationTools.java"
public class com.bnd.multimedialearning.jni.JNICalculationTools {
public com.bnd.multimedialearning.jni.JNICalculationTools();
descriptor: ()V
public static native int addition(int, int);
descriptor: (II)I
public static native int subtraction(int, int);
descriptor: (II)I
public static native int multiplication(int, int);
descriptor: (II)I
public static native int division(int, int);
descriptor: (II)I
static {};
descriptor: ()V
}
然后我们一一比对方法名称,参数个数,参数类型,发现没有任何问题,到了这里我们基本就完成了jni整个流程的注册。就下来就是在.gradle配置文件里核对配置了。
3.3.5 .gradle配置文件核对配置
ndk{
moduleName "JNICalculationTools"
abiFilters "armeabi-v7a"
ldLibs "log"
}
注意配置的gradle文件千万不要错了,是在app目录下,看一下此时完整的配置目录结构:
最后附上完整的JNICalculationTools.c代码:
//引入头文件
#include "JNICalculationTools.h"
#include <jni.h>
#include <android/log.h>
#include <stdio.h>
//引入日志功能
#define LOG_TAG "NATIVE_LOG"
#define LOGD(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
jint addition(JNIEnv *env,jclass clazz,jint a,jint b);
jint subtraction(JNIEnv *env,jclass clazz,jint a,jint b);
jint multiplication(JNIEnv *env,jclass clazz,jint a,jint b);
jint division(JNIEnv *env,jclass clazz,jint a,jint b);
jint addition(JNIEnv *env,jclass clazz,jint a,jint b){
return a+b;
}
jint subtraction(JNIEnv *env,jclass clazz,jint a,jint b){
return a-b;
}
jint multiplication(JNIEnv *env,jclass clazz,jint a,jint b){
return a*b;
}
jint division(JNIEnv *env,jclass clazz,jint a,jint b){
return a/b;
}
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved){
LOGD("enter jni_onload");
JNIEnv* env = NULL;
jint result = -1;
if((*vm)->GetEnv(vm,(void**)&env,JNI_VERSION_1_6)!= JNI_OK){
return result;
}
const JNINativeMethod method[]={
{"addition","(II)I",(void*)addition},
{"subtraction","(II)I",(void*)subtraction},
{"multiplication","(II)I",(void*)multiplication},
{"division","(II)I",(void*)division}
};
jclass jClassName=(*env)->FindClass(env,"com/bnd/multimedialearning/jni/JNICalculationTools");
jint ret = (*env)->RegisterNatives(env,jClassName,method, 4);
if (ret != JNI_OK) {
LOGD("jni_register is Error!");
return -1;
}
return JNI_VERSION_1_6;
}
配置无误后就可以实现实战演练的效果图了,运行一下看一下最终的效果图如下:
好了,至此,关于jni的基础好实战知识已经讲解完毕了,希望对刚入门,或者即将入门的你有所帮助,同时,如果有写的不好的地方,希望路过的大佬指出,希望大家可以共同进步。