Android工程怎么调用C/C++代码(保姆级别,每一步截图+讲解)?

背景

突然想起做了这么久的JNI开发,却没有分享过相关内容。
今天就公司刚做完不久的一个项目来剖析,作为JNI的一个入门小实践。内容会做一些抽象,隐藏掉细节的业务部分。希望对刚学习JNI和要边学边开发的同学有所帮助。

新建工程

第一步就是新建一个C/C++工程:
在这里插入图片描述
一开始会有一个cpp目录,里面默认有CMakeLists.txt和一个.cpp文件:
在这里插入图片描述

拷贝/编写C/C++代码

通常来说C代码是由别的开发小组提供,我们负责写JNI代码,让Java层去调用C/C++的代码。
我们要做的是把C代码copy到cpp这个目录里面。

编写CMake配置文件

然后根据源代码去做一些CMake的配置,我这个项目是使用一个CMake文件去配置所有代码。想要更加灵活布局的话,也可以每个目录写一个Cmake文件,然后合并到根目录的Cmake中。
在这里插入图片描述
C代码的目录:
在这里插入图片描述
这个时候去编译,看能不能通过。
不能通过的话,说明CMake写的有问题,可以参考:Cmake专栏

写Java代码加载动态/静态库

上面搞定之后,可以正式开始写代码了。
写一个Java类用作加载原生库,可以是.so 或者.a。不了解的动态库和静态库的同学可以参考:Linux专栏
在这里插入图片描述
当没有实现C++方法的时候,方法名是报红的。点击:ALT+Enter可以自动生成对应的JNI方法,点击坐标的C++标记即可跳过去:
在这里插入图片描述
它会根据java的包名自动生成JNI方法名,方法名是由Java+包名+类名+方法名组合而成的。

需要注意的是:假如java改了包名或者类名,这个JNI方法也需要修改,不然无法映射链接过来。

java转换c++,c++转java

下面是示例代码,供参考:
C代码:

int ProcessInject(const char *ipeks,  unsigned char *ipek_mkey, char *fkey_fpath, unsigned char *fk_mkey, char *datetime);

JNI代码:

extern "C"
JNIEXPORT jint JNICALL
Java_com_xxx_nativelib_NativeLib_processInject(JNIEnv *env, jobject thiz, jbyteArray ipeks,
                                               jbyteArray ipek_mkey, jbyteArray fkey_fpath,
                                               jbyteArray fk_mkey, jbyteArray date_time) {
    jbyte *ipeks_c = env->GetByteArrayElements(ipeks, nullptr);
    jbyte *ipek_mkey_c = env->GetByteArrayElements(ipek_mkey, nullptr);
    jbyte *fkey_fpath_c = env->GetByteArrayElements(fkey_fpath, nullptr);
    jbyte *fk_mkey_c = env->GetByteArrayElements(fk_mkey, nullptr);
    jbyte *datetime_c = env->GetByteArrayElements(date_time, nullptr);

    int result = ProcessInject((const char *) ipeks_c, (unsigned char *) ipek_mkey_c,
                           (char *) fkey_fpath_c, (unsigned char *) fk_mkey_c, (char *) datetime_c);
    // Release resources
    env->ReleaseByteArrayElements(ipeks, ipeks_c, 0);
    env->ReleaseByteArrayElements(ipek_mkey, ipek_mkey_c, 0);
    env->ReleaseByteArrayElements(fkey_fpath, fkey_fpath_c, 0);
    env->ReleaseByteArrayElements(fk_mkey, fk_mkey_c, 0);
    env->ReleaseByteArrayElements(date_time, datetime_c, 0);

// 返回结果给 Java
    return result;
}

int类型的无需转换,直接使用即可。
Java的byte类型需要先转为jbyte* 类型,传入C/C++代码的时候根据实际情况强制。其它类型的话,
可以点jni.h头文件进去查看:
在这里插入图片描述
或者直接查看JNI关机文档,需要一定英文水平。

 jbyte *ipeks_c = env->GetByteArrayElements(ipeks, nullptr);
 //使用ipeks_c ,dosomething 
 ...
  // Release resources
    env->ReleaseByteArrayElements(ipeks, ipeks_c, 0);

传入并使用对象类型的时候,记得用完立刻释放,不然有时候需要循环的业务逻辑会消耗掉JNI可以开辟的空间,听说最多是五百个对象。没有实际考察和测试,供参考。

至于JNI使用的栈内存的原理我在公司部分已经做过一期技术分享。后续有时间会做相应的分享到网上,可以先关注一波这个账号。

关于ReleaseByteArrayElements的最后一个参数,绝大部分场景使用0就OK,意思是他会把C/C++源码对这个变量所做的修改刷新回去给Java层。

