NDK开发基础
1. 基础
1.1 指定平台
- 编译apk时可以指定编译哪些平台的so,在app下的build.gradle里配置 ndk 即可,默认不写则是四个平台都编译;
defaultConfig {
applicationId "com.example.devicecheckfinger"
minSdk 22
targetSdk 34
versionCode 1
versionName "1.0"
ndk{
abiFilters 'armeabi-v7a', 'arm64-v8a'
}
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
1.2 可选平台
- 查看自己的cpu支持哪些平台,可以使用Build下的一个api,如下:
Log.d("sana abi support", Arrays.toString(Build.SUPPORTED_ABIS));
// [arm64-v8a, armeabi-v7a, armeabi]
2. jni相关
首先,JNI是Java Native Interface缩写,JNI提供了一套标准API,允许Java代码与本地编译语言(如C/C++)交互;
- Java调用本地代码:通过
native关键字声明方法,由C/C++实现具体逻辑。 - 本地代码调用Java:C/C++可通过JNI访问Java对象、调用Java方法;
- 实际上ndk与jni是一回事,写的代码是c或者c++,而不是java的代码了;
在生成的示例文件中,可以发现有些函数前会有 extern "C" 字样,他是什么意思?
- extern "C"是为了解决C++ 的重载特性,会出现名称粉碎这个特性,C++编译器会把void foo(int)编译为类似_foo_int的符号,而C编译器只会生成 _foo ,若 C++ 直接调用 C 函数,会因符号名不匹配导致链接错误,这么写就是为了消除这种特性;
众所周知,jni函数前两个参数都是固定的,第一个为jnienv,第二个则是jclass或者jobject,第二个参数分别是如下情况:
- jclass对应的java方法是静态方法,有static修饰符;
- jobject对应的java方法则是实例方法;
- JVM虚拟机:JVM是Java虚拟机的简称,是Java字节码的运行环境,JVM标准实现是栈架构虚拟机,执行class文件,采用JIT即时编译,运行时效率不高;
- ART虚拟机:ART是Android Runtime,是Android系统上的运行环境,ART处理的是dex字节码文件格式;Android早期确实使用JVM(具体是Dalvik虚拟机)来运行应用,但Dalvik采用JIT即时编译,运行时效率不高;2014年安卓4.4开始引入ART作为实验选项,5.0后全面取代Dalvik;ART最大的改变是采用AOT预先编译技术,把字节码提前编译成本地机器码;
- so加载主要会执行的几个函数:首先是init、init_array、Jni_OnLoad;
3. java反射
资料:https://www.jianshu.com/p/9be58ee20dee
https://www.runoob.com/java/java-reflection.html
3.1 基本概念
- 在Android开发中,反射(Reflection) 是Java语言的核心特性之一,它允许程序在运行时动态加载类、查看类的信息(比如类名、方法、字段、构造器等)、动态调用对象的方法、动态访问或修改对象的字段;
- 或者说:JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制;
3.2 重要api
| 类名 | 用途 |
|---|
| Class类 | 代表类的实体,在运行的Java应用程序中表示类和接口 |
| Field类 | 代表类的成员变量(成员变量也称为类的属性) |
| Method类 | 代表类的方法 |
| Constructor类 | 代表类的构造方法 |
- Class代表类的实体,在运行的Java应用程序中表示类和接口,在这个类中提供了很多有用的方法,这里对他们简单的分类介绍;
- 获得类相关的方法:
| 方法 | 用途 |
|---|
| asSubclass(Class clazz) | 把传递的类的对象转换成代表其子类的对象 |
| Cast | 把对象转换成代表类或是接口的对象 |
| getClassLoader() | 获得类的加载器 |
| getClasses() | 返回一个数组,数组中包含该类中所有公共类和接口类的对象 |
| getDeclaredClasses() | 返回一个数组,数组中包含该类中所有类和接口类的对象 |
| forName(String className) | 根据类名返回类的对象 |
| getName() | 获得类的完整路径名字 |
| newInstance() | 创建类的实例 |
| getPackage() | 获得类的包 |
| getSimpleName() | 获得类的名字 |
| getSuperclass() | 获得当前类继承的父类的名字 |
| getInterfaces() | 获得当前类实现的类或是接口 |
| 方法 | 用途 |
|---|
| getField(String name) | 获得某个公有的属性对象 |
| getFields() | 获得所有公有的属性对象 |
| getDeclaredField(String name) | 获得某个属性对象 |
| getDeclaredFields() | 获得所有属性对象 |
| 方法 | 用途 |
|---|
| | |
| getAnnotation(Class annotationClass) | 返回该类中与参数类型匹配的公有注解对象 |
| getAnnotations() | 返回该类所有的公有注解对象 |
| getDeclaredAnnotation(Class annotationClass) | 返回该类中与参数类型匹配的所有注解对象 |
| getDeclaredAnnotations() | 返回该类所有的注解对象 |
| 方法 | 用途 |
|---|
| getConstructor(Class...<?> parameterTypes) | 获得该类中与参数类型匹配的公有构造方法 |
| getConstructors() | 获得该类的所有公有构造方法 |
| getDeclaredConstructor(Class...<?> parameterTypes) | 获得该类中与参数类型匹配的构造方法 |
| getDeclaredConstructors() | 获得该类所有构造方法 |
| 方法 | 用途 |
|---|
| getMethod(String name, Class...<?> parameterTypes) | 获得该类某个公有的方法 |
| getMethods() | 获得该类所有公有的方法 |
| getDeclaredMethod(String name, Class...<?> parameterTypes) | 获得该类某个方法 |
| getDeclaredMethods() | 获得该类所有方法 |
| 方法 | 用途 |
|---|
| isAnnotation() | 如果是注解类型则返回true |
| isAnnotationPresent(Class<? extends Annotation> annotationClass) | 如果是指定类型注解类型则返回true |
| isAnonymousClass() | 如果是匿名类则返回true |
| isArray() | 如果是一个数组类则返回true |
| isEnum() | 如果是枚举类则返回true |
| isInstance(Object obj) | 如果obj是该类的实例则返回true |
| isInterface() | 如果是接口类则返回true |
| isLocalClass() | 如果是局部类则返回true |
| isMemberClass() | 如果是内部类则返回true |
- Field代表类的成员变量(成员变量也称为类的属性):
| 方法 | 用途 |
|---|
| equals(Object obj) | 属性与obj相等则返回true |
| get(Object obj) | 获得obj中对应的属性值 |
| set(Object obj, Object value) | 设置obj中对应属性值 |
| 方法 | 用途 |
|---|
| invoke(Object obj, Object... args) | 传递object对象及参数调用该对象对应的方法 |
| 方法 | 用途 |
|---|
| newInstance(Object... initargs) | 根据传递的参数创建类的对象 |
- setAccessible方法,用于获得权限,是一个很重要的点;
4. 基础开发
- 目标:读写SD卡文件;创建项目后需要先在静态资源里申请权限,这里分别是sd卡的读、写、操作权限:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
extern "C"
JNIEXPORT jstring JNICALL
Java_com_sana_kanxuendk_MainActivity_readSDcardFile(JNIEnv *env, jobject thiz, jstring path) {
jstring jstring_ret = env->NewStringUTF("null");
char * tmp = (char *)env->GetStringUTFChars(jstring_ret, 0);
char* filePath = (char*)env->GetStringUTFChars(path, 0);
FILE *fp;
fp = fopen(filePath, "r");
if (fp == NULL){
LOGI("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));
jstring_ret = env->NewStringUTF(buff);
return jstring_ret;
}
package com.sana.kanxuendk;
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("sanaTag", "name:" + studentName + ",age:" + studentAge + ",flag:" + flag);
getAge();
return "studyRet";
}
public static int calcLength(String param) {
return param.length();
}
private int getAge() {
return studentAge;
}
}
- getDeclaredMethods:获得该类所有方法,但不包含构造方法:
public static void func_reflection(){
Class clz = null;
try {
// 获取该类
clz = Class.forName("com.sana.kanxuendk.Student");
// getDeclaredMethods 获取该类的所有方法 但不包括构造方法
Method[] declaredMethods = clz.getDeclaredMethods();
for (Method method : declaredMethods) {
Log.d("sanaTag", "method:" + method.getName());
Log.d("sanaTag", "method getReturnType:" + method.getReturnType().getSimpleName());
}
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
- getDeclaredConstructors:获取该类所有构造方法:
// 获取该类所有构造方法
Constructor[] declaredConstructors = clz.getDeclaredConstructors();
for (Constructor constructor : declaredConstructors) {
Log.d("sanaTag", "constructor:" + constructor.getName());
Log.d("sanaTag", "constructor toGenericString:" + constructor.toGenericString());
}
public static void func_reflection2() {
// 获取类
Class clz = Student.class;
try {
// 获取方法
Method method_study = clz.getMethod("study", new Class[]{int.class});
// 获取构造方法
Constructor constructor = clz.getConstructor(new Class[]{String.class, int.class});
// 实例化
Student student = (Student) constructor.newInstance("xiayutian", 23);
// 调用方法
String res = (String) method_study.invoke(student, new Object[]{666});
Log.d("sanaTag", "func_reflection2 res:" + res);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void func_reflection3() {
Class clz = one.getClass();
try {
// Method method_getAge = clz.getMethod("getAge", new Class[]{});
Method method_getAge = clz.getDeclaredMethod("getAge");
// 设置权限
method_getAge.setAccessible(true);
int age = (int) method_getAge.invoke(one, new Object[]{});
Log.d("sanaTag", "func_reflection3 age:" + age);
}catch (Exception e) {
e.printStackTrace();
}
}
5. NDK反射
NDK 通过 JNI 提供以下关键函数实现反射:
FindClass():获取 Java 类对象(如 jclass cls = env->FindClass("com/example/MyClass"););GetMethodID() / GetFieldID():获取方法或字段 ID(需指定方法名、签名和返回值类型);Call<Type>Method() / Get<Type>Field():通过 ID 调用方法或读写字段;
- 实例1:调用study方法;
extern "C"
JNIEXPORT jint JNICALL
Java_com_sana_kanxuendk_MainActivity_callJavaMethod(JNIEnv *env, jobject thiz, jobject student) {
// 找到类 与下一句 FindClass 同等
jclass jclazz_student = env->GetObjectClass(student);
jclass jclazz_student2 = env->FindClass("com/sana/kanxuendk/Student");
// 获取方法id ndk层面的获取方法 通过id去调用
jmethodID jmethod_study = env->GetMethodID(jclazz_student, "study", "(I)Ljava/lang/String;");
int flag = 34;
// 调用 jmethod_study 方法
jobject jstring_ret = env->CallObjectMethod(student, jmethod_study, flag);
char * tmp = (char *)env->GetStringUTFChars((jstring)jstring_ret, 0);
LOGI("jstring_ret : %s", tmp);
return flag;
}
- 实例2:调用static方法calcLength:
extern "C"
JNIEXPORT jint JNICALL
Java_com_sana_kanxuendk_MainActivity_callJavaMethod2(JNIEnv *env, jobject thiz) {
jclass jclazz_student2 = env->FindClass("com/sana/kanxuendk/Student");
jmethodID jmethod_study = env->GetStaticMethodID(jclazz_student2, "calcLength", "(Ljava/lang/String;)I");
jstring jstring_name = env->NewStringUTF("sana");
int length = env->CallStaticIntMethod(jclazz_student2, jmethod_study, jstring_name);
LOGI("sana length : %d", length);
return length;
}