NDK开发前奏

1,114 阅读5分钟

一、环境配置

本文使用的相关工具版本如下

  • AndroidStudio 4.0.2
  • gradle 6.1.1
NDK 是什么

NDK全称:Native Development Kit

原生开发套件 (NDK) 是一套工具,使您能够在 Android 应用中使用 C 和 C++ 代码,并提供众多平台库,您可使用这些平台库管理原生 activity 和访问实体设备组件,例如传感器和触控输入。NDK 可能不适合大多数 Android 编程初学者,这些初学者只需使用 Java 代码和框架 API 开发应用。然而,如果您需要实现以下一个或多个目标,那么 NDK 就能派上用场:

  • 进一步提升设备性能,以降低延迟或运行游戏或物理模拟等计算密集型应用。
  • 重复使用您自己或其他开发者的 C 或 C++ 库。
接下来然我们搭建一下NDK的开发环境

1.png

2.png 我这里选择的是我项目中使用的版本。当然推荐大家选择较新的版本。

环境配置非常简单,选择NDK和Cmake相关的安装包就可以了

创建NDK项目工程

image.png

  • 和平时一样New Project

WechatIMG154.png

  • 滑动到最下面选择 Native C++工程

WechatIMG155.png

  • 这里有一个地方需要注意,包名不能带下划线

WechatIMG156.png

  • 这里是选择C++的版本,这里我平时选择的是C++14,当然默认也可以。
  • 接下来点击 finish ,等待创建好即可

WechatIMG160.png

  • 项目创建完毕是一个现在的状态
  • 下面我们点击运行即可

WechatIMG163.jpeg

  • 可以看到项目已经顺利跑起来了

这里注意一下:

WechatIMG159.png

  • 我们创建完项目,顺利运行起来之后,这里是没有配置ndk路径的,这里我们配置一下ndk的路径,在平时开发中可以避免不必要的麻烦
项目结构

我们看一下现在的项目结构和平时我们创建的普通的有什么不同

WechatIMG165.png

  • 我们可以看到 和普通的项目结构相比 现在的项目在main文件夹下多了一个cpp的文件目录
  • 这个cpp文件目录中主要是存放我们编写的c++文件
  • 现在这个cpp文件目录中有两个文件 native-lib.cpp 和 CMakeLists.txt
    • native-lib.cpp:这是我们编写的c++代码文件,比如上面效果图中的 Hello from C++就是在该文件中创建的
    • CMakeLists.txt:是cmake用来生成Makefile文件需要的一个描述编译链接的规则文件,包含了相关的配置信息,比如 指定cmake的版本

WechatIMG160.png 这张图中有 System.loadLibrary()external 修饰的函数和平时项目中的有所不同

  • System.loadLibrary():这个方法是加载so库使用的
  • external:被该关键词修饰的方法,是本地方法,相当于Java中的 native,在Kotlin中声明方法,需要在C/C++中实现该方法

WechatIMG161.png 上面这张图中我们有两部分平时我们没有见过

  • 第一部分
externalNativeBuild {
    cmake {
        cppFlags "-std=c++14"
    }
}

该部分是我们在创建项目时,选择的C++版本

  • 第二部分
externalNativeBuild {
    cmake {
        path "src/main/cpp/CMakeLists.txt"
        version "3.10.2"
    }
}

path:指的是CMakelists.txt文件的路径

version:指的是Cmake的版本

WechatIMG166.png

  • extern "C" :是指采用C的编译方式
  • JNIEXPORT :JNI的一个关键字, 标记为该方法可以被外部调用
  • JNICALL : JNI的一个关键字
  • JNIEnv :C和Java相互调用的桥梁
  • jobject: Java传递下来的对象
  • std :命名空间
  • hello.c_str() :将字符串转为char类型数组
  • env ->NewStringUTF():创建jstring字符串对象

二、JNI互调

什么是JNI

JNI 的全称是 Java Native Interface,从名称上面翻译,它是一个 Java 和 C 语言的接口,通过这个翻译我们基本可以判定,这个 JNI 其实就是 Java 语言和 C 语言之间通讯的桥梁。

为什么要有JNI

