首页 > 其他分享 >NDK开发

NDK开发

时间:2024-09-13 17:28:00浏览次数:3  
标签:NDK String void nullptr 开发 env chen JNI

NDK

NDK

在Android开发中,NDK是一组用于开发C和C++代码的工具集合。它允许开发者使用C/C++编写底层代码,并与Java代码相结合。

NDK的文件配置

如需为您的应用编译和调试原生代码,您需要以下组件:

  • Android 原生开发套件 (NDK):这是一套可让您在 Android 应用中使用 C 和 C++ 代码的工具。
  • CMake:这是一款外部构建工具,可与 Gradle 搭配使用来构建原生库。如果您只计划使用 ndk-build,则不需要此组件。
  • LLDB:Android Studio 用于调试原生代码的调试程序。默认情况下,安装 Android Studio 时会随同安装 LLDB。

JNI(Java Native Interface)

jni用于java代码和其他语言所写的代码进行交互

JNI中的NDK文件和正常的java文件的区别在于

  1. 在MainActivity的位置会声明external fun stringFromJNI(): String这个函数是cpp文件的索引
  2. 同时在 build.gradle.kts 中会多出externalNativeBuild来对于生成的NDK文件的arm构架,x86/64构架的描述
  3. 同时多出了cpp文件以外还要CMakeLists的对于cmake的版本信息描述

多线程创建

#include <jni.h>
#include <string>
#include <android/log.h>
#include <pthread.h>
#define TAG "Tag_data"
#define  LOGD(...)  __android_log_print(ANDROID_LOG_DEBUG,"tag",__VA_ARGS__);
void myThread() {
    LOGD("myThread_data");
}

extern "C" JNIEXPORT jstring JNICALL
Java_com_chen_anative_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    pthread_t pthread1;
    pthread_create(&pthread1, nullptr, reinterpret_cast<void *(*)(void *)>(myThread), nullptr);//线程创建
    pthread_join(pthread1, nullptr);//等待线程执行完成
    LOGD("pthread_create Success");
    pthread_exit(nullptr);//线程退出
    return env->NewStringUTF(hello.c_str());
}

JNI_OnLoad

这个函数是在加载so文件加载过程中自动会去加载的一个函数,加载的会比MainActivity早,我们可以去构造这个函数,去指定需要执行代码

JNIEXPORT jint JNI_OnLoad(JavaVM *vm ,void *reserved)
{
    JNIEnv *env = nullptr;
    if(vm->GetEnv((void **)&env,JNI_VERSION_1_6) != JNI_OK)
    {
        LOGD("GETevn failed");
        return -1;
    }
    LOGD("this is JNI_OnLOAD");
    return  JNI_VERSION_1_6;
}
//这个函数的返回值是JNI的版本,可以通过比对获取Env的版本查看是否符合JNI的版本

JavaVM的结构:

上面的vm类下的GetEnv函数去获取了jni中用于交互的函数,并且比较了对于的jni的版本号,我们来看看JavaVM结构体的结构体形式

c++版:
struct _JavaVM {
    const struct JNIInvokeInterface* functions;
#if defined(__cplusplus)
    jint DestroyJavaVM()
    { return functions->DestroyJavaVM(this); }
    jint AttachCurrentThread(JNIEnv** p_env, void* thr_args)
    { return functions->AttachCurrentThread(this, p_env, thr_args); }
    jint DetachCurrentThread()
    { return functions->DetachCurrentThread(this); }
    jint GetEnv(void** env, jint version)
    { return functions->GetEnv(this, env, version); }
    jint AttachCurrentThreadAsDaemon(JNIEnv** p_env, void* thr_args)
    { return functions->AttachCurrentThreadAsDaemon(this, p_env, thr_args); }
#endif /*__cplusplus*/
c版:
struct JNIInvokeInterface {
    void*       reserved0;
    void*       reserved1;
    void*       reserved2;

