本系列文章如下:
我们这里做一个简单的计算器demo,其中运算的逻辑由Native实现,而且我们采用动态注册的方式来实现
样式大概如下:
里面有两个输入框,下面有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); }
然后,我们先用来看下对应的布局文件如下:
这时候,我们来看下对应的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#include #include
依照前面动态注册方法的步骤,我们要重写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 := jnidemo3LOCAL_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
,如下图:
然后打开Android Studio
里面的Terminal
,然后拖拽JNITools.class
到Terminal
。这样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" }
这时候我们目录结构如下:
jnitools.c
的内容如下: #include#include #include #include 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; }
至此,整个代码已经全部完成。我们可以运行下试试。其实挺简单的。