Android JNI笔记

作者: R7_Perfect | 来源:发表于2019-08-01 15:40 被阅读11次

    C语言不跨平台:

    ARMv5——armeabi 第5代、第6代的ARM处理器,早期的手机用的比较多
    ARMv7 ——armeabi-v7a 第7代及以上的 ARM 处理器。2011年以后的生产的大部分Android设备都使用它。
    ARMv8——arm64- v8a 第8代、64位ARM处理器,很少设备,三星 Galaxy S6是其中之一。
    x86——x86 平板、模拟器用得比较多。
    MIPS ——mips
    MIPS64——mips64
    x86_64——x86_64 64位的平板。

    JNI的命名规则

    JNIExport jstring JNICALL Java_com_example_hellojni_MainActivity_stringFromJNI( JNIEnv* env,jobject thiz ) 
    

    函数例子:

    jdouble Java_pkg_Cls_f__ILjava_lang_String_2 (JNIEnv *env, jobject obj, jint i, jstring s)
    {
         const char *str = (*env)->GetStringUTFChars(env, s, 0); 
         (*env)->ReleaseStringUTFChars(env, s, str); 
         return 10;
    }
    

    *env:一个接口指针, JNIEnv是一个线程相关的结构体,该结构体代表了Java在本线程的执行环境
    obj:在本地方法中声明的对象引用
    i和s:用于传递的参数

    JNIEnv的作用
    调用Java 函数:JNIEnv代表了Java执行环境,能够使用JNIEnv调用Java中的代码
    操作Java代码:Java对象传入JNI层就是jobject对象,需要使用JNIEnv来操作这个Java对象

    与JNIEnv相关的常用函数
    创建Java中的对象

    jobject NewObject(JNIEnv *env, jclass clazz,jmethodID methodID, ...):
    jobject NewObjectA(JNIEnv *env, jclass clazz,jmethodID methodID, const jvalue *args):
    jobject NewObjectV(JNIEnv *env, jclass clazz,jmethodID methodID, va_list args):
    

    创建Java类中的String对象

    jstring NewString(JNIEnv *env, const jchar *unicodeChars,jsize len):
    

    创建类型为基本类型PrimitiveType的数组

    ArrayType New<PrimitiveType>Array(JNIEnv *env, jsize length);
    指定一个长度然后返回相应的Java基本类型的数组
    

    创建类型为elementClass的数组

    jobjectArray NewObjectArray(JNIEnv *env, jsize length,
    jclass elementClass, jobject initialElement);
    

    获取数组中某个位置的元素

    jobject GetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index);
    

    获取数组的长度

    jsize GetArrayLength(JNIEnv *env, jarray array);
    

    添加并编写Android.mk文件

    LOCAL_PATH := $(call my-dir)
    
    include $(CLEAR_VARS)
    
    LOCAL_MODULE    := ndkdemotest-jni
    
    LOCAL_SRC_FILES := ndkdemotest.c
    
    include $(BUILD_SHARED_LIBRARY)
    

    LOCAL_PATH := $(call my-dir):每个Android.mk文件必须以定义开始。它用于在开发tree中查找源文件。宏my-dir则由Build System 提供。返回包含Android.mk目录路径。

    include $(CLEAR_VARS) :CLEAR_VARS变量由Build System提供。并指向一个指定的GNU Makefile,由它负责清理很多LOCAL_xxx。例如LOCAL_MODULE,LOCAL_SRC_FILES,LOCAL_STATIC_LIBRARIES等等。但不是清理LOCAL_PATH。这个清理是必须的,因为所有的编译控制文件由同一个GNU Make解析和执行,其变量是全局的。所以清理后才能便面相互影响。

    LOCAL_MODULE := ndkdemotest-jni:LOCAL_MODULE模块必须定义,以表示Android.mk中的每一个模块。名字必须唯一且不包含空格。Build System 会自动添加适当的前缀和后缀。例如,demo,要生成动态库,则生成libdemo.so。但请注意:如果模块名字被定义为libabd,则生成libabc.so。不再添加前缀。

    LOCAL_SRC_FILES := ndkdemotest.c:这行代码表示将要打包的C/C++源码。不必列出头文件,build System 会自动帮我们找出依赖文件。缺省的C++ 源码的扩展名为.cpp。

    include (BUILD_SHARED_LIBRARY):BUILD_SHARED_LIBRARY是Build System提供的一个变量,指向一个GUN Makefile Script。它负责收集自从上次调用include(CLEAR_VARS)后的所有LOCAL_xxxxinx。并决定编译什么类型

    BUILD_STATIC_LIBRARY:编译为静态库
    BUILD_SHARED_LIBRARY:编译为动态库
    BUILD_EXECUTABLE:编译为Native C 可执行程序
    BUILD_PREBUILT:该模块已经预先编译

    修改相应的配置文件

    android {
        compileSdkVersion 26
        defaultConfig {
    
            ndk{
                moduleName "ndkdemotest-jni"
                abiFilters "armeabi", "armeabi-v7a", "x86"
    
            }
        }
    
            externalNativeBuild {
                ndkBuild {
                    path 'src/main/jni/Android.mk'
                }
            }
            sourceSets.main {
                jni.srcDirs = []
                jniLibs.srcDirs = ['src/main/jniLibs']
            }
        }
    }
    

    通过CMake工具

    build.gradle里面的代码:

    android {
        defaultConfig {
            externalNativeBuild {
                cmake {
                    cppFlags ""
                }
            }
        }
        externalNativeBuild {
            cmake {
                path "CMakeLists.txt"
            }
        }
    }
    

    defaultConfig外面的 externalNativeBuild - cmake,指明了 CMakeList.txt 的路径;
    defaultConfig 里面的 externalNativeBuild - cmake,主要填写 CMake 的命令参数。即由 arguments 中的参数最后转化成一个可执行的 CMake 的命令,可以在 app/externalNativeBuild/cmake/debug/{abi}/cmake_build_command.txt中查到。

    CMakeLists.txt

    cmake_minimum_required(VERSION 3.4.1)
    
    add_library( # Sets the name of the library.
                 native-lib
    
                 # Sets the library as a shared library.
                 SHARED
    
                 # Provides a relative path to your source file(s).
                 src/main/cpp/native-lib.cpp )
    
    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 )
    
    
    target_link_libraries( # Specifies the target library.
                           native-lib
    
                           # Links the target library to the log library
                           # included in the NDK.
                           ${log-lib} )
    

    cmake_minimum_required(VERSION 3.4.1):指定CMake的最小版本
    add_library:创建一个静态或者动态库,并提供其关联的源文件路径,开发者可以定义多个库,CMake会自动去构建它们。Gradle可以自动将它们打包进APK中。
    第一个参数——native-lib:是库的名称
    第二个参数——SHARED:是库的类别,是动态的还是静态的
    第三个参数——src/main/cpp/native-lib.cpp:是库的源文件的路径

    find_library:找到一个预编译的库,并作为一个变量保存起来。由于CMake在搜索库路径的时候会包含系统库,并且CMake会检查它自己之前编译的库的名字,所以开发者需要保证开发者自行添加的库的名字的独特性。
    第一个参数——log-lib:设置路径变量的名称
    第一个参数—— log:指定NDK库的名子,这样CMake就可以找到这个库

    target_link_libraries:指定CMake链接到目标库。开发者可以链接多个库,比如开发者可以在此定义库的构建脚本,并且预编译第三方库或者系统库。
    第一个参数——native-lib:指定的目标库
    第一个参数——${log-lib}:将目标库链接到NDK中的日志库,


    注册native函数

    静态注册native函数

    public class JniDemo1{
           static {
                 System.loadLibrary("samplelib_jni");
            }
    
            private native void nativeMethod();
    }
    

    动态注册native函数

    #include <jni.h>
    #include "Log4Android.h"
    #include <stdio.h>
    #include <stdlib.h>
    
    using namespace std;
    
    #ifdef __cplusplus
    extern "C" {
    #endif
    
    static const char *className = "com/gebilaolitou/jnidemo/JNIDemo2";
    
    static void sayHello(JNIEnv *env, jobject, jlong handle) {
        LOGI("JNI", "native: say hello ###");
    }
    
    static JNINativeMethod gJni_Methods_table[] = {
        {"sayHello", "(J)V", (void*)sayHello},
    };
    
    static int jniRegisterNativeMethods(JNIEnv* env, const char* className,
        const JNINativeMethod* gMethods, int numMethods)
    {
        jclass clazz;
    
        LOGI("JNI","Registering %s natives\n", className);
        clazz = (env)->FindClass( className);
        if (clazz == NULL) {
            LOGE("JNI","Native registration unable to find class '%s'\n", className);
            return -1;
        }
    
        int result = 0;
        if ((env)->RegisterNatives(clazz, gJni_Methods_table, numMethods) < 0) {
            LOGE("JNI","RegisterNatives failed for '%s'\n", className);
            result = -1;
        }
    
        (env)->DeleteLocalRef(clazz);
        return result;
    }
    
    jint JNI_OnLoad(JavaVM* vm, void* reserved){
        LOGI("JNI", "enter jni_onload");
    
        JNIEnv* env = NULL;
        jint result = -1;
    
        if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
            return result;
        }
    
        jniRegisterNativeMethods(env, className, gJni_Methods_table, sizeof(gJni_Methods_table) / sizeof(JNINativeMethod));
    
        return JNI_VERSION_1_4;
    }
    
    #ifdef __cplusplus
    }
    #endif
    

    JNI中的签名

    为什么JNI中突然多出了一个概念叫"签名"?
    因为Java是支持函数重载的,也就是说,可以定义相同方法名,但是不同参数的方法,然后Java根据其不同的参数,找到其对应的实现的方法。这样是很好,所以说JNI肯定要支持的,那JNI要怎么支持那,如果仅仅是根据函数名,没有办法找到重载的函数的,所以为了解决这个问题,JNI就衍生了一个概念——"签名",即将参数类型和返回值类型的组合。如果拥有一个该函数的签名信息和这个函数的函数名,我们就可以顺序的找到对应的Java层中的函数了。

    如果查看类中的方法的签名

    javap -s -p MainActivity.class
    
    Compiled from "MainActivity.java"
    public class com.example.hellojni.MainActivity extends android.app.Activity {
      static {};
        Signature: ()V
    
      public com.example.hellojni.MainActivity();
        Signature: ()V
    
      protected void onCreate(android.os.Bundle);
        Signature: (Landroid/os/Bundle;)V
    
      public boolean onCreateOptionsMenu(android.view.Menu);
        Signature: (Landroid/view/Menu;)Z
    
      public native java.lang.String stringFromJNI(); //native 方法
        Signature: ()Ljava/lang/String;  //签名
    
      public native int max(int, int); //native 方法
        Signature: (II)I    //签名
    }
    

    JNI规范定义的函数签名信息

    Z boolean
    B byte
    C char
    S short
    I int
    J long
    F float
    D double
    V void
    [签名 数组
    [i int[]
    [Ljava/lang/Object String[]

    native代码反调用Java层代码

    获取Class对象

    jclass jcl_string=env->FindClass("java/lang/String");
    

    获取属性方法

    jfieldID GetFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
    jmethodID GetMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
    jfieldID GetStaticFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
    jmethodID GetStaticMethodID(JNIEnv *env, jclass clazz,const char *name, const char *sig);
    

    构造一个对象

    jobject NewObject(jclass clazz, jmethodID methodID, ...)
    jmethodID mid = (*env)->GetMethodID(env, cls, "<init>", "()V");
    obj = (*env)->NewObject(env, cls, mid);
    

    相关文章

      网友评论

        本文标题:Android JNI笔记

        本文链接:https://www.haomeiwen.com/subject/axwqdctx.html