    jint        (*DestroyJavaVM)(JavaVM*);
    jint        (*AttachCurrentThread)(JavaVM*, JNIEnv**, void*);
    jint        (*DetachCurrentThread)(JavaVM*);
    jint        (*GetEnv)(JavaVM*, void**, jint);
    jint        (*AttachCurrentThreadAsDaemon)(JavaVM*, JNIEnv**, void*);

通过以上我们其实可以看到的是c++版的参数少于c版的,其实可以看出来是因为c++一开始去创建了c版的JNIInvokeInterface函数的对象,然后通过指针去找到对应的函数的

SO中函数注册的过程

so中函数是如何进行注册使用的?其实是通过的JNI_OnLoad方法中进行函数注册加载的

extern "C"
JNIEXPORT jstring JNICALL
Java_com_chen_javaandso_MainActivity_stringFromJNI1(JNIEnv *env, jobject thiz,jint a ,jstring b) {
    // TODO: implement stringFromJNI1()

    return env->NewStringUTF(" ");
}

JNIEXPORT jint JNI_OnLoad(JavaVM *vm ,void *reserved)
{
    JNIEnv *env = nullptr;
    if(vm->GetEnv((void **)&env,JNI_VERSION_1_6) != JNI_OK)
    {
        LOGD("GETevn failed");
        return -1;
    }
//    typedef struct {
//        const char* name;
//        const char* signature;
//        void*       fnPtr;
//    }
    jclass Mainactivity = env->FindClass("com/chen/javaandso/MainActivity");
    JNINativeMethod method[]=
            {
                    {"stringFromJNI1","(ILjava/lang/String;Ljava/lang/String",
                     (void*)Java_com_chen_javaandso_MainActivity_stringFromJNI}
    };
    env->RegisterNatives(Mainactivity,method,(int)sizeof(method)/sizeof(JNINativeInterface));

    return  JNI_VERSION_1_6;
}

这里我们可以看到的是,先获取了jclass类的函数对象,通过JNINativeMethod的method数组进行的函数注册

//    typedef struct {
//        const char* name;
//        const char* signature;
//        void*       fnPtr;
//    }

这里注册的是name——>函数名 signature——>对应参数以及返回值类型 fnPtr——>对应的函数指针

多个cpp文件编译成一个so文件

创建了多个cpp文件之后,假如要编译成一个so文件,我们知道编译使用NDK链接器到所有的cpp文件为一个so需要的是CMake,所以首先是CMakeList的文件包含cpp文件

add_library(${CMAKE_PROJECT_NAME} SHARED
        # List C/C++ source files with relative paths to this CMakeLists.txt.
        native-lib.cpp
        main.cpp
)

以及在使用过程的函数声明

void test();
extern "C" JNIEXPORT jstring JNICALL
Java_com_chen_javaandso_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */){
    std::string hello = "Hello from C++";
    test();
    return env->NewStringUTF(hello.c_str());
}

编译多个so文件

编译多个so文件需要多个CMakeList的配置文件去配置不同的so文件

add_library(
        chen_chen_chen1//so名
        SHARED
        # List C/C++ source files with relative paths to this CMakeLists.txt.
        native-lib.cpp//对应so所使用的cpp文件
        main.cpp
)
add_library(
        chen_chen_chen2
        SHARED
        # List C/C++ source files with relative paths to this CMakeLists.txt.
        demo.cpp
)

# Specifies libraries CMake should link to your target library. You
# can link libraries from various origins, such as libraries defined in this
# build script, prebuilt third-party libraries, or Android system libraries.
target_link_libraries(
        chen_chen_chen1//链接所对应的so名
        # List libraries link to the target library
        android//以下就是对应的需要进行链接的库android和log库
        log)

target_link_libraries(
        chen_chen_chen2
        # List libraries link to the target library
        android
        log)

SO直接的相互调用

so之间的相互调用利用的是在按照包的路径下进行的so的访问,而进行的相互调用

首先是获取包的路径以下是Kotlin语法(java语言包装下的语言)

fun getPath(cxt: Context): String? {//这里的String? 是返回String或者null数据
    val pm = cxt.packageManager//获取包管理器
    val pkgList = pm.getInstalledPackages(0)//得到已经下载好的包名
    if (pkgList == null || pkgList.size == 0) return null
    for (pi in pkgList) {//增强for循环遍历包名
        if (pi.applicationInfo.nativeLibraryDir.startsWith("/data/app/")//查看对应的包名中的app是否有"/data/app/"字符串
            && pi.packageName.startsWith("com.chen.javaandso")//包名是否包含"com.chen.javaandso"
        ) {
//Log.e("xiaojianbang", pi.applicationInfo.nativeLibraryDir);
            return pi.applicationInfo.nativeLibraryDir//是就直接返回path了
        }
    }
    return null
}

Mainactivity函数

class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState);
        Log.d("Tag", "getPath:"+getPath(getApplicationContext()));//调用getApplicationContext()来获取需要的Context类
        val path: String? = getPath(getApplicationContext()+"/libchen_chen_chen2.so");//获取path
        val stringVar: String = path as String;//转字符串,假如是null就会抛出异常
        stringVar+="/libchen_chen_chen2.so"
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        Log.d("tag","oncreate");
        // Example of a call to a native method
        binding.sampleText.text = stringFromJNI(stringVar);
    }
    private external fun stringFromJNI(string: String): String

