AndroidAndroid知识Android知识点和文章分享

Android Jni/NDK 开发入门详解

2017-11-22  本文已影响463人  AlbertHumbert

本人为初学者,文章写得不好,如有错误,请大力怼我

如何使用jni进行开发

本文主要针对Android环境进行NDK\Native\Jni开发进行介绍

使用2.2版本之前的Android Studio进行ndk开发是比较繁琐的,如果你还在使用旧版本的Android Studio,那么建议更新到3.0,现阶段3.0已经比较稳定了(虽然旧项目的gradle升级可能需要折腾一下)。下面介绍旧版本的开发流程只是为了能够更加详细地介绍jni。

jni并不是android框架内的概念,所以也会提及其他环境使用jni开发的方法,基本上大同小异,不过你可能还需要查阅其他文章来处理一些细节问题(如Windows下生成dll文件)

AS 2.2之前的做法

1.编写C/C++

    package com.linjiamin.jnishare;
    
    /**
    * Created by Albert on 17/11/16.
    */
    
    public class JniUtil {
    
        static {
            System.loadLibrary("sotest");
        }
    
        public static native int sum(int num1,int num2);
    }
    //在终端中
    cd app/src/main/java
    javac com/linjiamin/jnishare/JniUtil.java
    javah com.linjiamin.jnishare.JniUtil
    
    //生成的头文件如下
    
    /* DO NOT EDIT THIS FILE - it is machine generated */
    #include <jni.h>
    /* Header for class com_linjiamin_jnishare_JniUtil */
    
    #ifndef _Included_com_linjiamin_jnishare_JniUtil
    #define _Included_com_linjiamin_jnishare_JniUtil
    #ifdef __cplusplus
    extern "C" {
    #endif
    /*
    * Class:     com_linjiamin_jnishare_JniUtil
    * Method:    sum
    * Signature: (II)I
    */
    JNIEXPORT jint JNICALL Java_com_linjiamin_jnishare_JniUtil_sum 
    (JNIEnv *, jclass, jint, jint);
    
    #ifdef __cplusplus
    }
    #endif
    #endif
    JNIEXPORT: 在android和linux中是空定义的宏,而在windows下被定义为__declspec(dllexport),具体的作用我们不需要关心
    
    jni数据类型:如jint,jboolean,jstring,它们对应于本地方法的返回类型(int,boolean,String)之后会进一步介绍、
    
    JNICALL : 这是__stdcall等函数调用约定(calling conventions)的宏,这些宏用于提示编译器该函数的参数如何入栈(从左到右,从右到左),以及清理堆栈的方式等
    
    方法名: Java + 完整类名 + 方法名
    
    参数列表:JNIEnv * + jclass\jobject + 所有你定义的参数所对应的jni数据类型 ,JNIEnv*是指向jvm函数表的指针,如果该方法为静态方法则第二个参数为class否则为jobject,它是含有该方法的class对象或实例
    
    注意JNIEXPORT和JNICALL是固定的
    #include "jni.h"
    #include "com_linjiamin_jnishare_JniUtil.h"
    //
    // Created by Albert Humbert on 17/11/17.
    //
    
    JNIEXPORT jint JNICALL Java_com_linjiamin_jnishare_JniUtil_sum
    (JNIEnv * env, jclass obj, jint num1, jint num2){
      return num1 + num2;
    }

2.使用ndk编译so包

包结构
编写Android.mk
    LOCAL_PATH := $(call my-dir)
    include $(CLEAR_VARS)
    LOCAL_MODULE := libsotest
    LOCAL_SRC_FILES := com_linjiamin_jnishare_JniUtil.cpp
    include $(BUILD_SHARED_LIBRARY)
