NDK

定义:NDK编程指的是使用Android Native Development Kit(NDK)进行开发,它允许开发者使用C和C++等原生编程语言来编写Android应用的部分代码,通常是处理性能要求高或者需要直接访问设备硬件的功能

JNI函数命名规则

Java + 包名 + 类名 + 函数名(以_间隔),Eg:Java_a_b_c_ndk_MainActivity_getLength

Java_a_b_c_ndk_MainActivity_getLength(JNIEnv *env, jobject thiz, jstring param){}
/*每个参数的含义
JNIEnv env:
JNIEnv 是一个指向JNI环境的指针,它提供了一系列的JNI函数和数据结构,用于在Java虚拟机(JVM)中操作Java对象、调用Java方法、异常处理等。通过 env 参数,本地代码能够与JVM进行交互,执行JNI操作。

jobject thiz:
jobject 是JNI中的一个泛型类型,表示一个Java对象的引用。在本例中,thiz 参数代表了调用本地方法的Java对象的引用。通常情况下,它指向当前实例对象,即调用本地方法的对象实例。

jstring param:
jstring 是JNI中表示Java字符串的类型。在JNI中,字符串类型需要特殊处理,因为Java中的字符串和C/C++中的字符串表示方式不同。param 参数表示从Java层传递过来的字符串参数。*/

数据类型

函数签名

查看函数签名的方法

输入: javap -s a.b.c.ndk01.MainActivity

读写SDCard

申请读、写、管理SDCard权限

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>

MainActivity.java

package a.b.c.ndk;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.View;
import android.widget.TextView;

import a.b.c.ndk.databinding.ActivityMainBinding;

public class MainActivity extends AppCompatActivity {
private final static int MY_PERMISSION_REQUEST_WRITE_CODE = 11;

// Used to load the 'ndk' library on application startup.
static {
System.loadLibrary("ndk");
}
public int testFun(String a,double b,long c)
{
return 1;
}

private ActivityMainBinding binding;

@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);

binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
testFun("aa",4.5,5);

// Example of a call to a native method
TextView tv = binding.sampleText;
tv.setText(stringFromJNI());
tv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view)
{
@Override
int ret = ContextCompat.checkSelfPermission(MainActivity.this,Manifest.permission.WRITE_EXTERNAL_STORAGE);//这里注意:Manifest一定要选android
if(ret == PackageManager.PERMISSION_GRANTED)
{
//PackageManager.PERMISSION_GRANTED: 这个常量的值是 0,表示应用程序已被授予了该权限。
//PackageManager.PERMISSION_DENIED: 这个常量的值是 -1,表示应用程序未被授予该权限,可能是因为尚未请求权限或用户显式拒绝了。
Log.i("qqqqqqqq","已经有写SDCard的权限了");
String fp1 = Environment.getExternalStoragePublicDictory(Environment.DIRECTORY_DOWNLOADS).getAbsoultaePath();
String fc = readSDCardFile(fp1+"/b.txt");
Log.i("qqqqqqqq","文件内容:"+fc);

}
else
{
Log.i("qqqqqqqq","还没有写SDcard的权限");
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},MY_PERMISSION_REQUEST_WRITE_CODE)
}

}
});
@Override
public void onRequestPermissionResult(int requestCode,@Nonull String[] permission,@Nonull int[] grantResults)
{
/*requestCode:用于标识权限请求的请求码,通常是在发起权限请求时传递的。
permissions:请求的权限数组,包含了应用程序请求的权限列表。
grantResults:授权结果数组,包含了对应于请求权限数组中每个权限的授权结果、(PackageManager.PERMISSION_GRANTED 或 PackageManager.PERMISSION_DENIED)。*/
super.onRequestPermissionResult(requestCode,permission,grantResult);
swtich(requetsCode)//注意:break不要忘了写
{
case MY_PERMISSION_RQQUEST_WRITE_CODE:
{
if(grantResult.length>0&&grantResults[0]!=-1)
{
Log.i("qqqqqqqq","写SDcard权限申请成功");
}
else
{
Log.i("qqqqqqqq","写SDcard权限申请失败");
}
break;
}
case 33:
{
Log.i("qqqqqqqq","这是申请其他权限的结果");
break;
}
}
}
}