然后直接交给cpp里面去实现

extern "C" JNIEXPORT jstring JNICALL
Java_com_chen_javaandso_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */,
        jstring path) {
    std::string hello = "Hello from C++";
    const char* cpath = env->GetStringUTFChars(path, nullptr);//这里是java转c++的转char函数
    if (cpath == nullptr) {
        // 处理 JNIEnv::GetStringUTFChars 返回 nullptr 的情况
        return nullptr;
    }
    void* soinfo = dlopen(cpath, RTLD_NOW);//这里去获取对应路径下的so文件的句柄
    if (soinfo == nullptr) {
        // 处理 dlopen 失败的情况
        __android_log_print(ANDROID_LOG_ERROR, "JNI", "Failed to load library: %s", dlerror());
        env->ReleaseStringUTFChars(path, cpath);
        return nullptr;
    }
    //由于我们是通过的对应路径去查找的so文件的函数名,所以这里创建了一个函数指针去得到对应的函数句柄,然后直接调用
    void (*def)(char*) = reinterpret_cast<void (*)(char*)>(dlsym(soinfo, "_Z7seconedv"));//直接去利用句柄去实现查找对应的函数,这里的函数名由于没有加上extern "C",所以会在执行过程中改变
    if (def == nullptr) {
        // 处理 dlsym 找不到符号的情况
        __android_log_print(ANDROID_LOG_ERROR, "JNI", "Failed to find symbol: %s", dlerror());
        dlclose(soinfo);
        env->ReleaseStringUTFChars(path, cpath);
        return nullptr;
    }
    def(nullptr);
    dlclose(soinfo); // 关闭动态库句柄
    env->ReleaseStringUTFChars(path, cpath); // 释放 JNI 字符串
    return env->NewStringUTF(hello.c_str());
}

这里是可以成功找到对应的so以及so下对应的函数的

假如我们想要直接进行so之间的交互也可以通过在创建so的时候,用链接器将两个so提前链接起来

这样通过声明就可以之间进行不同so的函数之间的使用

add_library(
        firstso
        SHARED
        # List C/C++ source files with relative paths to this CMakeLists.txt.
        senconedso
        native-lib.cpp
        main.cpp
)
add_library(
        senconedso
        SHARED
        # List C/C++ source files with relative paths to this CMakeLists.txt.
        demo.cpp
)

通过JNI去创建Java对象

我们现在使用JNI大多数是为了将c++和java代码进行交互,所以这里其实是用c++的语法来实现创建java的对象,这里的方式和反射的意思差不多,通过找到对应的类和方法,去调用对应的构造函数,实现对象的创建

使用NewObject创建(直接调用构造函数)

    jclass claszz =  env->FindClass("com/chen/c/NDKDemo");
    jmethodID methodID = env->GetMethodID(claszz,"<init>","()V");
    jobject jobject1 =  env->NewObject(claszz,methodID);
    LOGD("Object:%p",jobject1);

使用AllocObject创建(通过先开辟空间再调用构造函数)

    jclass clazz1 = env->FindClass("com/chen/c/NDKDemo");
    jmethodID methodID2 = env->GetMethodID(clazz1, "<init>", "(Ljava/lang/String;I)V");
    jobject ReflectDemoObj2 = env->AllocObject(clazz1);
    jstring jstr = env->NewStringUTF("from jni str");
    env->CallNonvirtualVoidMethod(ReflectDemoObj2, clazz1, methodID2, jstr, 100);

通过JNI去访问java类的属性

