想要使用jni进行ndk开发,我们首先要安装下面这些工具,否则直接从入门到放弃。
下载ndk支持
在Android studio中下载上图中框选的两个工具,版本号自己任意选一个。下载完成之后,Android Studio就拥有了进行ndk编译的能力。
新建ndk工程
如果是一个全新的工程,我们在新建工程时,选择c++工程,然后无脑下一步即可,这样Android Studio就会给你生成一个c++工程的模板代码,我们后续要添加一些c/c++代码都可以仿照这个模板。
如果,我们是现在原有的工程上添加一个c/c++的library呢?我们可以新建一个native library,然后也是无脑下一步,跟新建一个java的library流程一致。
c/c++代码提示设置
在我们编写c/c++的过程中,有一个十分恶心的东西,就是没有代码提示,有时候编译器抽风,无故报红。这对一些ndk老手来说可能不算什么,但是对一些新手来说就十分难受了,那么我们该如何解决这个问题呢?
jni,即Java本地接口,它的存在使得Java与本地其他类型语言(如c、c++)能够进行交互。java的很多功能实际上的驱动都是通过c/c++开发的,通过JNI,Java可以调用c/c++实现的驱动,从而扩展jvm的能力。另外,在高效率的数学运算、游戏的实时渲染、音视频的编码和解码方面,一般都是用c开发的。
jni在线api:https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/jniTOC.html
jni和ndk的关系
案例讲解
我们来看看ndk开发中涉及到的几个文件:
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html
# Sets the minimum version of CMake required to build the native library.
cmake_minimum_required(VERSION 3.18.1)
# Declares and names the project.
project("jnidemo")
# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.
add_library( # Sets the name of the library.
jnidemo
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
native-lib.cpp)
# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.
find_library( # Sets the name of the path variable.
log-lib
# Specifies the name of the NDK library that
# you want CMake to locate.
log)
# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.
target_link_libraries( # Specifies the target library.
jnidemo
# Links the target library to the log library
# included in the NDK.
${log-lib})
#include <jni.h>
#include <string>
// 日志输出
#include <android/log.h>
#define TAG "Brett"
// __VA_ARGS__ 代表 ...的可变参数
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__);
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__);
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__);
extern "C" JNIEXPORT jstring JNICALL
Java_com_mvp_jnidemo_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
extern "C"
JNIEXPORT void JNICALL
Java_com_mvp_jnidemo_MainActivity_changeStr(JNIEnv *env, jobject thiz) {
//jclass获取方式一
// jclass cls = env->FindClass("com/mvp/jnidemo/MainActivity");
//方式二
jclass cls = env->GetObjectClass(thiz);
jfieldID nameFid = env->GetFieldID(cls, "name", "Ljava/lang/String;");
jstring value = env->NewStringUTF("Beyond");
env->SetObjectField(thiz, nameFid, value);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_mvp_jnidemo_MainActivity_changeAge(JNIEnv *env, jclass clazz) {
jfieldID ageFid = env->GetStaticFieldID(clazz, "age", "I");
int age = env->GetStaticIntField(clazz, ageFid);
env->SetStaticIntField(clazz, ageFid, age + 10);
}
//c调用java
extern "C"
JNIEXPORT jint JNICALL
Java_com_mvp_jnidemo_MainActivity_callJavaMethod(JNIEnv *env, jobject thiz) {
jclass mainActivitCls = env->GetObjectClass(thiz);
// jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
jclass cls = env->GetObjectClass(thiz);
jmethodID addMid = env->GetMethodID(cls, "add", "(II)I");
// jint CallIntMethod(jobject obj, jmethodID methodID, ...)
int result = env->CallIntMethod(thiz, addMid, 1, 1);
LOGD("result:%d\n", result)
// ++++++++++++++++++++++ C调用 public String showString(String str,int value) 函数
jmethodID showStringMid = env->GetMethodID(mainActivitCls, "showString", "(Ljava/lang/String;I)Ljava/lang/String;");
// jobject (*CallObjectMethod)(jobject, jmethodID, ...);
jstring value = env->NewStringUTF("李元霸");
jstring resultStr = (jstring) env->CallObjectMethod(mainActivitThis, showStringMid, value, 9527);
// jstring是在jni中的,c/c++中需要转为char*
const char * resultCstr = env->GetStringUTFChars(resultStr, NULL);
LOGD("r==:%s\n", resultCstr);
}
extern “C” // 表示下面的代码,采用C的编译方式 如果是新建了一个.c文件,则需要删掉否则编译会报错,如果是新建了一个c++文件,则需要添加
JNIEXPORT // JNIEXPORT: JNI重要标记关键字,不能少 可以认为是为不同os平台设定的一个调用规则(标记为该方法可以被外部调用) Windows内部规则,与Linux内部规则 不同
void // void 代表java中的 void
JNICALL // 也是一个关键字,(可以少的) jni call (约束函数入栈顺序,和堆栈内存清理的规则)
JNIEnv *env JNI JNIEnv是整个jni的核心所在,我们学习jni其实就是学习JNIEnv里面提供的api方法
如果当前是 native-lib.c
// (*env)->xxx函数
// (*env)->DeleteLocalRef()
// C语言是 JNIEnv *env 二级指针
// (*env)->DeleteLocalRef(env, NULL); // C是没有对象的,想持有env环境,就必须传递进去
(*env)->NewStringUTF(env,“AAAA”);
如果当前是 native-lib.cpp ->调用一级指针下的函数
// env->xxx函数
// env->DeleteLocalRef()
// C++语言是 JNIEnv *env 一级指针
// env->DeleteLocalRef(NULL); // C++是有对象的,本来就会持有this,所以不需要传
为简单通用起见,我们就使用c++语言来编写相关的逻辑。
package com.mvp.jnidemo;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
import com.mvp.jnidemo.databinding.ActivityMainBinding;
public class MainActivity extends AppCompatActivity {
private String name = "Brett";
private static int age = 20;
// Used to load the 'jnidemo' library on application startup.
//可以认为哪里又native方法,哪里就需要System.loadLibrary("xxx");方法
static {
System.loadLibrary("jnidemo");
}
private ActivityMainBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
// Example of a call to a native method
TextView tv = binding.sampleText;
tv.setText(stringFromJNI());
Log.e("MainActivity", "name is " + name+" ,age is "+age);
changeStr();
changeAge();
Log.e("MainActivity", "name is " + name+" ,age is "+age);
callJavaMethod();
}
public int add(int num1, int num2) {
Log.e("MainActivity", "num1 is " + num1 + " ,num2 is " + num2);
return num1 + num2;
}
public String showString(String str,int value) {
System.out.println("C居然调用了我 showString str:" + str + " value:" + value);
return "【" + str + "】";
}
/**
* A native method that is implemented by the 'jnidemo' native library,
* which is packaged with this application.
*/
public native String stringFromJNI();
public native void changeStr();
public static native void changeAge();
public native int callJavaMethod();
}
代码 | 类型 |
---|---|
“I” | int |
“B” | byte |
“C” | char |
“D” | double |
“F” | float |
“J” | long |
“S” | short |
“Z” | boolean |
“V” | void |
“[…;” | 数组 |
“[[…;” | 二维数组 |
“[[[…;” | 三维数组 |
String[] | [Ljava/lang/String; |
String[][] | [[Ljava/lang/String; |
int,String,String[] | ILjava/lang/String;[Ljava/lang/String; |
int,boolean,long,String[],double | IZJ[Ljava/lang/String;D |
Class>,String,Object…paramType | Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/Object; |
int[] | [I |