Android JNI 篇 - 从入门到放弃

本篇文章已授权微信公众号 guolin_blog(郭霖)独家发布

一、JNI 涉及的名词概念

二、JNI 在 Android Studio 搭建

三、JNI 类型,方法对照表

四、JNI 场景实践

五、JNI Java 和 C++ 无缝对接

六、JNI 开源实战

一、JNI 涉及的名词概念

1.1、 JNI:Java Native Interface
  • 它是Java平台的一个特性(并不是Android系统特有的)。实现Java代码调用C/C++的代码,C/C++的代码也可以调用Java的代码.
1.2、 二进制库分类 : 静态库,动态库.
系统 静态库文件
Windows .lib
Linux .a
MacOS/IOS .a
系统 动态库文件
Windows .dll
Linux .so
MacOS/IOS .dylib
  • 静态库

这么解释:

.a 静态库就是好多个 .o 合并到一块的集合,经常在编译C 库的时候会看到很多.o,这个.o 就是目标文件 由 .c + .h 编译出来的。.c 相当于 .java, .hC 库对外开放的接口声明。对外开放的接口 .h.c 需要一一对应,如果没有一一对应,外部模块调用了接口,编译的时候会提示找不到方法。.a 存在的意义可以看成 Android aar 存在的意义,方便代码不用重复编译, 最终为了生成 .so (apk)

  • 动态库,在 Android 环境下就是 .so ,可以直接被java 代码调用的库.
1.3、 CPU 架构(ABI):armeabi,armeabi-v7a,x86,mips,arm64-v8a,mips64,x86_64

各个平台架构的区别就是指令集不一样,浮点运算能力不一样,按照上面排列的顺序,浮点运算能力运行从低到高。

  • armeabi:这是相当老旧的一个版本,缺少对浮点数计算的硬件支持,在需要大量计算时有性能瓶颈 (微信)
  • armeabi-v7a: ARM v7 目前主流版本,兼容 armeabi (facebook app)
  • arm64-v8a: 64 位支持 兼容 armeabi-v7a armeabi
  • mips/mips64: 极少用于手机可以忽略
  • x86/x86_64: x86 架构一般用于 TV 电视机 ,兼容 armeabi

建议 android apk 为了减少包体大小只接入 armeabi-v7a 即可

1.4、 Android 特有的文件 :Android.mk Application.mk
  • Android.mk:在 Android 上编译需要的配置文件,相当于 build.gradle,详细细节后面会讲到。

  • Application.mk:上代码

APP_PLATFORM := android-14 //指定 android 系统
APP_ABI := armeabi-v7a // 指定生成哪个架构的 so

更多详情

1.5、 NDK :Android 平台上用来编译 C/C++库的工具

下载地址

二、JNI 在 Android Studio 搭建

2.1、创建一个子module,创建 java 层代码,新建一个HelloWorld 类准备和 c 层对接,代码如下:
public class HelloWorld {

    static {
        try {
            System.loadLibrary("helloworld");
        } catch (Exception e) {
        }
    }
    private volatile static HelloWorld instance;
    private HelloWorld() {
    }
    public static HelloWorld getInstance() {
        if(instance == null) {
            synchronized (HelloWorld.class) {
                if(instance == null) {
                    instance = new HelloWorld();
                }
            }
        }
        return  instance;
    }


  public native String nativeGetString();

}

很明显上面类分成三部分:

  • static 代码块,调用了System.loadLibrary("helloworld");这句代码代表着,使用这个类之前都会去加载libhelloworld.so 这个动态库,注意.so前面有lib。那这个动态库如何生成,后面讲。
  • 这个类是一个单例
  • 有一个 native 的方法 public native String nativeGetString();这个方法的实现在 c 层。所以接下来我们要构建 c 层的代码。
2.2、接着在子module的目录下建立一个叫做 jni 的文件夹。例如:
image
2.3、创建 c 代码,和配置文件,看下图的位置:
image
  • 生成一个 helloworld_android.c,代码如下:对接 java 层 ,下面的方法
    JNIEXPORT jstring JNICALL Java_com_tct_helloworld_HelloWorld_nativeGetString(JNIEnv *env, jobject obj)javapublic native String nativeGetString(); 方法的 代码实现:
#include <jni.h>
#include <stdio.h>
#include <stdlib.h>

//public native String nativeGetString();