获取静态字段的属性

    //获取静态字段
    jfieldID static_FieldID =
            env->GetStaticFieldID(claszz,"privateStaticStringField","Ljava/lang/String;");
    jstring static_string = static_cast<jstring>(env->GetStaticObjectField(claszz, static_FieldID));
    const char* String1 =  env->GetStringUTFChars(static_string, nullptr);
    LOGD("privateStaticString: %s", String1);

获取对象字段

    jfieldID fieldID1 = env->GetFieldID(claszz,"publicStringField","Ljava/lang/String;");
    jstring object_string = static_cast<jstring>(env->GetObjectField(jobject1, fieldID1));
    const char* object_field = env->GetStringUTFChars(object_string, nullptr);
    LOGD("publicStringField: %s", object_string);
    env->ReleaseStringUTFChars(object_string, object_field);

修改字段

    jfieldID static_FieldID =
            env->GetStaticFieldID(claszz,"privateStaticStringField","Ljava/lang/String;");
    env->SetStaticObjectField(claszz,static_FieldID,env->NewStringUTF("xiaojianbang"));
    jstring static_string = static_cast<jstring>(env->GetStaticObjectField(claszz, static_FieldID));
    const char* String1 =  env->GetStringUTFChars(static_string, nullptr);
    LOGD("privateStaticString: %s", String1);

对于字符数组的修改和打印

    //获取数组字段 ID
    jfieldID bytearray = env->GetFieldID(claszz,"byteArray","[B");
    jbyteArray jbyteArray1 = static_cast<jbyteArray>(env->GetObjectField(jobject1, bytearray));
    int _byteArrayLength = env->GetArrayLength(jbyteArray1);
//    private byte[] byteArray = new byte[]{1,2,3,4,5,6,7,8,9,10};
//    获取数组字段 ID
//    修改数组字段
    char javaByte[10];
    for(int i = 0; i < 10; i++){
        javaByte[i] = static_cast<char>(100 - i);
    }//创建数组
    const jbyte *java_array = reinterpret_cast<const jbyte *>(javaByte);//转为jbyte类型,用于之后进行SetByteArrayRegion
    env->SetByteArrayRegion(jbyteArray1, 0, _byteArrayLength, java_array);
//    获取数组字段
    jbyteArray byteArray = static_cast<jbyteArray>(env->GetObjectField(jobject1, bytearray));
    _byteArrayLength = env->GetArrayLength(byteArray);
    char* str = reinterpret_cast<char *>(env->GetByteArrayElements(byteArray, nullptr));
    for(int i = 0; i< _byteArrayLength; i++){
        LOGD("str[%d]=%d", i, str[i]);
    }
//    env->ReleaseByteArrayElements(jbyteArray, reinterpret_cast<jbyte *>(cbyteArray), 0);

通过JNI去访问Java方法

调用静态函数

jclass ReflectDemoClazz = env->FindClass("com/xiaojianbang/ndk/NDKDemo");
jmethodID publicStaticFuncID =
env->GetStaticMethodID(ReflectDemoClazz, "publicStaticFunc", "()V");
env->CallStaticVoidMethod(ReflectDemoClazz, publicStaticFuncID);

调用对象函数

这里注意一下,由于我们是去调用的是对象函数,也就是说我们的调用的是对象的方法,那么method对应的就不再是jclass了而应该是对应初始化了对象之后的对象了

    jmethodID privateFuncID =
            env->GetMethodID(claszz, "privateFunc", "(Ljava/lang/String;I)Ljava/lang/String;");
    //"privateFunc"
    jmethodID ReflectDemoInit =
            env->GetMethodID(claszz, "<init>", "(Ljava/lang/String;)V");//先初始化对象
    //"<init>"
    jstring str1 = env->NewStringUTF("this is from NDK");
    jobject ReflectDemoObj = env->NewObject(claszz, ReflectDemoInit, str1);//这里就是使用的method就是初始了对象的method

    jstring str2 = env->NewStringUTF("this is from JNI");
    jstring retval =
            static_cast<jstring>(env->CallObjectMethod(ReflectDemoObj, privateFuncID, str2, 1000));

allObjectMethodA 的使用 :传联合体args

