Android JNI学习(六)——Java与Native实战演习
前言:
前几篇我主要介绍了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"><TextViewandroid: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" /><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"><EditTextandroid:id="@+id/inputOne"android:hint="请输入第一个数字"android:inputType="number"android:layout_weight="1.0"android:layout_width="match_parent"android:layout_height="wrap_content" /><EditTextandroid:inputType="number"android:id="@+id/inputTwo"android:hint="请输入第二个数字"android:layout_weight="1.0"android:layout_width="match_parent"android:layout_height="wrap_content" /></LinearLayout><TextViewandroid:layout_marginTop="5dp"android:textSize="22sp"android:textStyle="bold"android:text="请选择计算类型:"android:layout_width="match_parent"android:layout_height="wrap_content" /><LinearLayoutandroid:layout_marginTop="5dp"android:orientation="horizontal"android:layout_width="match_parent"android:layout_height="wrap_content"><Buttonandroid:gravity="center"android:layout_weight="1.0"android:text="加法"android:id="@+id/add"android:layout_width="match_parent"android:layout_height="wrap_content" /><Buttonandroid:gravity="center"android:layout_weight="1.0"android:text="减法"android:id="@+id/sub"android:layout_width="match_parent"android:layout_height="wrap_content" /><Buttonandroid:gravity="center"android:layout_weight="1.0"android:text="乘法"android:id="@+id/mul"android:layout_width="match_parent"android:layout_height="wrap_content" /><Buttonandroid:gravity="center"android:layout_weight="1.0"android:text="除法"android:id="@+id/div"android:layout_width="match_parent"android:layout_height="wrap_content" /></LinearLayout><TextViewandroid: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.jniimport 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.toastclass 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?.idval 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.jniobject 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): Intinit {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 -llogLOCAL_MODULE := JNICalculationToolsLOCAL_SRC_FILES := JNICalculationTools.cinclude $(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: ()Vpublic static native int addition(int, int);descriptor: (II)Ipublic static native int subtraction(int, int);descriptor: (II)Ipublic static native int multiplication(int, int);descriptor: (II)Ipublic static native int division(int, int);descriptor: (II)Istatic {};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的基础好实战知识已经讲解完毕了,希望对刚入门,或者即将入门的你有所帮助,同时,如果有写的不好的地方,希望路过的大佬指出,希望大家可以共同进步。
Android JNI学习(六)——Java与Native实战演习相关推荐
- Android JNI学习(五)——Java与Native之间如何实现相互调用
本章将讲述Java与Native之间如何实现相互调用.我将围绕围绕如下三点来讲解. #mermaid-svg-qeVnGlVrLWrB5ryX .label{font-family:'trebuche ...
- Android JNI学习(四)——JNI的常用方法的API
前三篇主要讲解了jni基础相关的理论知识,今天主要讲解一下JNI的常用方法的API,掌握了基本的理论知识和常用的API接下来才能更好的实战. jni的常用API大纲 再看API前,我建议大家主要结合官 ...
- android jni中的java调c的两种方法
Andoird 中使用了一种不同传统Java JNI的方式来定义其native的函数. 也就是java虚拟机通过一种机制可以找到对应的C函数 这里就涉及到静态注册和动态注册jni函数的方法 一.这里 ...
- android jni c调用java,Android学习JNI,使用C调用JAVA语言
本节学习使用C语言调用JAVA语言.在生活中比如我们某些底层的硬件必须使用C语言去编写,当C语言返回的某些数值显示在界面上时,就比如使用JNI.这样可以将C返回的值显示在界面上. 本节模拟传感器返回的 ...
- Android JNI 学习(十):String Operations Api Other Apis
一.String Operations(字符串操作) 1. NewString jstring NewString(JNIEnv *env, const jchar *unicodeChars, js ...
- Android JNI 学习(十一):Invocation Api
1. 简介 Invocation API允许软件提供商在原生程序中内嵌Java虚拟机.因此可以不需要链接任何Java虚拟机代码来提供Java-enabled的应用程序. 以下代码演示如何使用: #in ...
- Android多媒体学习六:访问网络上的Audio对应的M3U文件,实现网络音频流的播放
Android中提供了对网络上流媒体的支持,我们可以使用MediaPlayer类来播放一个网络上的音频文件. 但是网络上的站点并不建议我们直接访问流,我们需要获取他提供的M3U文件,根据M3U文件来实 ...
- Android Framework学习(六)之RefBase,SP,WP
Android中通过引用计数来实现智能指针,并且实现有强指针与弱指针.由对象本身来提供引用计数器,但是对象不会去维护引用计数器的值,而是由智能指针来管理. 要达到所有对象都可用引用计数器实现智能指针管 ...
- android java服务,Android进阶学习必会:Java Binder中的系统服务
前言 这个知识点是Android进阶学习必须掌握的知识点之一,也是高阶Android架构师经常问到的点.在这里分想给大家,希望对大家的工作和学习有所帮助.喜欢本文的记得点赞关注哦~ 在前面的Andro ...
最新文章
- 博客园今天早上是不是出现什么问题了?
- 一文总结词向量的计算、评估与优化
- 网络工程学习资料2---IEEE 802 标准集合
- kong组件_Kong插件开发工具包
- 11月碎碎念-谈职场礼貌
- openlayers加载svg,如何在OpenLayers-3中将SVG图像用作地图标记?
- Paint X for Mac的用法
- python成长之路--python的安装与配置 pycharm的安装与激活
- 电子工程师私藏的一个网站
- 12306列车时刻表查询api功能实现
- UVA1389 Hard Life
- hdu5755 Gambler Bo(高斯消元)
- 西门子 Prodave通讯
- 基于内容可变长度分块Content Defined Chunking
- 磁盘管理以及文件系统管理
- git 远端更新合并到本地
- oracle文件快速入库,文件入库ORACLE自动化脚本
- 机器人彩铅画_高达机器人铅笔画图片
- GS1条形码为什么那么贵?有什么便宜的办法吗?
- ATH9K Driver Learning Part II: Important Transmission Functions
热门文章
- SQL 2016 性能调优培训来了!!! 还免费!!!
- 灌醉茅台董事长拿到便宜酒?潘长江和茅台双双回应...
- 哔哩哔哩公布2021年度弹幕:“破防了”
- 雷军接连退出多家小米关联公司董事职务
- 华为鸿蒙OS 2.0系列Beta 2发布:逼近公测版
- 荣耀V40将采用300Hz 触控采样率,1月18日正式发布!
- 开着开着,Model S天窗飞了!特斯拉回应...
- 蚂蚁集团暂缓两地上市,重新上市或推迟半年,阿里股价相继大跌...
- 李开复“口误”惹事,人脸隐私数据合作?蚂蚁、旷视大喊冤枉!
- 马斯克:2024年送人上火星 2050年建城