/**
* A native method that is implemented by the 'ndk' native library,
* which is packaged with this application.
*/
public native String stringFromJNI();
public native int getLength(String param);
public native String readSDCardFile(String filePath);
}

native-lib.cpp

include <jni.h>
#include <string>
#include<android/log.h>

#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,"qqqqqqqq", __VA_ARGS__);//注意别写错
#define LOGD(...) __android_log_print(ANDROID_LOG_WARN,,"qqqqqqqq", __VA_ARGS__);
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,"qqqqqqqq", __VA_ARGS__);
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,"qqqqqqqq", __VA_ARGS__);
/*#define LOGI(...):

#define 是 C/C++ 语言中的预处理指令,用于定义宏。
LOGI 是宏的名称,可以根据需要自定义。在这里,LOGI 可以用来打印信息级别的日志。
__android_log_print():

__android_log_print() 是 Android NDK 提供的一个函数,用于在 Android 的 LogCat 中打印日志信息。
它接受多个参数,包括日志级别、标签和日志内容等。

ANDROID_LOG_INFO:
ANDROID_LOG_INFO 是 __android_log_print() 函数的第一个参数,表示日志级别为信息(INFO)。其他常用的日志级别还包括 ANDROID_LOG_DEBUG、ANDROID_LOG_WARN 等。

"qqqqqqqq":
"qqqqqqqq" 是日志的标签,用于在 LogCat 中识别和过滤日志条目。可以根据实际需求修改为其他标签。

__VA_ARGS__:
__VA_ARGS__ 是一个特殊的宏,表示宏中的可变参数列表。在这里,它用来接收 LOGI 宏的所有额外参数,并将它们传递给 __android_log_print() 函数。*/
extern "C" JNIEXPORT jstring JNICALL
Java_a_b_c_ndk_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
extern "C"
JNIEXPORT jint JNICALL
Java_a_b_c_ndk_MainActivity_getLength(JNIEnv *env, jobject thiz, jstring param)
{


}
extern "C"
JNIEXPORT jstring JNICALL
Java_a_b_c_ndk_MainActivity_readSDCardFile(JNIEnv *env, jobject thiz, jstring file_path) {
jstring jstring_ret = env->NewStringUTF("null");
char*tmp = (char*)env->GetStringUTFChars(jstring_ret,0);
char* filePath = (char *)env->GetStringUTFChars(file_path,0);//JNI_TRUE:表示返回的字符串是从 Java 字符串复制出来的,即 JNI 在返回的字符串中创建了一个新的副本。
// JNI_FALSE:表示返回的字符串是直接指向 Java 字符串的内部缓冲区,没有进行复制。这种情况下,应该避免在本地代码中修改字符串内容,以免影响 Java 层原始数据。
FILE *fp;
fp = fopen(filePath,"r");//"r" 表示以只读方式打开文件。这意味着你可以从文件中读取数据,但不能写入或修改文件内容。
if(fp == NULL)
{
LOGE("fp == NULL, %s",filePath);
return jstring_ret;
}
char buff[1024];

while (fgets(buff,1024,fp)!=NULL)
{
LOGI("fgets :%s",buff);
}
env->ReleaseStringChars(jstring_ret, reinterpret_cast<const jchar *>(tmp));//reinterpret_cast 是 C++ 中的类型转换操作符
jstring_ret = env->NewStringUTF(buff);
return jstring_ret;
}

Java反射

含义:在运行时检查或操作类、方法、属性等程序结构的能力。通俗地讲,反射允许程序在运行时动态地获取信息和操作对象,而不需要在编译时就知道这些信息。

1、获取类

Class.forName("类名全路径")
类名.class
实例.getClass

2、获取方法和属性