jvalue args[2];
string str2 = env->NewStringUTF("this is from JNI");
args[0].l = str2;
args[1].i = 1000;
jstring retval =
static_cast<jstring>(env->CallObjectMethodA(ReflectDemoObj, privateFuncID, args));
const char* cpp_retval = env->GetStringUTFChars(retval, nullptr);
LOGD("cpp_retval: %s", cpp_retval);
env->ReleaseStringUTFChars(retval, cpp_retval);

参数是数组,返回值是数组的函数的方法调用

    private static int[] privateStaticFunc(String[] str){
        StringBuilder retval = new StringBuilder();
        for(String i : str) {
            retval.append(i);
        }
        Log.d("xiaojianbang", "this is privateStaticFunc: " + retval.toString());
        return new int[]{0,1,2,3,4,5,6,7,8,9};
    }

这里首先要创建对应的需要的String str作为参数,传入的jobjectArray需要用env->SetObjectArrayElement来设置

    jclass claszz =  env->FindClass("com/chen/c/NDKDemo");
    jmethodID methodID = env->GetMethodID(claszz,"<init>","()V");
    jmethodID privateStaticFunc = env->GetStaticMethodID(claszz,"privateStaticFunc","([Ljava/lang/String;)[I");
//下面参数设定
    jclass StringClazz = env->FindClass("java/lang/String");
    jobjectArray StringArr = env->NewObjectArray(3, StringClazz, nullptr);
    for(int i = 0; i < 3; i++){
        jstring str3 = env->NewStringUTF("NDK");
        env->SetObjectArrayElement(StringArr, i, str3);
    }
//传参
    jintArray int_array = static_cast<jintArray>(env->CallStaticObjectMethod(claszz,
                                                                             privateStaticFunc,StringArr));
    int *c_array =  env->GetIntArrayElements(int_array, nullptr);
    LOGD("cintArr[0]=%d", c_array[0]);

NDK中的内存管理(局部引用)

  1. 局部引用:大部分的jni函数调用之后的返回值都是局部引用,这种局部引用在整个函数体执行完成之后就会自动的释放到,即使是调用的全局变量,在局部引用赋值之后也会被释放掉,不能由局部引用去传递。同时,局部引用的数目也是有限的,需要去使用env——>DeleteLocalRef来删除局部引用

  2. 同时可以使用env——>PushLocalFrame(num)和env——>PopLocalFrame(nullptr)来去自动删除局部引用

  3. 同时假如我们想要去实现全局引用就要使用env->NewGlobalRef

        jclass claszz =  env->FindClass("com/chen/javaandso/NDKDemo");
        NDKClass = static_cast<jclass>(env->NewGlobalRef(claszz));
    

子线程中获取java类

  1. 直接Java的系统类就可以直接去使用Java的包名进行直接引用
void myThread() {
    JNIEnv * env = nullptr;
    jclass MainactivityClaszz = env->FindClass("java/lang/String");
    LOGD("MainactivityClaszz:%p",&MainactivityClaszz);
    }
  1. 通过在构造全局变量,在JNI_Onload里面去进行Findclass之后直接调用全局变量,直接去
JNIEXPORT jint JNI_OnLoad(JavaVM *vm,void *reserved)
{
    globalVM =vm;
    JNIEnv *env = nullptr;
    vm->GetEnv((void **)&env,JNI_VERSION_1_6);
    jclass mainativities_temp =  env->FindClass("com/chen/anative/MainActivity");
    mainativities_temp= static_cast<jclass>(env->NewGlobalRef(mainativities_temp));
}
  1. 由于我们在子线程中的classloader会变换,会导致使用不同的classloader分析出的同一个函数,结果却是不一样的,所有先要进行classloader的获取,在JNI_Onload中先获取全局变量的getclassLoader,获取loader
JNIEXPORT jint JNI_OnLoad(JavaVM *vm,void *reserved)
{
    globalVM =vm;
    JNIEnv *env = nullptr;
    vm->GetEnv((void **)&env,JNI_VERSION_1_6);
    jclass MainActivity =  env->FindClass("com/chen/anative/MainActivity");
    jclass javaClass =  env->FindClass("java/lang/Class");
    jmethodID methodId_getClassLoader = env->GetMethodID(javaClass,"getClassLoader", "()Ljava/lang/ClassLoader;");
    jobject tempclass =  env->CallObjectMethod(MainActivity,methodId_getClassLoader);
    get_classloader = env->NewGlobalRef(tempclass);
}

