美文网首页
Android 使用cmake进行NDK开发

Android 使用cmake进行NDK开发

作者: xulj100 | 来源:发表于2020-07-23 20:42 被阅读0次

1、什么是NDK

  • 定义:Native Development Kit,NDK是Google开发的一套开发和编译工具集。
  • 作用:快速开发C、 C++的动态库,并自动将so和应用一起打包成 APK,
    即可通过 NDK在 Android中 使用 JNI与本地代码(如C、C++)交互。

2、什么是JNI

JNI 全称 Java Native Interface,Java 本地化接口,可以通过 JNI 调用系统提供的 API。
JNI是一套编程接口,用来实现Java代码与本地的C/C++代码进行交互。

3、什么是cmake

CMake是一个跨平台的编译(Build)工具,可以用简单的语句来描述所有平台的编译过程。
CMake 允许开发者编写一种平台无关的 CMakeList.txt 文件来定制整个编译流程,然后再根据目标用户的平台进一步生成所需的本地化 Makefile 和工程文件。

4、NDK项目创建

Android studio2.3后就开始支持cmake,个人推荐使用cmake进行NDK开发。

  • 首先新建项目选择选择模板时选择"Native C++ ",就会自动创建对应的C++文件,CMakeLists.txt配置文件,build.gradle会加入一些配置信息。
  • 如果你的的项目已经创建过了,你可以把cpp文件夹、gradle配置信息、CMakeLists.txt文件拷贝到对应目录。


    image01.png

build.gradle配置信息

android {
    compileSdkVersion 29

    defaultConfig {
        applicationId "com.hy.myapp"
        minSdkVersion 19
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

        ndk {
            abiFilters 'armeabi'
        }

        externalNativeBuild {
            cmake {
                cppFlags "-std=c++11"
            }
        }
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }

    externalNativeBuild {
        cmake {
            path "CMakeLists.txt"
            version "3.10.2"
        }
    }
}

5、cmake语法

# 指定cmake的最小版本 
cmake_minimum_required(VERSION 3.4.1)

# 搜索当前目录下的所有.cpp文件
aux_source_directory(./src/main/cpp DIR_CPP)

message(${CMAKE_ANDROID_ARCH_ABI})
message(${CMAKE_SOURCE_DIR})  

# 指定头文件目录
include_directories(src/main/cpp/ffmpeg3/include)
include_directories(src/main/cpp/include)

add_library( # 生成库的名字
        native-lib

        # 生成动态库或共享库
        SHARED

        # 将以下的C++文件编译到动态库
        src/main/cpp/native-lib.cpp
        ${DIR_CPP}
       )

# 生成静态库
# 静态库在程序编译时会被连接到目标代码中,程序运行时将不再需要该静态库。
# add_library(native-static-lib STATIC native-static-lib.cpp)

set(lib_src_dir  ${CMAKE_SOURCE_DIR}/src/main/jniLibs/armeabi)

# 添加其他预构建的库
add_library(avcodec-lib SHARED IMPORTED)
set_target_properties(avcodec-lib PROPERTIES IMPORTED_LOCATION ${lib_src_dir}/libavcodec.so)

add_library(avdevice-lib SHARED IMPORTED)
set_target_properties(avdevice-lib PROPERTIES IMPORTED_LOCATION ${lib_src_dir}/libavdevice.so)

add_library(avfilter-lib SHARED IMPORTED)
set_target_properties(avfilter-lib PROPERTIES IMPORTED_LOCATION ${lib_src_dir}/libavfilter.so)

add_library(avformat-lib SHARED IMPORTED)
set_target_properties(avformat-lib PROPERTIES IMPORTED_LOCATION ${lib_src_dir}/libavformat.so)

add_library(avutil-lib SHARED IMPORTED)
set_target_properties(avutil-lib PROPERTIES IMPORTED_LOCATION ${lib_src_dir}/libavutil.so)

add_library(swresample-lib SHARED IMPORTED)
set_target_properties(swresample-lib PROPERTIES IMPORTED_LOCATION ${lib_src_dir}/libswresample.so)

add_library(swscale-lib SHARED IMPORTED)
set_target_properties(swscale-lib PROPERTIES IMPORTED_LOCATION ${lib_src_dir}/libswscale.so)

# 查找到指定的预编译库,并将它的路径存储在变量中
find_library(
        log-lib

        log)