类.getDeclaredMethods();
类.getDeclaredConstructors();
类.getMethod();
类.getDeclaredFields();

3、调用方法

方法.invoke();

用法详细解释

类.getDeclareMethods();
//这个方法返回一个 Method 对象数组,代表了该类中所有声明的方法,包括 public、protected、default(包级别)和 private 方法,但不包括从父类继承的方法。
//语法:Method[] methods = 类.getDeclaredMethods();
Class<?> clazz = MyClass.class;
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
System.out.println(method.getName());
}
类.getDeclaredConstructors();
//这个方法返回一个 Constructor 对象数组,代表了该类中所有声明的构造函数,包括 public、protected、default 和 private 构造函数。
//语法:Constructor<?>[] constructors = 类.getDeclaredConstructors();
Class<?> clazz = MyClass.class;
Constructor<?>[] constructors = clazz.getDeclaredConstructors();
for (Constructor<?> constructor : constructors) {
System.out.println(constructor.getName());
}
类.getMethod();
//这个方法用于获取指定方法名和参数类型的 Method 对象,只能获取到 public 方法,包括从父类继承而来的 public 方法。
//语法:Method method = 类.getMethod("methodName", parameterTypes);
Class<?> clazz = MyClass.class;
Method method = clazz.getMethod("someMethod", String.class, int.class);
类.getDeclaredFields();
//这个方法返回一个 Field 对象数组,代表了该类中所有声明的字段(成员变量),包括 public、protected、default 和 private 字段
//语法:Field[] fields = 类.getDeclaredFields();
Class<?> clazz = MyClass.class;
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
System.out.println(field.getName());
}
Eg:
public class Person {
private String name;
private int age;

public Person(String name, int age) {
this.name = name;
this.age = age;
}

public void sayHello() {
System.out.println("Hello, my name is " + name + " and I am " + age + " years old.");
}

private void privateMethod() {
System.out.println("This is a private method.");
}
}


import java.lang.reflect.Field;

public class ReflectionExample {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
Class<Person> personClass = Person.class;

// 获取所有声明的字段
Field[] fields = personClass.getDeclaredFields();

// 输出字段的名称和值
for (Field field : fields) {
field.setAccessible(true); // 因为字段是私有的,需要设置可访问
String fieldName = field.getName();
System.out.println("Field name: " + fieldName);
}
}
}


输出:
Field name: name
Field name: age

MainActivity,部分代码

public void fun1()
{
Class clz = null;
try
{
clz = Class.forName("a.b.c.ndk.Student");
Method[] declaredMethods = clz.getDeclareMethods();
for(Method method :declareMethods)
{
String methodName = method.getName();
String returnType = method.getReturnType().getSimpleName();
Log.i("qqqqqqqq","method:"+methodName+","+returnType);
}
Constructor[] declaredConstructors = clz.getDeclaredConstructors();
for(Constructor constructor:declareConstructors)
{
String methodName = constructor.getName();
String returnType = constructor.toGenericString();
Log.i("qqqqqqqq","Constructor:"+methodName","+returnType);
}
}catch(Exception e)
{
e.printStackTrace();
}
}
public void fun2()
{
Class clz = Student.class;
try
{
Method method_study = clz.getMethod("study",new Class[]{int.class});
Constructor constructor_Student = clz.getConstructor(new Class[]{String.class,int.class});
Student student = (Student)constructor_Student.newInstance(new Object[]{"lisi",25});
Object obj_ret = method_study.invoke(student,new Object[]{77});
Log.i("qqqqqqqq","study ret:"+(String) obj_ret);
}catch(Exception e)
{
e.printStackTrace();
}
}
public void fun3()
{
Class clz = one.getClass();
try
{
Method method_getAge0 = clz.getDeclaredMethod("getAge",new Class[]{});
method_getAge0.setAccessible(true);
//Method method_getAge = clz.getMethod("getAge",new Class[]{}); 只能获取public方法
Object obj_ret = method_getAge0.invoke(one,new Object[]{});
Log.i("qqqqqqqq","getAge ret:"+(int) obj_ret);
}catch(Exception e)
{
e.printStaceTrace();
}
}