同时在子线程中利用loadclass加载classloader,通过调用来获取jclass的主线程类

void myThread() {
    JNIEnv * env = nullptr;
    jclass class_classloader = env->FindClass("java/lang/ClassLoader");
    jmethodID methodid_loadclass = env->GetMethodID(class_classloader,"loadClass", "(Ljava/lang/String;)Ljava/lang/Class;");
    jclass Mainactivity = static_cast<jclass>(env->CallObjectMethod(get_classloader,
                                                                    methodid_loadclass,
                                                                    env->NewStringUTF(
                                                                            "com.chen.anative.MainActivity")));
    if(Mainactivity == nullptr)
    {
        LOGD("NULLPTR");
        pthread_exit(0);
    }
    jmethodID JNI_methodID =  env->GetMethodID(Mainactivity,"stringFromJNI","()Ljava/lang/String");
    LOGD("myThread env->GetMethodID:%p",JNI_methodID);
}

实现子线程获取classloader来调用获取主线程类

init和init_array

init和init_array都是程序中比JNI_Onload还要加载的更早的函数,这里可能会存放so层的代码解码以及去混淆的程序,从而在程序执行到主函数时可以直接获取解密之后的代码。

init初始化:

extern "C" void _init()
{
    LOGD("extern \"C\" void _init()");
}

init_array初始化:

__attribute__ ((constructor(180), visibility("hidden"))) void initArrayTest1(){
    LOGD("initArrayTest1");
}
__attribute__ ((constructor(150))) void initArrayTest2(){
    LOGD("initArrayTest2");
}
__attribute__ ((constructor(101))) void initArrayTest3(){

    LOGD("initArrayTest3");
}

这里的(constructor(150))是构造函数,且最后的数字越小,执行时刻越早,但是前一百的数字大多数是由系统调用的,所有一般我们以100往后的数字开始使用, visibility("hidden")这里参数表示可见度为隐藏

onCreateNative化

我们知道要是我们的代码直接在java层就直接能够进行反编译读取那么是不安全的,所有我们要做的就是native化,也就是将关键代码存到我们的so层里面去,尽可能的保护起来

这里我们先演示一下native:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

结果:

extern "C" JNIEXPORT void JNICALL
Java_com_chen_anative_MainActivity_onCreate(
        JNIEnv * env,
        jobject MainActivity thiz,
        jobject Bundle saved_instance_state)
{
//    super.onCreate(savedInstanceState)
    jclass AppCompatActivityclazz = env->FindClass("androidx/appcompat/app/AppCompatActivity");
    //找到super对于的类androidx/appcompat/app/AppCompatActivity
    jmethodID Oncreate_method =  env->GetMethodID(AppCompatActivityclazz,"onCreate", "(Landroid/os/Bundle;Landroid/os/PersistableBundle;)V");
    //找到了类之后就要去找方法(因为oncreate是super类的方法所有先找类再找方法)
    env->CallNonvirtualVoidMethod(thiz,AppCompatActivityclazz,Oncreate_method,saved_instance_state);
    //这里是子类调用父类,所以使用的是Nonvirtual,第一个参数就是父类
}

这里最终实现的过程就是 super.onCreate(savedInstanceState),只能转为了native上,进行了保护

其实这样的转换是很简单的,和frida的使用有点像

binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
// Example of a call to a native method
binding.sampleText.text = stringFromJNI()

所以其实全部都能进行native了

jclass Activity  = env->FindClass("android/app/Activity");
jmethodID LayoutInflater = env->GetMethodID(Activity,"getLayoutInflater", "()Landroid/view/LayoutInflater;");
jclass ActivityMainBinding  = env->FindClass("com/chen/anative/databinding/ActivityMainBinding");
jmethodID inflateID= env->GetStaticMethodID(ActivityMainBinding,"inflate","(Landroid/view/LayoutInflater;)Lcom/chen/anative/databinding/ActivityMainBinding;");
jobject inflate_result = env->CallStaticObjectMethod(ActivityMainBinding,inflateID);

这一大串就实现了 binding = ActivityMainBinding.inflate(layoutInflater) 并且将ActivityMainBinding类型的返回值赋值给了inflate_result。

