Android JNI学习(六)——Java与Native实战演习

前言:

前几篇我主要介绍了jni先关的基础知识和常用API,相信看过的童靴对JNI已经有了一定的了解,如果不了解也没关系,下面我给出了链接,可以点进去学习。接下来我将实战一个完整案例,案例很简单,就是一个简单的计算器。

  1. Android JNI(一)——NDK与JNI基础
  2. Android JNI(二)——实战JNI入门之Hello World
  3. Android JNI(三)——JNI数据结构之JNINativeMethod
  4. Android JNI学习(四)——JNI的常用方法的API 
  5. 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的基础好实战知识已经讲解完毕了,希望对刚入门,或者即将入门的你有所帮助,同时,如果有写的不好的地方,希望路过的大佬指出,希望大家可以共同进步。

猜你喜欢

转载自blog.csdn.net/ljx1400052550/article/details/115092140