# 设置 target 需要链接的库
target_link_libraries( # 目标库
        native-lib

        # 目标库需要链接的库
        avcodec-lib
        avdevice-lib
        avfilter-lib
        avformat-lib
        avutil-lib
        swresample-lib
        swscale-lib
        # log-lib 是上面 find_library 指定的变量名
        ${log-lib})

gradle build 详细错误信息显示,可以使用以下命令

gradlew compileDebug --stacktrace
gradlew compileDebug --stacktrace -info
gradlew compileDebug --stacktrace -debug
(推荐) gradlew compileDebugSources --stacktrace -info

6、JNI语法

  • java 调用C++方法
public class TestJni {

    private static final String TAG =TestJni.class.getSimpleName();

    static {
        System.loadLibrary("native-lib");
    }

    public static native String stringFromJNI();
}
extern "C" {
JNIEXPORT jstring JNICALL
Java_com_hy_learn_TestJni_stringFromJNI(JNIEnv *env, jclass clazz) {
    std::string hello = "hello world from C++";
    return env->NewStringUTF(hello.c_str());
}
}
  • C++ 调用Java方法
/**     对照表
     *  |JAVA             |JNI
     *  |boolean          |Z
     *  |byte             |B
     *  |char             |C
     *  |short            |S
     *  |int              |I
     *  |long             |L
     *  |float            |F
     *  |double           |D
     *
     *  |String           |Ljava/lang/String;
     *  |Class            |Ljava/lang/Class;
     *  |Throwable        |Ljava/lang/Throwable;
     *  |int[]            |[I
     *  |byte[]           |[B
     *  |Object[]         |[Ljava/lang/Object;
     *
     */
  • jni访问java类字段
/**
 * 实体类
 */
public class UserBean {
    public String id;
    public String name;
    public String pwd;
    public String tel;
    public int age;
    public static int score=100;

    public UserBean(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public static void plusFun(int x,int y){
        int sum=x+y;
        Log.d(" static function --",String.valueOf(sum));
    }

    public void getUser(String name,int age,String tel){
        Log.d("userBean ----",name+ " : "+age+ " : "+tel);
    }

    public static int getUserInfo(byte[]data,int length){
        for(byte b:data){
            Log.d("data ----",String.valueOf(b));
        }
        return length;
    }

}
  //jni访问java类字段
  public static native void accessField(UserBean userBean);
//------------  JNI修改类的属性字段     -----------------
extern "C"
JNIEXPORT void JNICALL
Java_com_hy_learn_TestJni_accessField(JNIEnv *env, jclass clazz, jobject user) {
    jclass cls = env->GetObjectClass(user);
    jfieldID jfId = env->GetFieldID(cls, "name", "Ljava/lang/String;");
    jfieldID jfId_age = env->GetFieldID(cls, "age", "I");
    jstring str=env->NewStringUTF("my name is C++");
    env->SetObjectField(user,jfId,str);
    env->SetIntField(user,jfId_age,16);
}
  • jni 访问 java类静态字段
 //jni访问java类静态字段
 public static native void accessStaticField(UserBean userBean);
//------------ JNI修改类静态属性字段    -----------------
extern "C"
JNIEXPORT void JNICALL
Java_com_hy_learn_TestJni_accessStaticField(JNIEnv *env, jclass thiz, jobject user) {
    jclass cls = env->GetObjectClass(user);
    jfieldID jfId = env->GetStaticFieldID(cls, "score", "I");
    int score=env->GetStaticIntField(cls,jfId);
    env->SetStaticIntField(cls,jfId,score-1);
}
  • jni 调用 java 类方法
  //jni 调用 java 类方法
 public static native void callMethod(UserBean userBean);
//---------------- JNI调用类方法   -----------------
extern "C"
JNIEXPORT void JNICALL
Java_com_hy_learn_TestJni_callMethod(JNIEnv *env, jclass clazz, jobject user) {
    jclass cls = env->GetObjectClass(user);
    jmethodID mid=env->GetMethodID(cls,"getUser","(Ljava/lang/String;ILjava/lang/String;)V");
    jstring name = env->NewStringUTF("leo");
    jstring tel = env->NewStringUTF("10086");
    env->CallVoidMethod(user,mid,name,20,tel);
}
  • jni 调用 java 类静态方法
// jni调用java类静态方法
public static native void callStaticMethod(UserBean userBean);
//---------------- JNI调用类静态方法   -----------------
extern "C"
JNIEXPORT void JNICALL
Java_com_hy_learn_TestJni_callStaticMethod(JNIEnv *env, jclass clazz, jobject user) {
    jclass cls = env->GetObjectClass(user);
    jmethodID mid=env->GetStaticMethodID(cls,"getUserInfo","([BI)I");
    int size=6;
    char *c_str ="011011";
    jbyteArray array=env->NewByteArray(size);
    for(int i=0;i<size;i++){
        env->SetByteArrayRegion(array,0,strlen(c_str), (jbyte *) c_str);
    }
    env->CallStaticIntMethod(cls,mid,array,size);
}
  • jni通过接口参数回调
 // 接口作为参数
public interface ICallback {
     void onSuccess(String msg);
     void onFail(String error);
}
 // jni通过接口参数回调
public static native void callbackMethod(ICallback callback);
//---------------- JNI主线程调用   -----------------
extern "C"
JNIEXPORT void JNICALL
Java_com_hy_learn_TestJni_callbackMethod(JNIEnv *env, jclass clazz, jobject callback) {
    jclass cls = env->GetObjectClass(callback);
    jmethodID mid_success=env->GetMethodID(cls,"onSuccess","(Ljava/lang/String;)V");
    jmethodID mid_fail=env->GetMethodID(cls,"onFail","(Ljava/lang/String;)V");
    jstring msg=env->NewStringUTF("C++ success");
    jstring error=env->NewStringUTF("C++ fail");
    env->CallVoidMethod(callback,mid_success,msg);
    env->CallVoidMethod(callback,mid_fail,error);
}
// --------     调用  -------------
  TestJni.callbackMethod(object : ICallback {
            override fun onSuccess(msg: String?) {
                Logger.d("onSuccess  -----", msg)
            }

            override fun onFail(error: String?) {
                Logger.d("onFail   --------", error)
            }
        })
  • jni 子线程通过接口参数回调java类方法
//jni子线程通过接口参数回调java类方法
public static native void callbackChildThread(ICallback callback);
static jobject threadObject;
static jmethodID threadMethod;

//声明一个线程
pthread_t childThread;
JavaVM *jvm;
//-- 在加载动态库时会去调用 JNI_Onload 方法,可以得到 JavaVM 实例对象 --
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
    JNIEnv *env;
    jvm = vm;
    if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
        return -1;
    }
    return JNI_VERSION_1_6;
}

