Android studio项目添加c++文件

前言

Android studio工程中经常会用到native 方法,方法之一是在libs文件夹中引入so文件,这样需要用到native方法的类直接调用如下方法加载库即可:

System.loadLibrary("test-lib");

如果不想引用so文件,想要直接在AS中实现native方法也是可以的。此时需要在项目中添加cpp文件,且需要做相关的配置,下面详细说明。

概述

环境准备

要想在AS中编译cpp文件,则需要下载NDK和安装cmake工具,否则无法使用。

NDK和cmake的安装很简单,AS中直接就可以处理,操作如下:

Tools->SDK Manager->Android SDK->SDK Tools

按如上点击,找到SDK Tools,就可以看到一系列的工具供安装,如下图是已经安装了NDK和Cmake的。
在这里插入图片描述

创建cpp目录以及cpp文件

在如下目录创建cpp文件夹:

mkdir app/src/main/cpp/

接着在该目录下创建cpp文件:
在这里插入图片描述

在此命名cpp文件名为test.cpp,并添加如下内容:

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

JNIEXPORT jstring JNICALL Java_com_xxx_yyy_MainActivity_startTestJni(
        JNIEnv *env,
        jobject thiz) {
            char *hello = "hello test...";
            return env->NewStringUTF(hello);
        }

如上com_xxx_yyy为包名,而MainActivity则是表示在MainActivity类中使用native方法。

每个类对应一个native文件,有一一对应的关系,这里需要注意。否则运行的时候会报找不到对应的class。

创建CMakeLists.txt 文件

CMakeLists.txt是编译cpp文件的规则,在该文件中会指定cpp文件编译出来的so库名称。创建方法如下:
在这里插入图片描述

在此命名为CMakeLists.txt。在该文件中添加如下内容:

cmake_minimum_required(VERSION 3.4.1)

add_library( # Sets the name of the library.
         test-lib

         # Sets the library as a shared library.
         SHARED

         # Provides a relative path to your source file(s).
         src/main/cpp/test.cpp )

find_library( # Sets the name of the path variable.
          log-lib

          # Specifies the name of the NDK library that
          # you want CMake to locate.
          log )

target_link_libraries( # Specifies the target library.
                   test-lib

                   # Links the target library to the log library
                   # included in the NDK.
                   ${log-lib} )

在add_library中指定so库名称,属性(是否设置为共享库)以及源文件。

build.gradle中配置

打开build.gradle向android/defaultConfig节区追加以下内容:

externalNativeBuild {
    cmake {
        cppFlags ""
    }
}

向android节区追加以下内容:

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

最终配置如下图:
在这里插入图片描述

MainActivity中使用该native方法

先声明对应的native方法,如这里的test.cpp中添加的native方法如下:

Java_com_xxx_yyy_MainActivity_startTestJni

那么我们在MainActivity中声明如下:

public native String startTestJni();

当然,在调用该方法前,还需要加载对应的so库,名称为test-lib,如下:

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

这样就可以使用该native方法了。

如何简化 native方法名

在上面的例子中,native方法名长得可怕,且方法名受包名限制,如下:

JNIEXPORT jstring JNICALL Java_com_xxx_yyy_MainActivity_startTestJni

这样一坨方法名,简直无法接受。

方法名如此受限是因为JNI方法采用了静态注册的方法。聪明如你,这时肯定想到,要想使用简洁不受限的方法名,则需要使用动态注册的方法了。

使用动态注册的方法时,需要在cpp文件中实现JNI_OnLoad方法:

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
    JNIEnv* env = NULL;
    jint result = -1;

    if (vm->GetEnv((void**) &env, JNI_VERSION_1_6) != JNI_OK) {
        return -1;
    }
    assert(env != NULL);

    if (!registerNatives(env)) {
        return -1;
    }

    result = JNI_VERSION_1_6;
    return result;
}