标签:NDK,String,void,nullptr,开发,env,chen,JNI
From: https://www.cnblogs.com/ovo-fisherman/p/18412571

相关文章

  • Python网页应用开发神器Dash 2.18.1稳定版本来啦
    本文示例代码已上传至我的Github仓库:https://github.com/CNFeffery/dash-masterGitee同步仓库地址:https://gitee.com/cnfeffery/dash-master大家好我是费老师,上周Dash发布了2.18.0新版本,并于今天发布了可稳定使用的2.18.1版本(自古.1版本最稳✌),今天的文章中就将针对2.18.1......
  • 基于Java部门办公网站系统的设计与开发的计算机毕设
    摘 要本次课程设计主要是针对于学校教研信息的登记管理归档根据实际工作流程做出适合实际工作的,能够减少工作流程的工作量,有效的提高工作效率的网站系统。本次设计在开始的前台界面设计时主要是使用DIV+CSS布局样式,设计时能灵活准确的定位每一个模块的位置,在程序语言上使用的是ja......
  • 第22篇 如何部署gitLab进行开发版本控制
    1.版本控制工具常用的版本管理工具有:github,gitlab,subversion官网:https://about.gitlab.com/国内镜像:https://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/yum/版本管理,系统上线流程:开发代码(开发人员)测试(测试人员)发布(运维人员)测试(测试人员)发邮件申请发布(开发人员)邮件发给开......
  • 电子体温计芯片方案设计与开发
     电子体温计是一种以电子电路为核心的温度测量仪器,具有测量速度快、温度分辨率高、测量结果准确可靠、使用方便等特点。电子体温计主要用于检测人体体温,可分为接触式和非接触式两大类。传统的体温计是通过水银温度计来测温的,这种温度计的使用方法是:将体温计的水银柱末端浸入液体......
  • A178-基于java+springboot+vue开发的租房网站(源码+数据库+LW+部署文档)
    功能介绍平台采用B/S结构,后端采用主流的Springboot框架进行开发,前端采用主流的Vue.js进行开发。整个平台包括前台和后台两个部分。前台功能包括:首页、房屋详情页、门票订单、用户中心模块。后台功能包括:总览、订单管理、房屋管理、分类管理、设施管理、评论管理、用户管理、......
  • Java 开发中锁的选择与使用
    Java开发中锁的选择与使用1.引言2.Java中的锁机制3.synchronized关键字示例好处注意点4.ReentrantLock类示例好处注意点5.ReadWriteLock接口示例好处注意点6.Atomic类示例好处注意点7.锁的选择与对比1.引言在并发编程中,锁是一种常见的机制,用于......
  • 电子体温计芯片方案设计与开发
    电子体温计是一种以电子电路为核心的温度测量仪器,具有测量速度快、温度分辨率高、测量结果准确可靠、使用方便等特点。电子体温计主要用于检测人体体温,可分为接触式和非接触式两大类。传统的体温计是通过水银温度计来测温的,这种温度计的使用方法是:将体温计的水银柱末端浸入液......
  • 旅游网站开发:SpringBoot框架高级应用
    第五章系统的实现5.1登录界面登录窗口,用户通过登录窗口可以进行登录或注册。还没注册的用户可以进行填写用户名、密码进行注册操作,如图5-1所示,图5-2登录窗口界面5.2管理员功能模块用户登录成功后,可以进行查看个人中心、用户管理、路线分类管理、旅游路线管理、最新路......
  • 经典前端+后端+表头+表身的开发实战参考简易模板【珍藏】
    前端部分(Vue3+ElementPlus)1.修改MPS002HList.vue(主生产计划列表)a.添加查询表单在模板中添加查询表单,包含产品料号、品名、规格和年月的输入项。<template><div><!--查询表单--><el-form:inline="true":model="filters"class="demo-form-inline&qu......
  • 5 敏捷开发
    一、敏捷开发的特点敏捷开发是一种以人为核心、迭代、循序渐进的软件开发方法。其特点主要包括:快速响应变化:敏捷开发强调快速响应需求变更,通过短周期迭代和频繁交付,确保软件能够迅速适应市场需求的变化。持续交付价值:通过每个迭代周期交付可工作的软件,敏捷开发能够持续向用......