第十四章 JNI 和 NDK编程

  1. Java JNI的本意是Java Native Interface(java 本地接口),为了方便java调用C、C++等本地代码所封装的一层接口。
  2. Java的优点是跨平台,但是和本地交互的时候出现了短板,一些和操作系统相关的操作出现了短板。于是提供JNI专门用于和本地代码交互。通过Java JNI,用户可以调用C、C++所编写的本地代码。
  3. NDK 是Android所提供的一个工具集合,通过NDK可以在Android中更方便通过JNI来访问本地代码,比如C/C++。NDK提供了交叉编辑器,只需要简单滴修改mk文件就可以生成特定CPU平台的动态库。
  4. 使用NDK的好处:
    (1) 提高代码的安全性。由于so库反编译比较困难,因此NDK提高了Android程序的安全性。
    (2) 可以很方便地使用目前已有的C/C++开源库。
    (3) 便于平台间的移植,通过C/C++实现的动态库可以很方便地在其他平台上使用
    (4) 提高程序在某些特定情形下的执行效率,但是并不能明显提升Android程序的性能
  5. NDK的开发是基于JNI的

编译Java得到 class 文件,然后通过javah命令导出 JNI 的头文件
具体的命令:

javac D:\DuerOS\Android_art\14_ndk\src\main\java\com\why\a14_ndk\JniTest.java    //编译Java源文件得到class文件 该类不能import Android中的包
javah -classpath D:\DuerOS\Android_art\14_ndk\src\main\java -d D:\DuerOS\Android_art\14_ndk\src\main\java\com\why\a14_ndk -jni com.why.a14_ndk.JniTest           //通过javah命令导出JNI的头文件
javah -classpath 包名文件夹路径 -d 头文件输出路径 -jni 包名.类名(不带.class)
-classpath <路径> 用于装入类的路径。注意是包名文件夹的路径,不是class的路径,如果没有包名的话就是class的路径。
-d <目录> 输出目录
-jni 生成 JNI样式的头文件(默认)

NDK 的开发流程#

1. 创建一个Android项目,声明所需的native方法

public class MainActivity extends Activity {

    private TextView mTextView;

    static {
        /**
         * Android.mk中LOCAL_MODULE 指定的名字就是生成的so文件名字(只不过没有前缀lib-),在这里加载
         */
        System.loadLibrary("jni-test");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mTextView = findViewById(R.id.msg);
        mTextView.setText(get());
        startActivity(new Intent(this, SecondActivity.class));
        set("hello world from Main");
    }

    /**
     * native的java方法如果有返回值,那么c/c++中必须保持一致,
     * 
     * 如果java中是返回值是String,而c/c++中是void,会报错
     */
    private native String get();
    private native String set(String str);

}

2. 实现 Android 项目中所声明的 native 方法
如果在类种import Android包,那么不能使用 javac 。可以定义一个Jni.java,在Activity中生成一个Jni对象,通过该对象调用native方法。(javac,javah方便写c/c++,因为模板和方法名给写好了,生成头文件.h)
在与java的同级目录下创建一个名为 jni 的目录(名字不要修改,如果修改需要在 build.gradle 中指定)。在该jni目录下创建3
个文件text.cpp、Android.mk 和 Application.mk。
Android.mk

#makefile文件 作用就是向编译系统描述我要编译的文件在什么位置,要生成的文件叫什么名字,是什么类型
#获取当前路径
LOCAL_PATH := ${call my-dir}

#清除上次编译的信息
include $(CLEAR_VARS)

#指定最终生成的文件名字
LOCAL_MODULE := jni-test
#要编译的C的代码的文件名
LOCAL_SRC_FILES := test.cpp

#要生成的是一个动态链接库
include $(BUILD_SHARED_LIBRARY)

Application.mk

#指定so库的CPU平台的类型,比如armeabi,这里是x86(模拟器)
APP_ABI := x86

cpp

//第二个参数jobject    就是调用当前native方法的java对象
//第一个参数JNIEnv*   JNIEnv是结构体JNINativeInterface这个结构体的一级指针
//                  JNIEnv*是结构体JNINativeInterface这个结构体的二级指针
//
//  JNINativeInterface 定义了大量的函数指针,在JNI开发中非常常用  比如:env->NewStringUTF("Hello from JNI !")     
//如果包名中有"_",一定要用"_1"来代替
JNIEXPORT jstring JNICALL Java_com_why_a14_1ndk_MainActivity_get
  (JNIEnv * env, jobject thiz){
        printf("invoke get in c++\n");
        return env->NewStringUTF("Hello from JNI !");
    }

build.gradlebuildTypes中加上一下代码,指定so库的位置

        sourceSets {
            main {
                //指定jni的代码路径
                jni.srcDirs = []
            }
        }