当我们调用System.loadLibrary方法加载so库时,系统就会去查找对应的JNI_OnLoad方法,如果没找到,则默认为静态注册方法,此时直接返回so加载成功的标志。

如果系统找到了JNI_OnLoad方法,但没做相应的方法注册,则此时系统会抛出异常。

动态注册实现

动态注册方法实现源码如下:

//方法名
static JNINativeMethod gMethods[] = {
    { "startTestJni", "()Ljava/lang/String;",(void*) native_startTestJni },
};


static int registerNativeMethods(JNIEnv* env, const char* className,
                                 JNINativeMethod* gMethods, int numMethods) {
    jclass clazz;

    clazz = env->FindClass(className);
    if (clazz == NULL) {
        return JNI_FALSE;
    }
    if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
        return JNI_FALSE;
    }

    return JNI_TRUE;
}


static int registerNatives(JNIEnv* env) {
    //这里需要指定使用该native方法的类名,由包名和类名组成
    const char* kClassName = "com/xxx/yyy/MainActivity";
    return registerNativeMethods(env, kClassName, gMethods,
                                 sizeof(gMethods) / sizeof(gMethods[0]));
}

registerNatives方法最终是在JNI_OnLoad方法中调用。

这里重点说下gMethods的规则:

static JNINativeMethod gMethods[] = {
    { "startTestJni", "()Ljava/lang/String;",(void*) native_startTestJni },
};

其中startTestJni为Java类中定义的Native方法名。

()Ljava/lang/String; 为方法的签名, ()表示该方法无参数, Ljava/lang/String;表示返回值为Java中的String类型。其他类型的使用以及带参如何写,可以网上查找相关知识,有很多。

而(void*) native_startTestJni为Native实现的方法名。这里强制转换成了函数指针。

所以如果采用动态注册的方法,最终的test.cpp文件如下:

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

#include <android/log.h>
static const char *TAG = "test";
#define LOGI(fmt, args...) __android_log_print(ANDROID_LOG_INFO,  TAG, fmt, ##args)
#define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, TAG, fmt, ##args)
#define LOGE(fmt, args...) __android_log_print(ANDROID_LOG_ERROR, TAG, fmt, ##args)


jstring  native_startTestJni(JNIEnv * env, jobject thiz)
{
    char *hello = "hello test ...";
    return env->NewStringUTF(hello);
}

static JNINativeMethod gMethods[] = {
        { "startTestJni", "()Ljava/lang/String;",(void*) native_startTestJni },
};


static int registerNativeMethods(JNIEnv* env, const char* className,
                                 JNINativeMethod* gMethods, int numMethods) {
    jclass clazz;

    clazz = env->FindClass(className);
    if (clazz == NULL) {
        return JNI_FALSE;
    }
    if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
        return JNI_FALSE;
    }

    return JNI_TRUE;
}

static int registerNatives(JNIEnv* env) {
    const char* kClassName = "com/cvte/irsensor/MainActivity";
    return registerNativeMethods(env, kClassName, gMethods,
                                 sizeof(gMethods) / sizeof(gMethods[0]));
}

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
    JNIEnv* env = NULL;
    jint result = -1;

    if (vm->GetEnv((void**) &env, JNI_VERSION_1_6) != JNI_OK) {
        return -1;
    }
    assert(env != NULL);

    if (!registerNatives(env)) {
        return -1;
    }

    result = JNI_VERSION_1_6;
    LOGD("this is JNI_OnLoad for test-lib..");
    return result;
}

结语

此次是要做一个串口服务,引用了一个之前同事开发好的so库,但该库有问题,导致串口通讯一直失败。于是就决定自己写一个。网上资料很多,也踩坑了不少,在此记录,免得下次再次踩坑。

微信公众号

我在微信公众号也有写文章,更新比较及时,有兴趣者可以扫描如下二维码,或者微信搜索【Android系统实战开发】,关注有惊喜哦!

在这里插入图片描述

  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值