零基础带你吃掉JNI全家桶(一)

前言

大家好!我又来了,这次准备着手写一个JNI开发系列,毕竟,现在JNI开发也是在各个公司越来越重要了,如果项目毕竟大,可能涉及的模块较多,比如你作为应用层的开发,难免避免不了需要使用一些库,一些加密操作等等,一般都会放在本地方法里面,比较安全,人家丢给你so文件或者静态a文件。。你不会用岂不是很尴尬。网上资料比较杂,而且很乱,大部分还是在用.mk的方法。

本系列就基于CMake形式,希望能够带着一些希望学习JNI开发的小伙伴一起学会JNI开发~比心❤

零基础带你吃掉JNI全家桶(二)

零基础带你吃掉JNI全家桶(三)

从一个栗子说起

c++ support
注意:要支持CMake,此时我们需要勾选 Include C++ support,然后点击Next--->Finish,完成工程的创建。 创建完成后,我们打开工程目录,发现增加了几个不一样的地方:
image.png
发现AS已经帮我们生产一个cpp目录以及一个native-lib.cpp的c++文件,在根目录下,也多了一个CMakeLists.txt文件,我们主要来关注 CMakeLists.txt里面的东东

#定义cmake支持的最小版本号
cmake_minimum_required(VERSION 3.4.1)


add_library( # 设置生成so库的文件名称,例如此处生成的so库文件名称应该为:libnative-lib.so
             native-lib

             # 设置生成的so库类型,类型只包含两种:
             # STATIC:静态库,为目标文件的归档文件,在链接其他目标的时候使用
             # SHARED:动态库,会被动态链接,在运行时被加载
             SHARED

             # 设置源文件的位置,可以是很多个源文件,都要添加进来,注意相对位置
             src/main/cpp/native-lib.cpp )

# 从系统里查找依赖库,可添加多个
find_library( # 例如查找系统中的log库liblog.so
              log-lib

              # liblog.so库指定的名称即为log,如同上面指定生成的libnative-lib.so库名称为native-lib一样
              log )
# 配置目标库的链接,即相互依赖关系
target_link_libraries( # 目标库(最终生成的库)
                       native-lib

                        # 依赖于log库,一般情况下,如果依赖的是系统中的库,需要加 ${} 进行引用,
                        # 如果是第三方库,可以直接引用库名,例如:
                        # 引用第三方库libthird.a,引用时直接写成third;注意,引用时,每一行只能引用一个库
                       ${log-lib} )
复制代码

这里我把注释进行了缩减,标注了中文注释,比较详细,不明白的可以看下每个的作用,当然还有很多API可以使用,后续再详细说明,也可以看看官方文档,[戳我戳我] (developer.android.google.cn/ndk/guides/…)

我们新建一个Helper类来编写native方法

public class NativeHelper  {
    static {
        System.loadLibrary("native-lib");
    }
    public  native String stringFromJNI();
    public  native int add(int a,int b);
 
}
复制代码

打开我们的MainActivity

public class MainActivity extends AppCompatActivity {

    // Used to load the 'native-lib' library on application startup.
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Example of a call to a native method
        TextView tv = (TextView) findViewById(R.id.sample_text);
        tv.setText(stringFromJNI());
    }
}
复制代码

可以看到最上面,静态代码块引用了native-lib这个库,然后直接调用native本地方法,将C++中返回的字符串拿到进行显示。然后看看C++具体是怎么是实现的

#include <jni.h>
#include <string>

extern "C" JNIEXPORT jstring

JNICALL
Java_com_example_hik_cmake_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}
复制代码

代码很简单,引用两个头文件,然后定义了一个方法,返回“Hello from C++”这个字符串,那有的朋友要问了,为什么java层直接调用stringFromJNI()方法能够直接映射到C++里面的方法呢,细心的小伙伴可能发现了,C++里面的这个方法名很长而且很熟悉。。这不是Java包名加上方法名拼凑而成的字符串吗,这种方式呢叫做静态注册,这样就能通过这个映射方式找到C++中的方法。

有的朋友又要说了,这么长方法名也太麻烦了,虽然可以自动生成,但是多不美观,多不优雅。。是滴!有静态注册,那当然就有动态注册了~我们来改一改代码:

#include <jni.h>
#include <string>
#include <android/log.h>

#define TAG "JNI_"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,TAG,__VA_ARGS__)
JNICALL
jstring backStringToJava(JNIEnv *env, jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}
//动态注册
jint registerMethod(JNIEnv *env) {
    jclass clz = env->FindClass("com/example/taolin/jni_project/NativeHelper");
    if (clz == NULL) {
        LOGD("con't find class: com/example/taolin/jni_project/NativeHelper");
    }
    JNINativeMethod jniNativeMethod[] = {{"stringFromJNI", "()Ljava/lang/String;", (void *) backStringToJava}};
    return env->RegisterNatives(clz,jniNativeMethod, sizeof(jniNativeMethod)/ sizeof(jniNativeMethod[0]));
}
jint JNI_OnLoad(JavaVM *vm, void *reserved){
    JNIEnv * env = NULL;
    if (vm->GetEnv((void**)&env,JNI_VERSION_1_6)!=JNI_OK){
        return JNI_ERR;
    }
    jint result = registerMethod(env);
    LOGD("RegisterNatives result: %d", result);
    return JNI_VERSION_1_6;
}
复制代码

这里呢,为了在C++中打印日志,我们需要引入log.h头文件,然后我们把之前的方法名改成backStringToJava,然后因为没有了静态注册的规则,Java层调用的使用当然就找不到我们对应的方法了,我们定义一个动态注册的方法,将Java中的方法和C++中的方法进行动态的绑定:

  • 通过env指针,拿到MainActivity的class对象,具体env指针后续会详细说明
  • 定义一系列的方法对象,每个包含三个参数,第一个是java中的方法名,第二个是方法对应的签名,第三个是C++中的方法名
  • 在JNI_OnLoad方法中,调用动态注册绑定方法进行绑定

有些朋友可能对方法签名不太明白,后续语法会详细说明,这里先简单说下,方法签名也就是一个方法唯一性的一个标准,上面的()Ljava/lang/String;就是stringFromJNI的签名,前面的括号里面是参数的签名,因为这里没有参数,所以为空,紧接着后面是返回值得签名,规则是,如果是基本数据类型就是相对应的基本数据类型,如果不是基本数据类型,那么就是L+对象包名+“;”,注意这里的分号不可省略!!根据这个规则,下面那个方法的签名就是(II)I,依次类推,没明白的也没关系,后面会详细对JNI中的语法详细解释,先知道有这么回事就好了。

public native String stringFromJNI(); 
//签名:()Ljava/lang/String;
public native int add(int a int b) 
//签名:"(II)I"
复制代码

然后我们直接运行APP,可以发现页面上显示出来了“Hello from C++”字符串,然后看看我们生成的so文件在哪:

image.png

OK!大功告成,我们的第一步就完成了,成功的完成了Java调用C++的方法,但别高兴的太早,这只是第一步,好了,看到这的奖励自己跟辣条吧~,溜了溜了。。

猜你喜欢

转载自juejin.im/post/5c6920ea6fb9a049fe35b0a1