3. 在命令行中通过 ” ndk-build “编译产生so库
ndk-build 之后(进入到工程中ndk-build),NDK 会创建一个和jni同级目录的 libs,libs 下存放的就是so库所在文件夹。
在与java同级的目录下创建一个名为 jniLibs 的目录(名字不要修改,如果修改需要在 build.gradle 中指定),将生成的so库所在文件夹复制到 jniLibs 目录中通过AS编译即可。
第十四章 JNI 和 NDK编程_第1张图片
第十四章 JNI 和 NDK编程_第2张图片

JNI 数据类型和类型签名

JNI 的数据类型包括两种,基本类型和引用类型。
基本类型有jboolean、jchar、jint等
引用类型有:类、对象和数组
他们和java中的数据类型对应关系见P484、P485
类型签名
类的签名:”L+包名+类名+ ; ” 。比如:java.lang.String,签名为“Ljava/lang/String;”
基本类型:大写字母表示:boolean-Z、byte-B、char-C、short-S、int-I、long-J、float-F、double-D、void-V
数组签名:”[+类型签名”。比如:char[] 签名为”[C”,String[] 签名为”[Ljava/lang/String;”
多维数组:”n个’[‘+类型签名”。比如:int[][] 签名为”[[I”
方法签名:(参数类型签名)+返回值类型签名。比如:boolean fun1(int a, double b, int[] b) 签名为”(ID[I)Z”

JNI 调用Java方法中的流程

  1. JNI调用Java的方法流程是先通过类名找到类,然后再根据方法名找到方法的 id,最后就可以调用这个方法了。
  2. 如果调用 java 的非静态方法,那么需要构造出类的对象后才能调用它。
    Jni.java
public class Jni {

    private static final String TAG = "Jni";

    public Jni(){}

    public native String get();
    public native void set(String string);

    public static void methodCalledByJni(String msgFromJni){
        Log.i(TAG, "methodCalledByJni: " + msgFromJni);
    }

    public String methodCallNotStaticByJni(String msgFromJni){
        Log.i(TAG, "methodCallNotStaticByJni: " + msgFromJni);
        return null;
    }
}

jni.cpp

#include <jni.h>
#include <stdio.h>

#ifdef __cplusplus
extern "C" {
#endif
  void callJavaStaticMethod(JNIEnv * env, jobject thiz){
    //首先根据类名找到类
    jclass clazz = env->FindClass("com/why/a14_ndk3/Jni");
    if(clazz == NULL){
        printf("find class MainActivity error");
        return;
    }
    //void methodCalledByJni(String msgFromJni)
    //再根据方法名找到方法id    "(Ljava/lang/String;)V" 方法签名
    jmethodID id = env->GetStaticMethodID(clazz, "methodCalledByJni", "(Ljava/lang/String;)V");
    if(id == NULL){
        printf("find method methodCalledByJni error");
    }
    jstring msg = env->NewStringUTF("msg send by callJavaStaticMethod in jni.cpp");
    //调用java中的方法
    env->CallStaticVoidMethod(clazz, id, msg);
  }

  void callJavaNotStaticMethod(JNIEnv * env, jobject thiz){
    //首先根据类名找到类
    jclass clazz = env->FindClass("com/why/a14_ndk3/Jni");
    //String methodCallNotStaticByJni(String msgFromJni)
    //再根据方法名找到方法id
    //先找到构造方法的方法id,利用该id创建对象
    jmethodID constructorId = env->GetMethodID(clazz, "", "()V");
    //根据类名和方法id创建类对象
    jobject obj = env->NewObject(clazz, constructorId);
    //找到要调用的方法的id
    jmethodID id = env->GetMethodID(clazz, "methodCallNotStaticByJni", "(Ljava/lang/String;)Ljava/lang/String;");
    //调用java中的方法
    //env->CallVoidMethod(obj, id, env->NewStringUTF("msg send by callJavaNotStaticMethod in jni.cpp"));
    env->CallObjectMethod(obj, id, env->NewStringUTF("msg send by callJavaNotStaticMethod in jni.cpp"));
  }
JNIEXPORT jstring JNICALL Java_com_why_a14_1ndk3_Jni_get
  (JNIEnv * env, jobject object){

        return env->NewStringUTF("调用jni中的C");
  }

JNIEXPORT void JNICALL Java_com_why_a14_1ndk3_Jni_set
  (JNIEnv * env, jobject object, jstring string){
        printf("设置数据成功\n");
        //callJavaStaticMethod(env, object);
        callJavaNotStaticMethod(env, object);
  }
#ifdef __cplusplus
}
#endif

GetMethodID、CallStaticVoidMethod等函数都是在jni.h中,可以在该文件夹中查看\android-ndk-r10e\platforms\android-21\arch-x86_64\usr\include

你可能感兴趣的:(Android开发艺术探索)