Android NDK 使用JNI调用java代码,通用代码封装

本文主要介绍一下内容:

  • 配合NDK NativeActivity封装jni调用需要的环境参数
  • 获取静态和非静态java方法信息的封装
  • 调用静态和非静态java方法的封装
  • 获取apk签名的jni调用封装
首先,我们需要获取,jni最重要的环境对象。
#include <android/native_activity.h>
#include <stdbool.h>

extern ANativeActivity* nativeActivity;

static inline JNIEnv* GetEnvPtr()
{
    static JNIEnv* envPtr = NULL;

    if (envPtr == NULL)
    {
        (*nativeActivity->vm)->AttachCurrentThread(nativeActivity->vm, &envPtr, NULL);
    }

    return envPtr;
}
ANativeActivity来自于NDK直接提供的接口。需要注意的是,JNIEnv*是和线程绑定的,所以需要在同一个线程获取并使用。


接下来,我们封装一个通用的方法,通过java类的全路径获取,这个类的jclass对象。有了jclass就可以获得类的方法和属性信息。
static inline jclass GetClass(char *className)
{
    JNIEnv* envPtr                   = GetEnvPtr();
    static  jobject   classLoaderObj = NULL;
    static  jmethodID loadClassId    = NULL;

    if (classLoaderObj == NULL)
    {
        jclass    activityCls      = (*envPtr)->FindClass(envPtr, "android/app/NativeActivity");
        jclass    loaderCls        = (*envPtr)->FindClass(envPtr, "java/lang/ClassLoader");

        jmethodID getClassLoaderId = (*envPtr)->GetMethodID
                                     (
                                        envPtr,
                                        activityCls,
                                        "getClassLoader",
                                        "()Ljava/lang/ClassLoader;"
                                     );

        classLoaderObj             = (*envPtr)->CallObjectMethod(envPtr, nativeActivity->clazz, getClassLoaderId);
        loadClassId                = (*envPtr)->GetMethodID
                                     (
                                         envPtr,
                                         loaderCls,
                                         "loadClass",
                                         "(Ljava/lang/String;)Ljava/lang/Class;"
                                     );
    }

    jstring classNameStr = (*envPtr)->NewStringUTF             (envPtr, className);
    jclass  cls          = (jclass) (*envPtr)->CallObjectMethod(envPtr, classLoaderObj, loadClassId, classNameStr);

    ALogA(cls != NULL, "JniTool GetClass can not load class = %s", className);

    (*envPtr)->DeleteLocalRef(envPtr, classNameStr);

    return cls;
}
这里,我们使用了内置类android/app/NativeActivity的类加载器,来载入指定的类。



接下来,有了以上两个方法,就可以封装获取java静态和非静态方法信息函数了。有了方法信息就可以调用这个方法了。
static inline void GetJniMethodInfo(bool isStatic, char* className, char* methodName, char* paramCode, JniMethodInfo* outJniMethodInfo)
{
    JNIEnv*   envPtr   = GetEnvPtr();
    jclass    cls      = GetClass(className);

    jmethodID methodID;

    if (isStatic)
    {
        methodID = (*envPtr)->GetStaticMethodID(envPtr, cls, methodName, paramCode);
    }
    else
    {
        methodID = (*envPtr)->GetMethodID(envPtr, cls, methodName, paramCode);
    }

    ALogA
    (
        methodID != NULL,
        "JniTool %s can not get methodID, className = %s, methodName = %s, paramCode = %s",
        isStatic ? "GetStaticMethodID" : "GetMethodID",
        className,
        methodName,
        paramCode
    );

    outJniMethodInfo->envPtr   = envPtr;
    outJniMethodInfo->cls      = cls;
    outJniMethodInfo->methodID = methodID;
}


static void GetMethodInfo(char* className, char* methodName, char* paramCode, JniMethodInfo* outJniMethodInfo)
{
    GetJniMethodInfo(false, className, methodName, paramCode,outJniMethodInfo);
}


static void GetStaticMethodInfo(char* className, char* methodName, char* paramCode, JniMethodInfo* outJniMethodInfo)
{
    GetJniMethodInfo(true, className, methodName, paramCode,outJniMethodInfo);
}
这两个方法,是方便自己调用java方法的时候使用的。使用时候返回值需要自己处理,参数主要根据函数自己传入不定参数。