编写Application.mk
    APP_PLATFORM = android-24
    APP_ABI := armeabi,armeabi-v7a,x86_64,arm64-v8a
    APP_STL := stlport_static
    APP_OPTIM := debug
    # for mac
    /Users/alberthumbert/Library/Android/sdk/ndk-bundle/platforms
    sourceSets.main {
    jniLibs.srcDir 'src/main/libs'
    jni.srcDirs = []
    }
    11-17 16:33:37.563 3824-3824/? D/JniUtil: test: 2

人生苦短,我用AS 3.0

自动生成函数

        public native boolean booleanFromJNI();
    JNIEXPORT jboolean JNICALL
    Java_com_linjiamin_myapplication_MainActivity_booleanFromJNI(JNIEnv *env, jobject instance) {
    
        // TODO
    
    }

编写CMakeList

    add_library( # 设置编译出来的so包的名字. 不需要添加lib前缀
                 native-lib
    
                 # 设置为共享链接库. 有SHARED,STATIC两种可选
                 SHARED
    
                 # 设置源文件的相对路径,可将多个源文件进行编译
                 src/main/cpp/native-lib.cpp )
    
    # Searches for a specified prebuilt library and stores the path as a
    # variable. Because CMake includes system libraries in the search path by
    # default, you only need to specify the name of the public NDK library
    # you want to add. CMake verifies that the library exists before
    # completing its build.
    
    find_library( # 设置外部引用库.这个库支持你在c/c++中打印log,具体请见  android/log.h
                log-lib
                # 外部引用库的名称
                log )
    
    # Specifies libraries CMake should link to your target library. You
    # can link multiple libraries, such as libraries you define in this
    # build script, prebuilt third-party libraries, or system libraries.
    
    target_link_libraries( # 指定被链接的库.
                        native-lib
    
                        # 链接log-lib到native-lib
                        ${log-lib} )
    extern "C"
    JNIEXPORT jboolean JNICALL
    Java_com_linjiamin_myapplication_JniUtil_booleanFromJNI(JNIEnv *env, jobject instance) {
    
    //上面提到的log库可以这么使用,而且你应该使用宏让它好看些
    __android_log_print(ANDROID_LOG_DEBUG,"stringFromJNI","%d",0);
    return (jboolean) true;
    
    }
    add_library( test-lib SHARED src/main/cpp/test-lib.cpp )

使用g++编译so包

    $ cd /Library/Java/JavaVirtualMachines/jdk1.7.0_71.jdk
    $ find . -iname jni.h
    ./Contents/Home/include/jni.h
    $ cd /Users/alberthumbert/Library/Android/sdk/ndk-bundle
    $ find . -iname jni.h
    ./sysroot/usr/include/jni.h
    g++ com_linjiamin_jnishare_JniUtil.cpp -fPIC -shared -o libsotest.so -Wl,--hash-style=sysv

*注意,nix平台使用lib表示一个so库,所以so文件必须以lib开头,但加载时请将lib前缀去掉**

    System.load("\***\***\***.so")
    export PATH=PATH:/XXX
    java -jar -Djava.library.path=/**/**   **.jar
    System.loadLibrary ("***");

ABI与so

    ndk{
        abiFilters  "armeabi-v7a", "x86", "armeabi"
    }

什么是jni

现在我们已经可以进行简单的ndk开发了,但为了加深理解认识,让我再来啰嗦一下jni

看过这一部分之后你对jni应该会有更近一步的感性认识

jni.h