JNIEXPORT jstring JNICALL Java_com_tct_helloworld_HelloWorld_nativeGetString(JNIEnv *env, jobject obj) {

    char *foo = malloc(sizeof(char) * 64); //申请内存
    //  char *foo = "helloworld";
    snprintf(foo, "%s", "helloworld"); //写入字符串到foo指针
    jstring jname = (*env)->NewStringUTF(env, foo);
    free(foo); //释放指针
    foo = NULL;
    return jname;  //返回字符串
    //return helloworld;
}
  • 生成并编写 Android.mk,代码如下
#获取当前目录的相对路径,也就是当前文件的父路径
LOCAL_PATH := $(call my-dir)
#清除当前的所有变量值
include $(CLEAR_VARS)

#本模块需要调用到的接口,也就是.h 文件
#LOCAL_C_INCLUDES := XXX

#本模块需要编译到的 c 文件
LOCAL_SRC_FILES := helloworld_android.c

#加入第三方库log库,NDK 自带的
LOCAL_LDLIBS := -llog

#生成库的名字。最终生成 libhelloworld
LOCAL_MODULE    := helloworld

#生成的是动态库.so
include $(BUILD_SHARED_LIBRARY)


#生成的是动态库.a
#include $(BUILD_STATIC_LIBRARY)
  • 生成并编写 Application.mk
APP_ABI := armeabi-v7a //生成 armeabi-v7a  的 so
APP_PLATFORM := android-21 //指定 tagerSDK
2.4、接下来配置子 modulebuild.gradleNDK
apply plugin: 'com.android.library'

