本系列文章如下:
我們這里做一個(gè)簡(jiǎn)單的計(jì)算器demo,其中運(yùn)算的邏輯由Native實(shí)現(xiàn),而且我們采用動(dòng)態(tài)注冊(cè)的方式來(lái)實(shí)現(xiàn)
樣式大概如下:

里面有兩個(gè)輸入框,下面有4個(gè)按鈕,代表加減乘除,最下面有個(gè)TextView,表示結(jié)果。
我們把運(yùn)算的邏輯抽象出來(lái),用一個(gè)JNITools來(lái)表示。代碼如下:
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);
}
然后,我們先用來(lái)看下對(duì)應(yīng)的布局文件如下:
<?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="請(qǐng)輸入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="請(qǐng)輸入b"
android:layout_weight="1.0"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
<TextView
android:text="請(qǐng)選擇符號(hào)"
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="計(jì)算結(jié)果"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
這時(shí)候,我們來(lái)看下對(duì)應(yīng)的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);
}
}
由于我們不是通過(guò)javah來(lái)生成.c文件,所以我們要?jiǎng)?chuàng)建一個(gè)jni的文件夾,然后創(chuàng)建一個(gè)jnitools.c文件。這時(shí)候jnitools.c文件里面應(yīng)該什么都沒(méi)有,我們看到JNITools這個(gè)文件有4個(gè)native方法,所以我們要也要在jnitools.c里面聲明這4個(gè)方法。如下:
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);
依次對(duì)應(yīng)Java層的native方法,然后我們寫(xiě)出這些方法的具體實(shí)現(xiàn),如下;
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>
依照前面動(dòng)態(tài)注冊(cè)方法的步驟,我們要重寫(xiě)JNI_OnLoad(JavaVM* vm, void* reserved)函數(shù)。所以我們?cè)?code>jnitools.c中重寫(xiě)函數(shù)這個(gè)函數(shù),如下:
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved){
return JNI_VERSION_1_6;
}
有時(shí)候,我們要打印日志,來(lái)幫助我們識(shí)別是否進(jìn)入這個(gè)方法,所以我們要配置一個(gè)log,這時(shí)候,我們創(chuàng)建一個(gè)Android.mk文件,然后進(jìn)行如下的編輯:
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>代碼
這時(shí)候,開(kāi)始注冊(cè)代碼,我們開(kāi)始編寫(xiě)注冊(cè)代碼
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved){
//打印日志,說(shuō)明已經(jīng)進(jìn)來(lái)了
__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;
}
//注冊(cè)四個(gè)方法,注意簽名
const JNINativeMethod method[]={
{"add","(II)I",(void*)addNumber},
{"sub","(II)I",(void*)subNumber},
{"mul","(II)I",(void*)mulNumber},
{"div","(II)I",(void*)divNumber}
};
//找到對(duì)應(yīng)的JNITools類
jclass jClassName=(*env)->FindClass(env,"com/gebilaolitou/jni/JNITools");
//開(kāi)始注冊(cè)
jint ret = (*env)->RegisterNatives(env,jClassName,method, 4);
//如果注冊(cè)失敗,打印日志
if (ret != JNI_OK) {
__android_log_print(ANDROID_LOG_DEBUG, "JNITag", "jni_register Error");
return -1;
}
return JNI_VERSION_1_6;
}
這里補(bǔ)充一下,很多人,最后失敗,都是簽名的問(wèn)題,可以建議使用java -p 命令行來(lái)對(duì)比簽名,我這是這樣操作的。首先打開(kāi)JNITools.class,如下圖:

然后打開(kāi)Android Studio里面的Terminal,然后拖拽JNITools.class到Terminal。這樣Terminal就自動(dòng)打開(kāi)了,然后我們?cè)?code>Terminal里面輸入pwd,就已經(jīng)在這個(gè)目錄里面了。然后我們?cè)?code>Terminal輸入如下代碼
javap -s JNITools.class
Terminal就會(huì)輸出這個(gè)類的所有簽名,如下:
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
}
我們?cè)谶@里核對(duì)下我們注冊(cè)方法的簽名。
最后,我們?cè)趯?duì)應(yīng)的build.gradle里面defaultConfig里面添加如下代碼:
ndk{
moduleName "jnidemo3"
abiFilters 'x86','armeabi-v7a','arm64-v8a'
ldLibs "log"
}
這時(shí)候我們目錄結(jié)構(gòu)如下:

jnitools.c的內(nèi)容如下:
#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;
}
至此,整個(gè)代碼已經(jīng)全部完成。我們可以運(yùn)行下試試。其實(shí)挺簡(jiǎn)單的。