因为 Java 和 C 之间无法直接通讯,无法直接通过代码显式调用,这中间需要一个翻译官来做这件事,而 JNI 出现的目的就是为了解决 Java 和 C 这两个不同语言之间的通讯问题。

下面从以下几个事例来介绍JNI互调
  • C/C++ 调用 Java 方法
public class MainActivity extends AppCompatActivity {
    static {
        System.loadLibrary("native-lib");
    }

    public native void callPlusMethod();

    public int onPlus(int number1, int number2) {
        int num = number1 + number2;
        return num;
    }

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

}
extern "C"
JNIEXPORT void JNICALL
Java_com_ndk_dome_MainActivity_callPlusMethod(JNIEnv *env, jobject jobj) {
    // 通过jobject获取到jclass
    jclass j_clzz = env->GetObjectClass(jobj);
    //通过jclass对象获取jmethodID
    jmethodID methodID = env->GetMethodID(j_clzz, "onPlus", "(II)I");
    //调用onPlus方法
    env->CallIntMethod(jobj, methodID, 3, 2);
}
  • C/C++ 修改 Java 变量
public class MainActivity extends AppCompatActivity {
    static {
        System.loadLibrary("native-lib");
    }
    
    public String name = "Delusion";
   
    public native void changeName();

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

}
extern "C"
JNIEXPORT void JNICALL
Java_com_ndk_dome_MainActivity_changeName(JNIEnv *env, jobject jobj) {
    //获取属性所在class对象 
    jclass j_clz = env->GetObjectClass(jobj);
    //获取属性jfieldID
    jfieldID j_fid = env->GetFieldID(j_clz, "name", "Ljava/lang/String;");
    //创建新的jstring对象
    jstring jniName = env->NewStringUTF("Dian");
    //修改为 Dian
    env->SetObjectField(jobj, j_fid, jniName);
}
  • C/C++ 创建Java对象
public class MainActivity extends AppCompatActivity {
    static {
        System.loadLibrary("native-lib");
    }
   
    public native Point createPoint();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);          
        Point point = createPoint();          
        Log.e("---->", "Point: x--->"+point.x+"   y--->"+point.y);
    }

}
public class Point {
    int x;
    int y;

    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }
    public void setY(int y) {
        this.y = y;
    }
}
JNIEXPORT jobject JNICALL
Java_com_jni_dome_MainActivity_createPoint(JNIEnv *env, jobject jobj) {
    //找到Point类
    jclass j_clzz = env->FindClass("com/jni/dome/Point");
    //获取构造方法的jmethodID
    jmethodID j_mid = env->GetMethodID(j_clzz, "<init>", "(II)V");
    //创建Point对象
    jobject point = env->NewObject(j_clzz, j_mid, 23, 34);

    //获取Point对象中x的jfieldID
    jfieldID j_fid = env->GetFieldID(j_clzz, "x", "I");
    //修改x的值
    env->SetIntField(point, j_fid, 18);
    //获取方法的jmethodID
    jmethodID j_set_y_mid = env->GetMethodID(j_clzz, "setY", "(I)V");
    //调用setY方法修改y的值
    env->CallVoidMethod(point, j_set_y_mid, 18);
    return point;
}
  • Java 和 C/C++数据类型转换
public class MainActivity extends AppCompatActivity {
    static {
        System.loadLibrary("native-lib");
    }
   
   public native String changeString(String str);

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);          
        String string = changeString("Delusion");        
        Log.e("---->",string);
    }

}
extern "C"
JNIEXPORT jstring JNICALL
Java_com_ndk_dome_MainActivity_changeString(JNIEnv *env, jobject thiz, jstring str) {
    //将jstring转为char类型数组
    const char *c_str = env->GetStringUTFChars(str, 0);
    c_str = "new string";
    //将char类型数组转为jstring类型
    return env->NewStringUTF(c_str);
}
  • C/C++ 中打印Android log
__android_log_print(ANDROID_LOG_ERROR,"TAG","-----> Log <-----");

日志等级

ANDROID_LOG_VERBOSE
ANDROID_LOG_DEBUG
ANDROID_LOG_INFO
ANDROID_LOG_WARN
ANDROID_LOG_ERROR

以上就是Java和C/C++相互调用的基础案例;