本系列文章如下:
我們這里做一個(gè)簡(jiǎn)單的計(jì)算器demo,其中運(yùn)算的邏輯由Native實(shí)現(xiàn),而且我們采用動(dòng)態(tài)注冊(cè)的方式來實(shí)現(xiàn)
樣式大概如下:
里面有兩個(gè)輸入框,下面有4個(gè)按鈕匀归,代表加減乘除项鬼,最下面有個(gè)TextView蜻直,表示結(jié)果饵逐。
我們把運(yùn)算的邏輯抽象出來括眠,用一個(gè)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);
}
然后倍权,我們先用來看下對(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í)候掷豺,我們來看下對(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);
}
}
由于我們不是通過javah
來生成.c文件,所以我們要?jiǎng)?chuàng)建一個(gè)jni
的文件夾薄声,然后創(chuàng)建一個(gè)jnitools.c
文件当船。這時(shí)候jnitools.c
文件里面應(yīng)該什么都沒有,我們看到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方法,然后我們寫出這些方法的具體實(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è)方法的步驟蒸绩,我們要重寫JNI_OnLoad(JavaVM* vm, void* reserved)
函數(shù)。所以我們?cè)?code>jnitools.c中重寫函數(shù)這個(gè)函數(shù)铃肯,如下:
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved){
return JNI_VERSION_1_6;
}
有時(shí)候患亿,我們要打印日志,來幫助我們識(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í)候咙冗,開始注冊(cè)代碼,我們開始編寫注冊(cè)代碼
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved){
//打印日志漂彤,說明已經(jīng)進(jìn)來了
__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");
//開始注冊(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ǔ)充一下挫望,很多人立润,最后失敗,都是簽名的問題媳板,可以建議使用java -p 命令行
來對(duì)比簽名桑腮,我這是這樣操作的。首先打開JNITools.class
蛉幸,如下圖:
然后打開Android Studio
里面的Terminal
破讨,然后拖拽JNITools.class
到Terminal
丛晦。這樣Terminal
就自動(dòng)打開了,然后我們?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)單的。