还有一个点要注意,就是Java层的byte是有符号的,可以是负数,C/C++层的unsigned char是无符号的。写业务的时候需要注意这个点,要了解清楚。

native层打印日志

打印日志可以直接copy这个,Google那边薅过来的:

/*
 * Copyright (C) 2015 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
#ifndef NATIVE_AUDIO_ANDROID_DEBUG_H_H
#define NATIVE_AUDIO_ANDROID_DEBUG_H_H
#include <android/log.h>

#if 1

#define MODULE_NAME "xxxxx"
#define LOGV(...) \
  __android_log_print(ANDROID_LOG_VERBOSE, MODULE_NAME, __VA_ARGS__)
#define LOGD(...) \
  __android_log_print(ANDROID_LOG_DEBUG, MODULE_NAME, __VA_ARGS__)
#define LOGI(...) \
  __android_log_print(ANDROID_LOG_INFO, MODULE_NAME, __VA_ARGS__)
#define LOGW(...) \
  __android_log_print(ANDROID_LOG_WARN, MODULE_NAME, __VA_ARGS__)
#define LOGE(...) \
  __android_log_print(ANDROID_LOG_ERROR, MODULE_NAME, __VA_ARGS__)
#define LOGF(...) \
  __android_log_print(ANDROID_LOG_FATAL, MODULE_NAME, __VA_ARGS__)

#else

#define LOGV(...)
#define LOGD(...)
#define LOGI(...)
#define LOGW(...)
#define LOGE(...)
#define LOGF(...)
#endif

#endif  // NATIVE_AUDIO_ANDROID_DEBUG_H_H

CMake文件里面记得确认已经引入log库。Google爸爸已经在NDK包里面提供,直接引入即可。

在这里插入图片描述
日志的使用示例:在这里插入图片描述

Android去调用Java层的native方法

上面这些搞定之后,直接在MainActivity里面new出前面创建的对象:在这里插入图片描述
调用native函数即可。
在这里插入图片描述

对外提供.so/.a库 + jar包

假如我们在CMake里面写了SHARED的话,编译之后,生成的是动态库,.so文件。
在这里插入图片描述
我们通过JNI代码调用的C/C++源码会被打包成一个.so库。也就是说,我们JNI写完之后提供给别人的就是这个.so库,再加上Java文件,就是这个:
在这里插入图片描述
因为JNI需要类名、包名完全一致,为了避免用户改了它们,导致调用不到JNI代码。我们把所有相关的Java代码打包为一个jar文件包。
不叫了解怎么通过Android studio打包jar文件包的可以参考:jar生成

那么.so在哪里呢?

在这里插入图片描述

检查APK里面是否已经被正常包含.so/.a

再看看APK里面是否已经把动态库文件打包进去了
在这里插入图片描述

完成

好了,这就是写一个JNI工程需要掌握的基本技能。
希望能帮助到大家。

  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
要在Android Studio中编写C/C++代码,你需要进行以下步骤: 1. 创建一个新的Android Studio项目。 2. 在你的项目中创建一个新的C/C++源文件。你可以使用菜单File->New->C/C++ Source File来创建源文件。 3. 在你的源文件中编写C/C++代码。 4. 在你的项目中创建一个新的JNI头文件。你可以使用菜单File->New->JNI/C++ Header File来创建头文件。 5. 在你的JNI头文件中声明你的C/C++函数和变量。例如,如果你的C/C++源文件中有一个函数叫做myFunction,你可以在你的JNI头文件中这样声明它: ```c++ JNIEXPORT void JNICALL Java_com_example_myapp_MyClass_myFunction(JNIEnv *env, jobject obj); ``` 请注意,这里的Java_com_example_myapp_MyClass_myFunction是根据你的Java类和函数名称来自动生成的。你需要根据你的实际项目进行修改。 6. 在你的Java代码中,使用JNI接口调用你的C/C++函数。例如,如果你的Java类是MyClass,你可以在它的某个方法中这样调用你的C/C++函数: ```java public void myMethod() { myFunction(); } ``` 7. 在你的项目中配置NDK环境。你需要下载NDK并在你的项目中配置NDK路径。你可以使用菜单File->Project Structure->SDK Location来配置NDK路径。 8. 在你的项目中配置CMake。你需要创建一个CMakeLists.txt文件来告诉Android Studio如何编译你的C/C++代码。你可以使用以下模板来创建CMakeLists.txt文件: ``` cmake_minimum_required(VERSION 3.10.2) project(myproject C CXX) add_library(mylib SHARED mysource.cpp) target_link_libraries(mylib log) ``` 请注意,这里的mysource.cpp和mylib是根据你的实际项目进行修改。 9. 运行你的项目并测试你的C/C++代码。 请注意,以上步骤只是一个基本的示例,你需要根据你的实际项目需求进行修改。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

林树杰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值