jni数据类型

    /* Primitive types that match up with Java equivalents. */
    typedef uint8_t  jboolean; /* unsigned 8 bits */
    typedef int8_t   jbyte;    /* signed 8 bits */
    typedef uint16_t jchar;    /* unsigned 16 bits */
    typedef int16_t  jshort;   /* signed 16 bits */
    typedef int32_t  jint;     /* signed 32 bits */
    typedef int64_t  jlong;    /* signed 64 bits */
    typedef float    jfloat;   /* 32-bit IEEE 754 */
    typedef double   jdouble;  /* 64-bit IEEE 754 */
    class _jobject {};
    class _jclass : public _jobject {};
    class _jstring : public _jobject {};
    class _jarray : public _jobject {};
    class _jobjectArray : public _jarray {};
    class _jbooleanArray : public _jarray {};
    class _jbyteArray : public _jarray {};
    class _jcharArray : public _jarray {};
    class _jshortArray : public _jarray {};
    class _jintArray : public _jarray {};
    class _jlongArray : public _jarray {};
    class _jfloatArray : public _jarray {};
    class _jdoubleArray : public _jarray {};
    class _jthrowable : public _jobject {};
    
    typedef _jobject*       jobject;
    typedef _jclass*        jclass;
    typedef _jstring*       jstring;
    typedef _jarray*        jarray;
    typedef _jobjectArray*  jobjectArray;
    typedef _jbooleanArray* jbooleanArray;
    typedef _jbyteArray*    jbyteArray;
    typedef _jcharArray*    jcharArray;
    typedef _jshortArray*   jshortArray;
    typedef _jintArray*     jintArray;
    typedef _jlongArray*    jlongArray;
    typedef _jfloatArray*   jfloatArray;
    typedef _jdoubleArray*  jdoubleArray;
    typedef _jthrowable*    jthrowable;
    typedef _jobject*       jweak;
    
    
    /*
     * Reference types, in C.
     */
    typedef void*           jobject;
    typedef jobject         jclass;
    typedef jobject         jstring;
    typedef jobject         jarray;
    typedef jarray          jobjectArray;
    typedef jarray          jbooleanArray;
    typedef jarray          jbyteArray;
    typedef jarray          jcharArray;
    typedef jarray          jshortArray;
    typedef jarray          jintArray;
    typedef jarray          jlongArray;
    typedef jarray          jfloatArray;
    typedef jarray          jdoubleArray;
    typedef jobject         jthrowable;
    typedef jobject         jweak;
    typedef union jvalue {
            jboolean    z;
            jbyte       b;
            jchar       c;
            jshort      s;
            jint        i;
            jlong       j;
            jfloat      f;
            jdouble     d;
            jobject     l;
        } jvalue;

常用的接口

在讲解JNIEnv和JavaVM之前先来尝试一下各种jni的基本操作,版本较新的AS已经支持了对C/C++ 的智能提示和代码补全功能,你可以很方便地试用JNIEnv提供的接口

这里只介绍几个例子,以后有时间我会另写文章介绍这些接口,强烈推荐你使用AS把可调用的函数浏览并选择性地使用一遍

修改成员变量

    public class MainActivity extends AppCompatActivity {
    
        public String mString = null;
    
    static {
            System.loadLibrary("native-lib");
        }
    
        private static final String TAG = "MainActivity";
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            Log.d(TAG, "onCreate: "+mString);
            getFieldFromJNI();
            Log.d(TAG, "onCreate: "+ mString);
        }
    
        public native String getFieldFromJNI();
    
    }
    extern "C"
    JNIEXPORT jstring JNICALL
    Java_com_linjiamin_jnilearning_MainActivity_getFieldFromJNI(JNIEnv *env, jobject instance) {
    
        jclass clazz = env->GetObjectClass(instance);
        //获取调用者的class对象
        jfieldID  jfID = env ->GetFieldID(clazz,"mString","Ljava/lang/String;");
        //获取成员变量的键
        jstring strValue = (jstring) env->GetObjectField(instance, jfID);
        //获取成员变量的值,不做操作
        char chars[10] = "whatever";
        jstring newValue = env->NewStringUTF(chars);
        //创建一个String对象
        env->SetObjectField(instance,jfID,newValue);
        //设置新的值
        return strValue;
    }

创建引用

引用类型
局部引用
    return (jstring) env->NewLocalRef(someValue);
    for(int i = 0;i<1000000000;i++){
        jstring newValue = env->NewStringUTF(chars);
    }
        for(int i = 0;i<1000000000;i++){
            jstring newValue = env->NewStringUTF(chars);
            env->DeleteLocalRef(newValue);
        }