android {
    compileSdkVersion 27

    externalNativeBuild.ndkBuild {
        path "src/main/jni/Android.mk" //指定c 层的 mk 文件的位置
    }
    defaultConfig {
        versionCode 1
        versionName "1.0"

        sourceSets {
            main {
                jni.srcDirs = [] //run 的时候不会重新编译 jni ,只有make 的时候生效
            }
        }
        ndk {
            abiFilters "armeabi-v7a"//让APK只包含指定的ABI
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
}

根目录下的 local.properties,配置自己的 NDK 路径

ndk.dir=G\:\\AndroidNDK\\android-ndk-r16b
2.5、把项目跑起来
  • make 一下子 module,把项目编译一下。把.so.aar 一次编译出来。
    image

    image
  • 观察编译完毕的目录结构,aar是出来了,但是好像没有发现 so 的踪影。
image
  • 解压 aaraar 其实就是 zip 压缩,只是谷歌把它换了个后缀名)。
    image
2.6、最后写个MainActivity.java 调用一下接口
  • 调用接口代码
    @OnClick(R.id.btnTestNDKCrash)
    void testNDKCrash(){
        String ret = HelloWorld.getInstance().nativeGetString();
        System.out.println("test "+ret);
    }
  • 发现崩溃了,如何定位并且解决?先看log
image

很明显这种异常,是崩溃在c层的log,并且 java 层是无法 try - catch 住的。只能在 c 层去解决这个问题。我们在项目中有时候也会遇到这种异常,有的时候是系统库奔溃了(无解,只能从java 层检查是否有 规范的调用接口),有时候是第三方的 so 库奔溃了(找到jni 的源码才能解决)。

  • 定位并解决问题

命令行:

G:\AndroidNDK\android-ndk-r16b\toolchains\arm-linux-androideabi-4.9\prebuilt\windows-x86_64\bin\arm-linux-androideabi-addr2line -e D:\StadyProject\OpenCode\breakpad-for-android-master\sample\helloworld\build\intermediates\ndkBuild\debug\obj\local\armeabi-v7a\libhelloworld.so 00000fb9

打开你的 Terminal 把上面的命令输进去,就可以看到闪退的代码行了:

image

定位奔溃的代码行:

G:\AndroidNDK\android-ndk-r16b\toolchains\arm-linux-androideabi-4.9\prebuilt\windows-x86_64\bin\arm-linux-androideabi-addr2line -e

目标文件 so 库的位置,so一个存在 aar ,一个存在 build 目录下面,位置比较深,但是都是固定目录,可以找到:

D:\StadyProject\OpenCode\breakpad-for-android-master\sample\helloworld\build\intermediates\ndkBuild\debug\obj\local\armeabi-v7a\libhelloworld.so

奔溃的 内存位置:

00000fb9

崩溃的代码行:

image

这句代码的意思是把 helloworld 字符串赋值到 foo 这个变量中去。但是少传了一个参数导致崩溃。 看下面两个函数的不同处

snprintf(foo, LEN, "%s", "helloworld");//最多传入 foo 能承载的字符数,多了一个参数
sprintf(foo, "%s", "helloworld");//无指定写入多少字符

那么改成以下代码,就可以了

#define LEN 64
snprintf(foo, LEN, "%s", "helloworld");

再回顾一下 java层代码:

   @OnClick(R.id.btnTestNDKCrash)
    void testNDKCrash(){
        String ret = HelloWorld.getInstance().nativeGetString();
        System.out.println("test "+ret);
    }

跑起来logcat

image

三、JNI 类型,方法对照表

3.1、基本类型对照表
Java类型 本地类型 描述
boolean jboolean C/C++8位整型
byte jbyte C/C++带符号的8位整型
char jchar C/C++无符号的16位整型
short jshort C/C++带符号的16位整型
int jint C/C++带符号的32位整型
long jlong C/C++带符号的64位整型e
float jfloat C/C++32位浮点型
double jdouble C/C++64位浮点型
Object jobject 任何Java对象,或者没有对应java类型的对象
Class jclass Class对象
String jstring 字符串对象
Object[] jobjectArray 任何对象的数组
boolean[] jbooleanArray 布尔型数组
byte[] jbyteArray 比特型数组
char[] jcharArray 字符型数组
short[] jshortArray 短整型数组
int[] jintArray 整型数组
long[] jlongArray 长整型数组
float[] jfloatArray 浮点型数组
double[] jdoubleArray 双浮点型数组
3.2、jni 层使用 java 类方法名称

Java类型 | 本地类型 | 描述

函数 Java数组类型 本地类型
GetBooleanArrayElements jbooleanArray jboolean
GetByteArrayElements jbyteArray jbyte
GetCharArrayElements jcharArray jchar
GetShortArrayElements jshortArray jshort
GetIntArrayElements jintArray jint
GetLongArrayElements jlongArray jlong
GetFloatArrayElements jfloatArray jfloat
GetDoubleArrayElements jdoubleArray jdouble
函数 描述
GetFieldID 得到一个实例的域的ID
GetStaticFieldID 得到一个静态的域的ID
GetMethodID 得到一个实例的方法的ID
GetStaticMethodID 得到一个静态方法的ID
Java 类型 符号
boolean Z
byte B
char C
short S
int I
long L
float F
double D
void V
objects对象 Lfully-qualified-class-name;L类名
Arrays数组 [array-type [数组类型
methods方法 (argument-types)return-type(参数类型)返回类型

四、JNI 场景实践

由于上面看了方法的对照表,下面讲解如何使用:

4.1、java 调用到 C
// JAVA 层方法
public native String nativeGetString(String tmp);
// 对应 JNI 层方法
JNIEXPORT jstring JNICALL
Java_com_tct_helloworld_HelloWorld_nativeGetString(JNIEnv *env, jobject obj,jstring jtmp) {
}
// JAVA 层方法
public native void nativeGetString(Model tmp);
// 对应 JNI 层方法
JNIEXPORT void JNICALL
Java_com_tct_helloworld_HelloWorld_nativeGetString(JNIEnv *env, jobject obj,jobject jmod) {
}
  • JNIEnv *envJNIjava 线程的上下文,每一个线程都有一个 env
  • jobject obj 代表的 java 的对象,从java 哪个对象调用下来的,就是哪对象。
4.2、C 层解析 java 类中的属性值,转成 C 层可使用的类型
//java 类
public class Model {

    public int code;
    public String name;

    public Model(int code, String name) {
        this.code = code;
        this.name = name;
    }
}

// JAVA 层方法
public native void nativeGetString(Model tmp);
// 对应 JNI 层方法
JNIEXPORT void JNICALL
Java_com_tct_helloworld_HelloWorld_nativeGetString(JNIEnv *env, jobject obj,jobject jmodel) {

   jclass jmodelClass = (*env)->GetObjectClass(env, jmodel);

    if (jmodelClass == 0) {
        return;
    }
    //获取变量 code 的值
    jfieldID fidCode = (*env)->GetFieldID(env, jmodelClass, "code", "I");
    int code = (*env)->GetIntField(env, jmodel, fidCode);

   //获取变量 name 的值
    jfieldID fidName = (*env)->GetFieldID(env, jmodelClass, "name",
                                           "Ljava/lang/String;");
    jstring jname = (jstring)(*env)->GetObjectField(env, jmodel, fidName);
    char *name = (*env)->GetStringUTFChars(env, jname, 0);
    
    // ..
    
    //使用完毕,char * 需要回收
    (*env)->ReleaseStringUTFChars(env, jname, name);
    // 自己生成的 jclass 需要回收,以及其他的引用也是需要的,局部变量不能超512 个,特别是在 for 循环体内要及时回收
    (*env)->DeleteLocalRef(env, jmodelClass);
}
4.3、C 层返回 java 对象
//java 层方法
private volatile static HelloWorld instance;
private HelloWorld() {
}
public static HelloWorld getInstance() {
    if(instance == null) {
       synchronized (HelloWorld.class) {
         if(instance == null) {
                instance = new HelloWorld();
           }
        }
   }
    return  instance;
}
public native static HelloWorld nativeGetInstance();

//C层方法
JNIEXPORT jobject JNICALL Java_com_tct_helloworld_HelloWorld_nativeGetInstance
        (JNIEnv *env, jclass cls) {
    //找到class
    jclass cls1 = (*env)->FindClass(env, "com/tct/helloworld/HelloWorld");
    //找到构造函数的方法ID
    jmethodID cid = (*env)->GetMethodID(env, cls1, "<init>", "()V");

    //生成一个对象返回
    jobject jInstance = (*env)->NewObject(env, cls1, cid);

    return jInstance;
}


// MainActivity.java 的调用方法
  @OnClick(R.id.btnTestNDKCrash)
    void testNDKCrash(){
        if(HelloWorld.getInstance() == HelloWorld.nativeGetInstance()) {
            System.out.println("HelloWorld instance true");
        } else {
            System.out.println("HelloWorld instance false");
        }

    }

得出 log

I/System.out: HelloWorld instance false

原来不仅仅反射机制能破解单例, JNI 也是可以破解单例。

4.4、C 层返回 java 对象数组
//java 层代码
public native static HelloWorld[] nativeGetInstanceArray();
// c 层代码   
JNIEXPORT jobjectArray JNICALL Java_com_tct_helloworld_HelloWorld_nativeGetInstanceArray
        (JNIEnv *env, jclass cls) {
    jclass cls1 = (*env)->FindClass(env, "com/tct/helloworld/HelloWorld");
    jmethodID cid = (*env)->GetMethodID(env, cls1, "<init>", "()V");
    jsize len = 10;
    jobjectArray mjobjectArray;
    //新建object数组
    mjobjectArray = (*env)->NewObjectArray(env, len, cls1, 0);
    for (int i = 0; i < len; ++i) {
        jobject jInstance = (*env)->NewObject(env, cls1, cid);
        (*env)->SetObjectArrayElement(env, mjobjectArray, i, jInstance);
        //回收,局部引用不能超过512个
        (*env)->DeleteLocalRef(env, jInstance);
    }

    (*env)->DeleteLocalRef(env, cls1);
    return mjobjectArray;
}

//MainActivity.java 调用
  @OnClick(R.id.btnTestNDKCrash)
    void testNDKCrash(){
        HelloWorld[] HelloWorlds = HelloWorld.getInstance().nativeGetInstanceArray();
        System.out.println("HelloWorld arrays length:"+HelloWorlds.length);
    }

log:

I/System.out: HelloWorld arrays length:10
4.5、C 层回调到 java

//java 层方法

public class TestBean {

    public int code;
    public String name;

    public TestBean(int code, String name) {
        this.code = code;
        this.name = name;
    }

    @Override
    public String toString() {
        return "TestBean{" +
                "code=" + code +
                ", name='" + name + '\'' +
                '}';
    }
}

 public interface HelloWorldListener {
        public void onLinstener(TestBean testBean);
}
    
public native  void nativeGetInstanceByThread(HelloWorldListener listener);
 
 


//c 层方法  



//jni 当前上下文,可用于当前 native 线程加入java 线程,用于回调,或者是获取 jvm 线程 上下文
JavaVM *g_VM;
//用来 findClass
jobject gClassLoader;
jmethodID gFindClassMethod;

//获取jvm 上下文
JNIEnv *getEnv() {
    JNIEnv *env;
    int status = (*g_VM)->GetEnv(g_VM, (void **) &env, JNI_VERSION_1_6);
    if (status < 0) {
        status = (*g_VM)->AttachCurrentThread(g_VM, &env, NULL);
        if (status < 0) {
            return NULL;
        }
    }
    return env;
}

/**
*  java 层调用 System.loadLibrary(); 的时候就会调用这个方法,此方法的目的是 找到classloader的对象,还有类加载的方法ID
*/
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *pjvm, void *reserved) {
    g_VM = pjvm;  // cache the JavaVM pointer
    JNIEnv *env = getEnv();
    //replace with one of your classes in the line below
    jclass randomClass = (*env)->FindClass(env, "com/tct/helloworld/HelloWorld");
    jclass classClass = (*env)->GetObjectClass(env, randomClass);
    jclass classLoaderClass = (*env)->FindClass(env, "java/lang/ClassLoader");
    jclass getClassLoaderMethod = (*env)->GetMethodID(env, classClass, "getClassLoader",
                                                      "()Ljava/lang/ClassLoader;");
    gClassLoader = (*env)->NewGlobalRef(env, (*env)->CallObjectMethod(env, randomClass,
                                                                      getClassLoaderMethod));
    gFindClassMethod = (*env)->GetMethodID(env, classLoaderClass, "findClass",
                                           "(Ljava/lang/String;)Ljava/lang/Class;");

    return JNI_VERSION_1_6;
}

//调用ClassLoder 去找到对应的类,在linux 线程是独立于JVM ,所以一般的 findClass 是找不到jvm中的类。只能使用八大基本类型。
jclass GlobalFindClass(const char* name) {
    JNIEnv* env = getEnv();
    return (jclass)((*env)->CallObjectMethod(env,gClassLoader, gFindClassMethod, (*env)->NewStringUTF(env,name)));
}

void test_process(void *p) {
    jobject callBack = (jobject)p;
    JNIEnv *env;
    jboolean mNeedDetach;
    //获取当前native线程是否有没有被附加到jvm环境中
    int getEnvStat = (*g_VM)->GetEnv(g_VM, (void **) &env, JNI_VERSION_1_6);
    if (getEnvStat == JNI_EDETACHED) {
        //如果没有, 主动附加到jvm环境中,获取到env
        if ((*g_VM)->AttachCurrentThread(g_VM, &env, NULL) != 0) {
            return;
        }
        mNeedDetach = JNI_TRUE;
    }
    jclass cls = GlobalFindClass( "com/tct/helloworld/TestBean");
    if (cls == 0) {
        LOGI("native cls= %ld", cls);
        return;
    }
    jmethodID cid = (*env)->GetMethodID(env, cls, "<init>", "(ILjava/lang/String;)V");
    jstring name = (*env)->NewStringUTF(env,"helloworld");
    jobject jInstance = (*env)->NewObject(env, cls, cid,(jint)1, name);


    //获取回调的类
    jclass  jcallBackClass = (*env)->GetObjectClass(env,callBack);

    //通过回调的类找到回调的方法
    jmethodID callbackid = (*getEnv())->GetMethodID(env, jcallBackClass, "onLinstener", "(Lcom/tct/helloworld/TestBean;)V");
    if(callbackid ==0) {
        return;
    }

    //调用回调的方法
    (*env)->CallVoidMethod(env,callBack,callbackid,jInstance);

    (*env)->DeleteGlobalRef(env, callBack);
    (*env)->DeleteLocalRef(env, jcallBackClass);
    (*env)->DeleteLocalRef(env, jInstance);
    (*env)->DeleteLocalRef(env, cls);
    (*env)->DeleteLocalRef(env, name);

    //释放当前线程
    if (mNeedDetach) {
        (*g_VM)->DetachCurrentThread(g_VM);
    }

}

int start_test_thread(jobject listener) {
    pthread_t tid;
    if (0 != (pthread_create(&tid, NULL, test_process, listener))) {
        return -1;
    } else {
        pthread_detach(tid); //设置成 分离线程,线程跑完自己回收内存
    }
    return 0;
}

JNIEXPORT void JNICALL Java_com_tct_helloworld_HelloWorld_nativeGetInstanceByThread
        (JNIEnv *env, jobject obj,jobject jListener) {
// 这里的内存区域属于 native 栈中。跑完这个方法,局部变量都会被回收。所以需要使用 NewGlobalRef 对 jListener 生成一个全局引用(linux 堆中)
    jobject callback = (*env)->NewGlobalRef(env, jListener);
    //开启线程
    start_test_thread(callback);
}

C 层回调 Java 层 方法,更多的解决方案,详细查看我的另一篇博客

五、JNI Java 和 C++ 无缝对接

5.1、 以上实践都是 javaC 的对接。然而 java 是面向对象, C 是面向过程没有对象的概念。
  • 举个场景例子:

如果 java 层需要发起AB个线程 到C层去请求数据,并且需要各自提供 请求、取消的接口。要实现多线程的取消接口,如果使用 C 封装 JNI,就需要提供链表(或者其他集合的数据结构)把每一个线程的 Tid(java),和请求绑定起来,取消的时候通过链表找到该线程的请求把柄,通过把柄取消。期间你会遇到链表插入删除,多线程锁,还得多个链表的全局引用。非常麻烦。

image
  • 然而 java 就是为了避免这种麻烦,实现高效率编程。面向对象诞生了。
  • 那么如何从 java -> C++->C 进行调用。上流程图,上代码:
image

java 层对接类 HelloWorld.java

public class HelloWorld {
    //加入一个变量 long 型保存 C++ 对象的地址
    public long mNativeContext = 0L;
    //类被创建,相对应的 JNI 也创建一个类
    public HelloWorld() {
        init();
    }
    public native void init();
    //..
}

JNI 层新建两个文件:HelloWorld.cppHelloWorld.h
HelloWorld.cpp 代码:


#include "HelloWorld.h"

extern "C" {

}

HelloWorld::HelloWorld() {
}

HelloWorld::~HelloWorld() {

}

 char * HelloWorld::getString() {
     return  "HelloWorld";

}

HelloWorld.h 代码




#ifndef HelloWorld_H
#define HelloWorld_H


class HelloWorld
{

public:
    HelloWorld();
    ~HelloWorld();
    char * getString();
};

#endif

JNI 层接口 helloworld_android.c 代码:

//创建一个结构体存放对象地址
typedef struct {
    jfieldID context;
} fields_t;

static fields_t fields;

//  System.loadLibrary("helloworld");触发被调用的方法
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *pjvm, void *reserved) {
    JNIEnv *env = getEnv();
    //获取 java 层 mNativeContext 变量的 ID ,并赋值到 fields.context 这个全局变量。
    fields.context = env->GetFieldID(randomClass, "mNativeContext", "J");
    // ...
    return JNI_VERSION_1_6;
}

JNIEXPORT void  JNICALL Java_com_tct_helloworld_HelloWorld_init
        (JNIEnv *env, jobject obj) {
  //初始化,HelloWorld 指针对象,并且强转指针为 long 型,赋值到 对应的java 对象中 mNativeContext 的变量中去
    HelloWorld *mHelloWorld = new HelloWorld();
    env->SetLongField(obj, fields.context, (long)mHelloWorld);
}

最后验证一下:

//java 层代码
//MainActivity.java
System.out.println("test1" +new HelloWorld().nativeGetStringByObject());
System.out.println("test2" +new HelloWorld().nativeGetStringByObject());

//HelloWorld.java
public native String nativeGetStringByObject();
  
//C 层代码 
static HelloWorld *getObject(JNIEnv *env, jobject thiz) {
    // No lock is needed, since it is called internally by other methods that are protected
    HelloWorld *retriever = (HelloWorld *) env->GetLongField(thiz,fields.context);
    return retriever;
}

JNIEXPORT jstring  JNICALL Java_com_tct_helloworld_HelloWorld_nativeGetStringByObject(JNIEnv *env, jobject obj) {
  char * p = getObject(env,obj)->getString();
  return env->NewStringUTF(p);
}

log

I/System.out: test1HelloWorld
I/System.out: test2HelloWorld

六、JNI 开源实战

对于 JNI 的一些的基本知识基本就讲完了。JNI 的用途为 java 开辟了另一扇大门,所有能在C 上面实现的。都能拿过来给Android平台上使用。
譬如以下一些 C库:

  • 音视频播放库
  • 高斯模糊库
  • openCV 人脸识别,车牌号码识别
  • 苹果的 AirPlay协议 蓝牙耳机

更多 C详情地址

接下来实战一个 bilibili/ijkPlayer音视频解码库的开源代码。
传送门

注: 感谢 http://www.cnblogs.com/daniel-shen/archive/2006/10/16/530587.html 提供表格

推荐阅读:Android 编译速度优化黑科技 - RocketX

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容