本系列文章如下:

我们这里做一个简单的计算器demo,其中运算的逻辑由Native实现,而且我们采用动态注册的方式来实现

样式大概如下:

image.png

里面有两个输入框,下面有4个按钮,代表加减乘除,最下面有个TextView,表示结果。

我们把运算的逻辑抽象出来,用一个JNITools来表示。代码如下:

public class JNITools {

    static {
System.loadLibrary("jnidemo3");
} //加法
public static native int add(int a,int b); //减法
public static native int sub(int a,int b); //乘法
public static native int mul(int a,int b); //除法
public static native int div(int a,int b); }

然后,我们先用来看下对应的布局文件如下:

<?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="com.gebilaolitou.jni.MainActivity"> <LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"> <EditText
android:id="@+id/inputa"
android:hint="请输入a"
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/inputb"
android:hint="请输入b"
android:layout_weight="1.0"
android:layout_width="match_parent"
android:layout_height="wrap_content" /> </LinearLayout> <TextView
android:text="请选择符号"
android:layout_width="match_parent"
android:layout_height="wrap_content" /> <LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"> <Button
android:gravity="center"
android:layout_weight="1.0"
android:text="@string/add"
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="@string/sub"
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="@string/mul"
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="@string/div"
android:id="@+id/div"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout> <TextView
android:id="@+id/result"
android:text="计算结果"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>

这时候,我们来看下对应的MainActivity

public class MainActivity extends AppCompatActivity implements View.OnClickListener{

    private Button btnAdd,btnSub,btnMul,btnDiv;
private EditText inputA,inputB;
private TextView tvResult; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
setupView();
addListener();
} private void addListener() {
btnAdd.setOnClickListener(this);
btnDiv.setOnClickListener(this);
btnMul.setOnClickListener(this);
btnSub.setOnClickListener(this);
} private void setupView() {
btnAdd=this.findViewById(R.id.add);
btnDiv=this.findViewById(R.id.div);
btnMul=this.findViewById(R.id.mul);
btnSub=this.findViewById(R.id.sub); inputA=this.findViewById(R.id.inputa);
inputB=this.findViewById(R.id.inputb); tvResult=this.findViewById(R.id.result);
} @Override
public void onClick(View v) {
double result=0;
String strA=inputA.getText().toString();
String strB=inputB.getText().toString();
int a=Integer.parseInt(strA);
int b=Integer.parseInt(strB);
switch (v.getId()){
case R.id.add:
result=JNITools.add(a,b);
break;
case R.id.div:
result=JNITools.div(a,b);
break;
case R.id.mul:
result=JNITools.mul(a,b);
break;
case R.id.sub:
result=JNITools.sub(a,b);
break;
}
tvResult.setText(""+result);
}
}

由于我们不是通过javah来生成.c文件,所以我们要创建一个jni的文件夹,然后创建一个jnitools.c文件。这时候jnitools.c文件里面应该什么都没有,我们看到JNITools这个文件有4个native方法,所以我们要也要在jnitools.c里面声明这4个方法。如下:

jint addNumber(JNIEnv *env,jclass clazz,jint a,jint b);

jint subNumber(JNIEnv *env,jclass clazz,jint a,jint b);

jint mulNumber(JNIEnv *env,jclass clazz,jint a,jint b);

jint divNumber(JNIEnv *env,jclass clazz,jint a,jint b);

依次对应Java层的native方法,然后我们写出这些方法的具体实现,如下;

jint addNumber(JNIEnv *env,jclass clazz,jint a,jint b){
return a+b;
} jint subNumber(JNIEnv *env,jclass clazz,jint a,jint b){
return a-b;
} jint mulNumber(JNIEnv *env,jclass clazz,jint a,jint b){
return a*b;
} jint divNumber(JNIEnv *env,jclass clazz,jint a,jint b){
return a/b;
}

最后,然后加入引用,如下:

#include <jni.h>
#include <stdio.h>
#include <stdlib.h>

依照前面动态注册方法的步骤,我们要重写JNI_OnLoad(JavaVM* vm, void* reserved)函数。所以我们在jnitools.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)

LOCAL_MODULE    := jnidemo3

LOCAL_SRC_FILES := jnitools.c

LOCAL_LDLIBS :=  -L$(SYSROOT)/usr/lib -llog

include $(BUILD_SHARED_LIBRARY)

然后在jnitools.c添加#include <android/log.h>代码

这时候,开始注册代码,我们开始编写注册代码

JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved){

    //打印日志,说明已经进来了
__android_log_print(ANDROID_LOG_DEBUG,"JNITag","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[]={
{"add","(II)I",(void*)addNumber},
{"sub","(II)I",(void*)subNumber},
{"mul","(II)I",(void*)mulNumber},
{"div","(II)I",(void*)divNumber}
}; //找到对应的JNITools类
jclass jClassName=(*env)->FindClass(env,"com/gebilaolitou/jni/JNITools"); //开始注册
jint ret = (*env)->RegisterNatives(env,jClassName,method, 4); //如果注册失败,打印日志
if (ret != JNI_OK) {
__android_log_print(ANDROID_LOG_DEBUG, "JNITag", "jni_register Error");
return -1;
} return JNI_VERSION_1_6;
}

这里补充一下,很多人,最后失败,都是签名的问题,可以建议使用java -p 命令行来对比签名,我这是这样操作的。首先打开JNITools.class,如下图:

image.png

然后打开Android Studio里面的Terminal,然后拖拽JNITools.classTerminal。这样Terminal就自动打开了,然后我们在Terminal里面输入pwd,就已经在这个目录里面了。然后我们在Terminal输入如下代码

javap -s JNITools.class

Terminal就会输出这个类的所有签名,如下:

Compiled from "JNITools.java"
public class com.gebilaolitou.jni.JNITools {
public com.gebilaolitou.jni.JNITools();
descriptor: ()V public static native int add(int, int);
descriptor: (II)I public static native int sub(int, int);
descriptor: (II)I public static native int mul(int, int);
descriptor: (II)I public static native int div(int, int);
descriptor: (II)I static {};
descriptor: ()V
}

我们在这里核对下我们注册方法的签名。
最后,我们在对应的build.gradle里面defaultConfig里面添加如下代码:

        ndk{
moduleName "jnidemo3"
abiFilters 'x86','armeabi-v7a','arm64-v8a'
ldLibs "log"
}

这时候我们目录结构如下:

image.png

jnitools.c的内容如下:

#include <jni.h>
#include <android/log.h>
#include <stdio.h>
#include <stdlib.h> jint addNumber(JNIEnv *env,jclass clazz,jint a,jint b); jint subNumber(JNIEnv *env,jclass clazz,jint a,jint b); jint mulNumber(JNIEnv *env,jclass clazz,jint a,jint b); jint divNumber(JNIEnv *env,jclass clazz,jint a,jint b); JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved){ __android_log_print(ANDROID_LOG_DEBUG,"JNITag","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[]={
{"add","(II)I",(void*)addNumber},
{"sub","(II)I",(void*)subNumber},
{"mul","(II)I",(void*)mulNumber},
{"div","(II)I",(void*)divNumber}
};
jclass jClassName=(*env)->FindClass(env,"com/gebilaolitou/jni/JNITools"); jint ret = (*env)->RegisterNatives(env,jClassName,method, 4); if (ret != JNI_OK) {
__android_log_print(ANDROID_LOG_DEBUG, "JNITag", "jni_register Error");
return -1;
} return JNI_VERSION_1_6;
} jint addNumber(JNIEnv *env,jclass clazz,jint a,jint b){
return a+b;
} jint subNumber(JNIEnv *env,jclass clazz,jint a,jint b){
return a-b;
} jint mulNumber(JNIEnv *env,jclass clazz,jint a,jint b){
return a*b;
} jint divNumber(JNIEnv *env,jclass clazz,jint a,jint b){
return a/b;
}

至此,整个代码已经全部完成。我们可以运行下试试。其实挺简单的。

github地址

作者:隔壁老李头
链接:https://www.jianshu.com/p/0f34c097028a
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

随机推荐

  1. 通过安装一个描述文件在控制台获得设备的udid

    在我的这篇博客里面说明了本地获得设备udid的方法,但是只能在模拟器中获得http://www.cnblogs.com/liyy2015/p/6090204.html 当然可以在设备上集成苹果的MDM ...

  2. 操作系统课程设计--Linux平台哲学家问题

    哲学家问题是操作系统中资源分配的经典问题 linux平台下的系统api不同于Windows下的实现 要求:一个正确的哲学家程序(不会发生死锁) 一个错误的哲学家程序(会发生死锁) 系统环境:Eleme ...

  3. 一个URL链接到一个页面发生了什么?

    最开始觉得这是一个很难理解的东西,后来看了很多人写的发现并没有那么难理解,本人只是一个学渣有什么说的不对的地方希望大家能够指出来! 一个URL从我们输入的那一刻起,到返回页面到底发生了什么呢? URL ...

  4. Java字符流读写数据的两种方式

    第一种方式:逐个字符进行读写操作(代码注释以及详细内容空闲补充) package IODemo; import java.io.FileReader; import java.io.FileWrite ...

  5. Oracle 搜集统计信息

    常用的统计信息收集脚本: 非分区表: BEGIN   DBMS_STATS.GATHER_TABLE_STATS(ownname          => 'SCOTT',             ...

  6. hdu 1998 奇数阶魔方(找规律+模拟)

    应该不算太水吧. 17  24   1   8  15   23   5   7  14  16    4   6  13  20  22   10  12  19  21   3   11  18 ...

  7. JavaScript 中 闭包 的详解

    闭包是什么 在 JavaScript 中,闭包是一个让人很难弄懂的概念.ECMAScript 中给闭包的定义是:闭包,指的是词法表示包括不被计算的变量的函数,也就是说,函数可以使用函数之外定义的变量. ...

  8. LPC 语言基础

    LPC是一种基于C语言开发的编程语言 主要用于写MUD(多使用着迷宫)游戏 LPC是一种面向对象的语言,它有object的概念,但是没有class LPC有四中函数类型1> apply 只能被游 ...

  9. opencv 常用头文件介绍

    1.OpenCV包含的模块 cv – 核心函数库 cvaux – 辅助函数库 cxcore – 数据结构与线性代数库 highgui – GUI函数库 ml – 机器学习函数库 2.常用头文件: #i ...

  10. 浅析C语言的变量

    参考资料 寄存器变量 用register声明的变量是寄存器变量,是存放在CPU的寄存器里的.而我们平时声明的变量是存放在内存中的.虽说内存的速度已经很快了,不过跟寄存器比起来还是差得远. 寄存器变量和 ...