//定义一个线程的回调
void *threadCallback(void *) {
    JNIEnv *env= nullptr;
    if(jvm->AttachCurrentThread(&env, nullptr)==0){
        jstring msg=env->NewStringUTF("C++ childThread success");
        env->CallVoidMethod(threadObject,threadMethod,msg);
        jvm->DetachCurrentThread();
    }
    //执行线程完毕之后,退出线程
    pthread_exit(&childThread);
}

//---------------- JNI子主线程调用   -----------------
extern "C"
JNIEXPORT void JNICALL
Java_com_hy_learn_TestJni_callbackChildThread(JNIEnv *env, jclass clazz, jobject callback) {
    threadObject=env->NewGlobalRef(callback);
    jclass cls = env->GetObjectClass(callback);
    threadMethod=env->GetMethodID(cls,"onSuccess","(Ljava/lang/String;)V");
    pthread_create(&childThread, nullptr, threadCallback, nullptr);
}

  • jni 访问 java 类构造器返回对象
//jni访问java类构造器返回对象
public static native UserBean callConstructor();
//---------------- JNI访问Java构造器    -----------------
extern "C"
JNIEXPORT jobject JNICALL
Java_com_hy_learn_TestJni_callConstructor(JNIEnv *env, jclass clazz) {
    jclass cls = env->FindClass("com/hy/learn/UserBean");
    jmethodID mId = env->GetMethodID(cls, "<init>", "(Ljava/lang/String;I)V");
    jstring name = env->NewStringUTF("jim from jni");
    jint tel = 18;
    jobject user = env->NewObject(cls, mId, name, tel);
    /** 第二种方法
     *  jobject users=env->AllocObject(cls);
     *  env->CallNonvirtualVoidMethod(users,cls,mId,name,tel);
     */
    return user;
}

7、总结
使用cmake进行NDK开发还是很方便的,也要有代码提示。源码地址:https://github.com/xulj-tech/LearnAndroid/tree/master/learn-jni-lib

相关文章

网友评论

      本文标题:Android 使用cmake进行NDK开发

      本文链接:https://www.haomeiwen.com/subject/ficklktx.html