Student.java

package a.b.c.ndk;

import android.util.Log;

public class Student
{
private String studentName;
private int studentAge = 22;

public Student(String studentName, int studentAge)
{
this.studentName = studentName;
this.studentAge = studentAge;
}

public Student(String studentName)
{
this.studentName = studentName;
}

public String study(int flag)
{
Log.i("qqqqqqqq","name:" + studentName +",age:" + studentAge + ",flag:" + flag);
return "studyRet";
}

public static int calaLength(String param)
{
return param.length();
}
public int getAge()
{
return studentAge;
}
}

NDK反射Java层

含义:NDK(Native Development Kit)是用于在 C/C++ 中编写代码并与 Java 层交互的工具集。在需要从 C/C++ 层访问 Java 层的情况下,可以通过 JNI(Java Native Interface)来实现。

1、获取类

jclass GetObjectClass(对象);
jclass FindClass("类名");

2、获取方法ID (javap -s a.b.c.javareflection.Student获取签名)

jmethodID GetMethonID(jclass clazz, const char*name, const char*sig);
jmethodID GetStaticMethodID(jclass clazz, const char* name,const char*sig);

3、调用方法

void CallVoidMethod(jobject obj,jmethodID methodID,......);
void CallStaticVoidMethod(jclass clazz, jmethodID methoID,......);
jobject CallObjectMethod(JNIEnv*,jobject,jmethodID,.......);

4、获取和设置成员变量

GetFieldID;
GetObjectField;
SetObjectField;

MainActivity,部分代码

Log.i("qqqqqqqq","callJavaFunFromJNI ret :"+callJavaFunFromJNI(one));
Log.i("qqqqqqqq","callJavaFunFromJNI ret :"+callStaticJavaFunFromJNI());
public native int callJavaFunFromJNI(Student param);
public native String callStaticJavaFunFromJNI();

native-lib.cpp,部分代码

extern "C"
JNIEXPORT jint JNICALL
MainActivity.callJavaFunFromJNI(JNIEnv *env, jobject thiz,jobject param)
{
jclass jclass_student = env->GetObjectClass(param);
jclass jclass_student2 = env->FindClass("a/b/c/ndk/Student");
jmethoID jmethodID_study = env-?GetMethod(jclass_student,"study","(I)Ljava/lang/String;");
int flag = 34;
jobject jobject_ret = env->CallObjectMethod(param,jmethodid_study,flag);//注意:不能直接传入值,要放个参数,否则会报错
char* t = (char*)env->GetStringUTFchars((jstring)jobject_ret,0);
LOGI("ndk call study ret: %s",t);
return flag;
}
extern "C"
JNIEXPORT jstring JNICALL
Java_a_b_c_ndk_MainActivity_callStaticJavaFunFromJNI(JNIEnv *env, jobject thiz)
{
jclass jclass_student2 = env->FindClass("a/b/c/ndk/Student");
jmethoID jmethodId_calcLength = env->GetStaticMethodID(jclass_student2,"calcLength","(Ljava/lang/String;)I");
jstring jstring_param = env->NewStringUTF("hahahaha");
jint jint_ret = env->CallStaticIntMethod(jclass_student2,jmethodId_calcLength,jstring_param);
LOGI("ndk call calcLength ret:%d",jint_ret);
return jstring_param;
}

NDK反射获取包名

查看系统源码:http://androidxref.com/6.0.1_r10/xref/frameworks/base/core/java/android/app/ActivityThread.java

native-lib.cpp,部分代码