然后,这里我们在封装一个更加通用的方法。不仅获取了方法信息,还直接调用,获得返回值并返回。当然,也区分了调用静态方法和非静态方法。
static jvalue CallObjectMethod(jobject object, char* methodName, char* paramCode, ...)
{
    JNIEnv*   envPtr    = GetEnvPtr();
    jclass    cls       = (*envPtr)->GetObjectClass(envPtr, object);
    jmethodID methodId  = (*envPtr)->GetMethodID   (envPtr, cls, methodName, paramCode);

    ALogA
    (
        methodId != NULL,
        "JniTool CallObjectMethod can not get methodID, methodName = %s, paramCode = %s",
        methodName,
        paramCode
    );

    char* p = paramCode;

    // skip '()' to find out the return type
    while (*p != ')')
    {
        p++;
    }
    // skip ')'
    p++;

    va_list  args;
    va_start(args, paramCode);
    jvalue   value;

    switch (*p)
    {
        case 'V':
            (*envPtr)->CallVoidMethodV             (envPtr, object, methodId, args);
            break;

        case '[':
        case 'L':
            value.l = (*envPtr)->CallObjectMethodV (envPtr, object, methodId, args);
            break;

        case 'Z':
            value.z = (*envPtr)->CallBooleanMethodV(envPtr, object, methodId, args);
            break;

        case 'B':
            value.b = (*envPtr)->CallByteMethodV   (envPtr, object, methodId, args);
            break;

        case 'C':
            value.c = (*envPtr)->CallCharMethodV   (envPtr, object, methodId, args);
            break;

        case 'S':
            value.s = (*envPtr)->CallShortMethodV  (envPtr, object, methodId, args);
            break;

        case 'I':
            value.i = (*envPtr)->CallIntMethodV    (envPtr, object, methodId, args);
            break;

        case 'J':
            value.j = (*envPtr)->CallLongMethodV   (envPtr, object, methodId, args);
            break;

        case 'F':
            value.f = (*envPtr)->CallFloatMethodV  (envPtr, object, methodId, args);
            break;

        case 'D':
            value.d = (*envPtr)->CallDoubleMethodV (envPtr, object, methodId, args);
            break;

        default:
            ALogA(false, "JniTool CallObjectMethod paramCode = %s, illegal", paramCode);
    }

    va_end(args);

    return value;
}


static inline jvalue CallClassMethodV(jclass cls, char* methodName, char* paramCode, va_list args)
{
    JNIEnv*   envPtr    = GetEnvPtr();
    jmethodID methodId  = (*envPtr)->GetStaticMethodID(envPtr, cls, methodName, paramCode);

    ALogA
    (
        methodId != NULL,
        "JniTool CallClassMethodV can not get methodID, methodName = %s, paramCode = %s",
        methodName,
        paramCode
    );

    char* p = paramCode;

    // skip '()' to find out the return type
    while (*p != ')')
    {
        p++;
    }
    // skip ')'
    p++;

    jvalue value;
    switch (*p)
    {
        case 'V':
            (*envPtr)->CallStaticVoidMethodV             (envPtr, cls, methodId, args);
            break;

        case '[':
        case 'L':
            value.l = (*envPtr)->CallStaticObjectMethodV (envPtr, cls, methodId, args);
            break;

        case 'Z':
            value.z = (*envPtr)->CallStaticBooleanMethodV(envPtr, cls, methodId, args);
            break;

        case 'B':
            value.b = (*envPtr)->CallStaticByteMethodV   (envPtr, cls, methodId, args);
            break;

        case 'C':
            value.c = (*envPtr)->CallStaticCharMethodV   (envPtr, cls, methodId, args);
            break;

        case 'S':
            value.s = (*envPtr)->CallStaticShortMethodV  (envPtr, cls, methodId, args);
            break;

        case 'I':
            value.i = (*envPtr)->CallStaticIntMethodV    (envPtr, cls, methodId, args);
            break;

        case 'J':
            value.j = (*envPtr)->CallStaticLongMethodV   (envPtr, cls, methodId, args);
            break;

        case 'F':
            value.f = (*envPtr)->CallStaticFloatMethodV  (envPtr, cls, methodId, args);
            break;

        case 'D':
            value.d = (*envPtr)->CallStaticDoubleMethodV (envPtr, cls, methodId, args);
            break;

        default:
            ALogA(false, "JniTool CallClassMethod paramCode = %s, illegal", paramCode);
    }

    return value;
}


