一、创建空白项目
二、创建控件(2个输入框、4个按钮分别id为:add,sub,mul,div)
三、为控件添加事件:
package com.example.ndk_dongtaizhuce;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
//声明变量
private EditText first;
private EditText second;
private Button add;
private Button sub;
private Button mul;
private Button div;
private float one;
private float two;
//加载JNI文件
static {
System.loadLibrary("jisuanqi");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//初始化控件
init();
//开始运算
yunsuan();
}
private void yunsuan(){
//为按钮添加事件
View.OnClickListener cl = new View.OnClickListener() {
@Override
public void onClick(View view) {
switch (view.getId()){
case R.id.add:
//获取两个编辑框的值
one = Float.parseFloat(first.getText().toString());
two = Float.parseFloat(second.getText().toString());
//显示
Toast.makeText(MainActivity.this,add(one,two)+"",Toast.LENGTH_LONG).show();
break;
case R.id.sub:
//获取两个编辑框的值
one = Float.parseFloat(first.getText().toString());
two = Float.parseFloat(second.getText().toString());
Toast.makeText(MainActivity.this,sub(one,two)+"",Toast.LENGTH_LONG).show();
break;
case R.id.mul:
//获取两个编辑框的值
one = Float.parseFloat(first.getText().toString());
two = Float.parseFloat(second.getText().toString());
Toast.makeText(MainActivity.this,mul(one,two)+"",Toast.LENGTH_LONG).show();
break;
case R.id.div:
//获取两个编辑框的值
one = Float.parseFloat(first.getText().toString());
two = Float.parseFloat(second.getText().toString());
Toast.makeText(MainActivity.this,div(one,two)+"",Toast.LENGTH_LONG).show();
break;
default:
break;
}
}
};
add.setOnClickListener(cl);
sub.setOnClickListener(cl);
mul.setOnClickListener(cl);
div.setOnClickListener(cl);
}
//初始化控件
private void init(){
//绑定两个编辑框
first = (EditText) findViewById(R.id.editText);
second = (EditText) findViewById(R.id.editText2);
//绑定四个按钮
add = (Button) findViewById(R.id.add);
sub = (Button) findViewById(R.id.sub);
mul = (Button) findViewById(R.id.mul);
div = (Button) findViewById(R.id.div);
}
//定义native方法,实现在so层
public native float add(float one,float two);
public native float sub(float one,float two);
public native float mul(float one,float two);
public native float div(float one,float two);
}
在app目录下创建新目录命名为JNI,创建jisuanqi.c文件
编写以下代码:
#include <jni.h>//引入头文件
//功能函数
jfloat addc(JNIEnv* env,jobject obj,jfloat a,jfloat b){
return a + b;
}
jfloat subc(JNIEnv* env,jobject obj,jfloat a,jfloat b){
return a - b;
}
jfloat mulc(JNIEnv* env,jobject obj,jfloat a,jfloat b){
return a * b;
}
jfloat divc(JNIEnv* env,jobject obj,jfloat a,jfloat b){
return a / b;
}
//绑定C层和java层
JNINativeMethod nativeMethod[]={
{"add","(FF)F",(void*)addc},
{"sub","(FF)F",(void*)subc},
{"mul","(FF)F",(void*)mulc},
{"div","(FF)F",(void*)divc},
};
//注册函数
jint registerNative(JNIEnv* env){
//获取类
jclass clazz = (*env)->FindClass(env,"com/example/ndk_dongtaizhuce/MainActivity");
//注册方法 env:this、clazz:类、nativeMethod:注册方法、注册函数个数
if((*env)->RegisterNatives(env,clazz,nativeMethod,sizeof(nativeMethod)/sizeof(nativeMethod[0]))!=JNI_OK){
return JNI_ERR;
}
return JNI_OK;
}
//使用JNI_OnLoad进行动态注册
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm,void* reserved){
JNIEnv* env;
//vm,void**:二级指针,JNI_VERSION_1_4:jni版本
if( (*vm)->GetEnv(vm,(void**)&env,JNI_VERSION_1_4)!=JNI_OK){
return JNI_ERR;
}
if(registerNative(env)!=JNI_OK){
return JNI_ERR;
}
return JNI_VERSION_1_4;
}
新建Android.mk和Application.mk
#Android.mk文件
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := jisuanqi #模块名称
LOCAL_SRC_FILES := jisuanqi.c #源文件 .c或者.cpp
LOCAL_ARM_MODE := arm #编译后的指令集 ARM指令
LOCAL_LDLIBS += -llog #依赖库
include $(BUILD_SHARED_LIBRARY) #指定编译文件的类型
#Application.mk文件
APP_ABI := all
cmd来到当前文件夹下:ndk-build
表示成功。
一、静态注册和动态注册的区别:
静态方法文件名过长:Java+包名+类名+方法名(native方法)
例:Java_com_example_demo_1jni_MainActivity_getstring
调用native方法时速度慢。
动态方法:有JNINativeMethod函数映射表,调用时查找速度快,方法名可以自定义。
JNI_OnLoad方法在启动so文件时最先调用,并注册方法到环境。
问题集锦:
一、加载so出错
1、检查c文件中
FindClass(env,"com/example/jisuanqi/MainActivity");
路径是否正确。
2、找不到so文件
Android开发中so库文件的存放位置
在自己编译so库文件,或者引用第三方的so库文件时,库文件存放目录不正确经常会引起很多问题。这里总结一下。
so文件存放目录 或 默认生成目录:
- jniLibs/CPU_ABI:对于Android Studio项目
- libs/CPU_ABI:对于Eclipse项目
- jni/CPU_ABI:在AAR文件中
- lib/CPU_ABI:在APK文件中
- 在app的nativeLibraryPath目录中:对于Android 版本<5.0 的设备
- 在app的legacyNativeLibraryDir/CPU_ARCH目录中:对于Android版本>=5.0的设备
以上提到的CPU_ABI的可能取值如下,它的值取决于你编译的目标平台。
- armeabi
- armeabi-v7a
- arm64-v8a
- x86
- x86_64
- mips
- mips64
引用so文件时,如果so文件被放在默认目录下:
- 在Android Studio中,so文件在jniLibs/CPU_ABI下
- 在Eclips中,so文件在jni/CPU_ABI下
那么你不用做额外的设置。
如果so文件没有被放在默认目录下
比如在Android Studio中,so文件放在了libs目录下。则需要在build.gradle中添加设置,来指定实际存放目录。
android {
...
sourceSets {
...
main.jni.srcDirs = []
main.jniLibs.srcDirs = ['libs']
}
....