本系列文章如下:

我们这里做一个简单的计算器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
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

Android JNI学习(五)——Demo演示的更多相关文章

  1. Android JNI 学习(五):References Api

    1. NewGlobalRef(创建全局引用) jobjectNewGlobalRef(JNIEnv *env, jobject obj); 创建 obj 参数所引用对象的新全局引用.obj 参数既可 ...

  2. Android JNI学习(四)——JNI的常用方法的中文API

    本系列文章如下: Android JNI(一)——NDK与JNI基础 Android JNI学习(二)——实战JNI之“hello world” Android JNI学习(三)——Java与Nati ...

  3. Android JNI学习(三)——Java与Native相互调用

    本系列文章如下: Android JNI(一)——NDK与JNI基础 Android JNI学习(二)——实战JNI之“hello world” Android JNI学习(三)——Java与Nati ...

  4. Android JNI学习(二)——实战JNI之“hello world”

    本系列文章如下: Android JNI(一)——NDK与JNI基础 Android JNI学习(二)——实战JNI之“hello world” Android JNI学习(三)——Java与Nati ...

  5. Android JNI 学习(一):JNI 简介

    JNI 即 Java Native Interface 是 native 编程接口,它允许在Java虚拟机(VM)内运行Java代码与其他编程语言(主要是C和C++)编写的应用程序和库进行交互操作. ...

  6. Android JNI 学习(十一):Invocation Api

    1. 简介 Invocation API允许软件提供商在原生程序中内嵌Java虚拟机.因此可以不需要链接任何Java虚拟机代码来提供Java-enabled的应用程序. 以下代码演示如何使用: #in ...

  7. Android JNI 学习(六):Object Operations Api

    一.AllocObject jobjectAllocObject(JNIEnv *env, jclass clazz); 分配新 Java 对象而不调用该对象的任何构造函数.返回该对象的引用. cla ...

  8. Android JNI 学习(二):JNI 设计机制

    本章我们重点说明以下JNI设计的问题,本章中提到的大多数设计问题都与native方法有关.至于调用相关的API的设计,我们会在后面进行介绍. 一.JNI接口函数和指针 native 代码通过调用JNI ...

  9. android JNI学习之一

    执行System.loadLibrary()函数时,VM会反向调用*.so里的JNI_OnLoad()函数.用途有二:1. VM询问此*.so使用的JNI版本编号.2. VM要求*.so做一些初期设定 ...

随机推荐

  1. oracle dump数据库

    最近正在看老白的<DBA的思想天空>,了解数据块结构,想通过dump data block验证oracle对于行尾的NULL,是不占用存储空间的. 我们先来看一下怎样dump数据块: 1. ...

  2. caffe pytho接口

    一:搭建Caffe 1.下载happynear的Caffe源码https://www.github.com/happynear/caffe-windows,第三方库3rdparty文件http://p ...

  3. js鼠标事件、键盘事件实例代码

    讲述了:鼠标的哪个按键被点击.当前鼠标的光标坐标是多少.被按下键的unicode码是多少.当前鼠标的光标相对于屏幕的坐标是多少.当前鼠标的光标坐标是多少.shift键是否按下.当前被点击的是哪一个元素 ...

  4. 编译redis

    apt-get install gcc tcl make MALLOC=libc

  5. depthstencil buffer 不支持 msaa

    phyreengine dx11 MRT不支持 depth rendertarget 的msaa 他里面竟然只写着,// not supported yet !!!! 导致hdao 时开msaa的话, ...

  6. 【DFS+堆的二叉树结构】15轻院校赛-J-堆

    [题目链接:J-堆] 1734: 堆 Time Limit: 1 Sec  Memory Limit: 128 MBSubmit: 239  Solved: 113 SubmitStatusWeb B ...

  7. Spring中@Autowired与@Resource的区别

    1.@Autowired与@Resource都可以用来装配bean. 都可以写在字段上,或写在setter方法上. 2.@Autowired默认按类型装配(这个注解是属业spring的),默认情况下必 ...

  8. STM32-cJSON库的打包和解析

    这几天使用了一下JSON在STM32103平台上的使用,还是很好用的,在此记录下. JSON是啥我也不总结了,就是直观的看起来是一个有格式的字符串,看起来非常清楚明白,有点像Python中的dicit ...

  9. Cotex-M4简介

    ARM Cortex™-M4 处理器是由 ARM 专门开发的最新嵌入式处理器,用以满足需要有效且易于使用的控制和信号处理功能混合的数字信号控制市场. 高效的信号处理功能与 Cortex-M 处理器系列 ...

  10. 课程一(Neural Networks and Deep Learning),第二周(Basics of Neural Network programming)—— 1、10个测验题(Neural Network Basics)

    --------------------------------------------------中文翻译---------------------------------------------- ...