static inline jvalue CallClassMethod(jclass cls, char* methodName, char* paramCode, ...)
{
    va_list args;
    va_start(args, paramCode);
    jvalue value = CallClassMethodV(cls, methodName, paramCode, args);
    va_end(args);

    return value;
}


static jvalue CallStaticMethod(char* className, char* methodName, char* paramCode, ...)
{
    va_list args;
    va_start(args, paramCode);
    jvalue value = CallClassMethodV(GetClass(className), methodName, paramCode, args);
    va_end(args);

    return value;
}
说明几点:
  • 参数使用了变参,因为调用的java方法不确定参数个数和类型,需要依次传入。
  • 返回值当然也是不确定类型的,所以使用了jValue这个union类型存放。
  • 这两个方法,只需要给出字符串类型的,类路径,方法名,参数列表


那么最后,封装了一个获取apk签名hashcode的方法。这个是很有用的一般用来作为apk的防破解。这里也可以看到如何使用以上函数的封装。
static int GetSignHashCode()
{
    JNIEnv*     envPtr          = GetEnvPtr();
    jobject     packageManager  = CallObjectMethod
                                  (
                                      nativeActivity->clazz,
                                      "getPackageManager",
                                      "()Landroid/content/pm/PackageManager;"
                                  ).l;

    jstring      packageName    = (jstring) CallObjectMethod
                                  (
                                      nativeActivity->clazz,
                                      "getPackageName",
                                      "()Ljava/lang/String;"
                                  ).l;

    jobject      packageInfo    = CallObjectMethod
                                  (
                                      packageManager,
                                      "getPackageInfo",
                                      "(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;",
                                      packageName,
                                      64
                                  ).l;

    jclass       packageInfoCls = (*envPtr)->GetObjectClass(envPtr, packageInfo);

    jfieldID     signaturesFid  = (*envPtr)->GetFieldID
                                  (
                                      envPtr,
                                      packageInfoCls,
                                      "signatures",
                                      "[Landroid/content/pm/Signature;"
                                  );


    jobjectArray signatureArr   = (*envPtr)->GetObjectField       (envPtr, packageInfo,  signaturesFid);
    jobject      signature      = (*envPtr)->GetObjectArrayElement(envPtr, signatureArr, 0);

    return CallObjectMethod(signature, "hashCode", "()I").i;
}

可以看到,函数的实现,就是使用jni调用各种java已有的代码来实现的。当然也可以获得签名的md5值,只是我懒得写了。另外需要注意的是,jni提供的New开头的函数,都需要调用Delete开头的函数释放内存。比如,NewStringUTF,DeleteLocalRef。



  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android NDKAndroid开发工具包的一部分,它允许开发者使用C或C++语言编写原生代码,以便实现更高效和更复杂的功能。而串口通信是一种通过串口接口实现数据传输的方式,常见于嵌入式系统和电子设备之间的通信。 Android NDK提供了对串口通信的支持,开发者可以使用JNI调用C或C++代码来实现串口通信功能。通过NDKJNI接口,开发者可以调用已经封装好的串口库,或者自己编写串口通信的相关代码。在UE8B项目中,我利用Android NDK的编译器将C代码通过JNI接口与Java代码进行交互,成功实现了串口通信的功能。 在使用Android NDK进行串口通信时,需要先在NDK配置文件Android.mk中指定源文件路径、编译选项和目标库类型等。然后,使用Android Studio进行项目编译和构建。在Java代码中,通过调用JNI接口函数,可以实现与C或C++代码的交互,从而完成串口通信的功能。 对于串口通信,需要了解串口的相关知识,包括波特率、数据位、校验位、停止位等参数的设置。在具体的应用场景中,我们可以根据需要进行相应的串口设置。通过使用NDK的相关接口,我们可以在Android平台上实现串口通信功能,从而满足特定需求。 总之,Android NDK提供了一种使用C或C++语言在Android平台上实现串口通信功能的方法。通过JNI接口,我们可以将Java代码和C代码进行交互,从而实现更高效和更复杂的串口通信功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值