全局引用
    jobject gInstance;
    
    …
    
    extern "C" JNIEXPORT void JNICALL 
    Java_com_linjiamin_jnilearning_MainActivity_useCPlusThread(JNIEnv *env, jobject instance) {
    methodID = env->GetMethodID(clazz,"sayHello","()V");
    gInstance = instance;
    ...
    }
    JNI DETECTED ERROR IN APPLICATION: native code passing in reference to invalid stack indirect reference table or invalid reference: 0x7fff871642e0
    jobject gInstance;
    
    …
    extern "C" JNIEXPORT void JNICALL 
    Java_com_linjiamin_jnilearning_MainActivity_useCPlusThread(JNIEnv *env, jobject instance) {
    
    methodID = env->GetMethodID(clazz,"sayHello","()V");
    gInstance = env->NewGlobalRef(instance);
    ...
    }
    
    …
    
    void fun() {
    ...
    ...
    env->DeleteGlobalRef(gInstance);
    }
弱引用
    gInstance = env->NewWeakGlobalRef(instance);
    env->DeleteWeakGlobalRef(gInstance)
    if (env->IsSameObject(gInstance,NULL)) {
        __android_log_print(ANDROID_LOG_DEBUG,"fun","%s","instance is NULL");
    }
    
        //或者
    
    if (!gInstance || !env->NewWeakGlobalRef(gInstance)) {
        __android_log_print(ANDROID_LOG_DEBUG,"fun","%s","instance is NULL");
    }

JNIEnv,JavaVM 以及多线程

        env->GetJavaVM(&gVm);
    public void resultCallback(boolean isSuccess,int result,String data){
        Log.d(TAG, "resultCallback: "+ isSuccess + " "+result + " " +data);
    }
    
    public native void useCPlusThread();
    extern "C"
    JNIEXPORT void JNICALL
    Java_com_linjiamin_jnilearning_MainActivity_useCPlusThread(JNIEnv *env, jobject instance) {
    
    env->GetJavaVM(&gVm);
    jclass clazz = env->GetObjectClass(instance);
    
    
    methodID = env->GetMethodID(clazz, "resultCallback", "(ZILjava/lang/String;)V");
    gInstance = env->NewGlobalRef(instance);
    
    pthread_t pthread[5];
    
    for(int i = 0;i<5;i++){
        pthread_create(&pthread[i], NULL, &fun, NULL);
    }
    }
    void *fun(void *arg) {
    sleep(3);
    
    JNIEnv *env;
    if (gVm->AttachCurrentThread(&env, NULL) != JNI_OK) {
        __android_log_print(ANDROID_LOG_DEBUG, "callJniInDifferentThread", "%s", "attach failed");
        return NULL;
    }
    
    jvalue * args = new jvalue[3];
    args[0].z = (jboolean) true;
    args[1].i = 1000;
    args[2].l = env->NewStringUTF("some data");
    
    env->CallVoidMethodA(gInstance, methodID, args);
    
    if (gVm->DetachCurrentThread() != JNI_OK) {
        __android_log_print(ANDROID_LOG_DEBUG, "callJniInDifferentThread", "%s", "detach failed");
    }
    
    return NULL;
    
    }
类型 缩写
Boolean Z
Byte B
Char C
Short S
Int I
Long L
Float F
Double D
Void V
Object 以"L"开头,以";"结尾,中间是用"/" 隔开的包及类名。比如:Ljava/lang/String;如果是嵌套类,则用$来表示嵌套。例如 "(Ljava/lang/String;Landroid/os/FileUtils$FileStatus;)Z"
返回值与参数 例 (IB)L 表示返回类型为long,参数为int和byte的函数

内存泄漏

局部引用的内存模型

    for(int i = 0;i<1000000000;i++){
        jstring newValue = env->NewStringUTF(chars);
    }

引用的使用规范

推荐阅读

上一篇下一篇

猜你喜欢

热点阅读