//通过反射获取整个应用的ActivityThread类,获取类里面的静态函数,这个函数返回的是ActivityThread的一个实例,通过这个实例调用成员函数,得到上下文
extern "C"
JNIEXPORT jstring JNICALL
Java_a_b_c_k02s10_MainActivity_getPkgName(JNIEnv *env, jobject thiz)
{
jclass cls_ActivityThread = env->FindClass("android/app/ActivityThread");
jmethodID jmethodId_currentActivityThread = env->GetStaticMethodID(cls_ActivityThread, "currentActivityThread", "()Landroid/app/ActivityThread;");
jobject ins_currentActivityThread = env->CallStaticObjectMethod(cls_ActivityThread, jmethodId_currentActivityThread);

jmethodID jmethodId_getApplication = env->GetMethodID(cls_ActivityThread, "getApplication", "()Landroid/app/Application;");
jobject ins_mInitialApplication = env->CallObjectMethod(ins_currentActivityThread, jmethodId_getApplication);

jclass cls_Application = env->GetObjectClass(ins_mInitialApplication);
jmethodID jmethodId_getPackageName = env->GetMethodID(cls_Application, "getPackageName", "()Ljava/lang/String;");
jstring pkgName = (jstring)env->CallObjectMethod(ins_mInitialApplication, jmethodId_getPackageName);
return pkgName;
}

动态注册JNI函数

typedef struct {
const char* name; // Java 中调用本地方法的方法名
const char* signature; // Java 方法的签名
void* fnPtr; // 指向本地方法实现的函数指针
} JNINativeMethod;

RegisterNatives

// 是 JNI(Java Native Interface)中的一个函数,用于注册本地方法(native methods)
//原型:
JNIEXPORT jint JNICALL
RegisterNatives(JNIEnv *env, jclass cls, const JNINativeMethod *methods, jint nMethods);
env:JNI 环境指针,通过它可以访问 JNI 提供的方法和功能。
cls:要注册本地方法的 Java 类的 jclass 对象。
methods:一个 JNINativeMethod 结构体数组,定义了要注册的本地方法。
nMethods:要注册的本地方法的数量。

GetEnv

jint result = vm->GetEnv(&env, JNI_VERSION_1_4);
/*vm 是一个 JavaVM* 类型的指针,代表了 Java 虚拟机。
&env 是 JNIEnv* 类型指针的地址,通过传递 &env,GetEnv 函数会将当前线程的 JNI 环境指针写入到 env 变量中。
JNI_VERSION_1_4 是希望使用的 JNI 版本。*/

MainActivity,部分代码

public native String getPkgName2222();
Log.i("qqqqqqqq","jni22222 return:"+getPkgName2222());

native-lib.cpp

jstring getAAAA(JNIEnv *env, jobject thiz)
{
jclass cls_ActivityThread = env->FindClass("android/app/ActivityThread");
jmethodID jmethodId_currentActivityThread = env->GetStaticMethodID(cls_ActivityThread, "currentActivityThread", "()Landroid/app/ActivityThread;");
jobject ins_currentActivityThread = env->CallStaticObjectMethod(cls_ActivityThread, jmethodId_currentActivityThread);

jmethodID jmethodId_getApplication = env->GetMethodID(cls_ActivityThread, "getApplication", "()Landroid/app/Application;");
jobject ins_mInitialApplication = env->CallObjectMethod(ins_currentActivityThread, jmethodId_getApplication);

jclass cls_Application = env->GetObjectClass(ins_mInitialApplication);
jmethodID jmethodId_getPackageName = env->GetMethodID(cls_Application, "getPackageName", "()Ljava/lang/String;");
jstring pkgName = (jstring)env->CallObjectMethod(ins_mInitialApplication, jmethodId_getPackageName);

jstring ret = env->NewStringUTF(" from getAAAA!!!");
return ret;
}

jint JNI_OnLoad(JavaVM* vm,void* reserved)
{
JNIEnv* env = NULL;
jint restule = -1;
vm->GetEnv((void**)(&env),JNI_VERSION_1_4);

JNINativeMethod methods[] = {{"getPkgName2222"},"()Ljava/lang/String;",(void*)getAAAA};
env->Register(cls_MainActivity,methods,sizeof(methods)/sizeof(JNINativeMethod));

result = JNI_VERSION_1_4;
return result;
}