Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Android通过jni调用本地c/c++接口方法总结

Android通过jni调用本地c/c++接口方法总结

作者头像
杨永贞
发布于 2022-11-21 02:15:15
发布于 2022-11-21 02:15:15
2.5K00
代码可运行
举报
运行总次数:0
代码可运行

网上有网友问android的原生应用,上层java代码如何通过jni调用本地的c/c++接口或第三方动态库 ?之前搞过android应用开发和底层c/c++接口开发都是一个人搞定,觉得还是蛮简单的。其实没啥难度,如果觉得难只是因为你没有经历过,只要搞过一遍基本就记住了。这里总结下方法留作备忘,同时分享给有需要的小伙伴。

网上这方面介绍的文章有很多,但都较凌乱或者不够系统,啰里啰唆一大堆前戏,不如实战来的快。长篇大论真没必要,我们只想上手用,先用起来再说,其他需要了再深入。为了做到通俗易懂和尽可能的简单,直接举例说明吧。举一个详细的例子从头到尾完整实现一遍,保证看一遍就会上手会用。

总体方法就是通过JNI(Java Native Interface),即 Java 本地接口,使得 Java 与本地其他类型语言如 C、C++交互。也就是在 Java 中调用 C/C++ 代码,或者在 C/C++ 中调用 Java 代码,下面一一详细介绍。

调用其他三方动态库的使用过程,可以参见博主的另一篇文章介绍:

支付宝二维码脱机认证库在android的app下测试过程记录_特立独行的猫a的博客-CSDN博客

java调用JNI总结_特立独行的猫a的博客-CSDN博客

目标任务

举例需求如下:MAC生成算法保密,是在c层实现的。java层业务需调用底层c语言实现的接口。

Java层需要的接口如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
byte[] calcDesMac64(byte[] key, byte[] data, int len)

环境准备

首先需要有编译c代码的环境,就是一套工具链和脚本。平常通过AndroidStudio搞android原生开发的都倒弄过环境,需要下载sdk开发包。但是如果涉及c/c++接口的本地代码,则还需要下载安装NDK,是 Android 的一个底层Native开发包。关于NDK的详细介绍这里就不科普了,文末有相关知识的引用,感兴趣的可以看看,我是觉得有点儿啰嗦。

下载安装NDK的方法,这里也不多介绍了,下载安装就是了。

实现步骤

一、定义java层需要用到的类和接口

首先需要定义好java层需要用到的类和接口,一旦定义好不能轻易变。由于是特殊的与底层交互的接口,最好单独指定一个特殊的包名称,并给出实现类的封装。如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package com.mypackage.jni;

public class CalcMac {

    public static String TAG = CalcMac.class.getSimpleName();

    static {
        System.loadLibrary("CalcMac");
    }

    public static synchronized byte[] calcDesMac64(byte[] key, byte[] data, int len){
        return Native_JniCalcDesMac64(key,data,len);
    }

    private static native final long Native_JniTest();
    private static native final byte[] Native_JniCalcDesMac64(byte[] key,byte[] data,int len);
}

这一步操作比较简单,接下来就是需要把用到的CalcMac.so搞出来了。否则代码也编译不过呀,会提示System.loadLibrary找不到动态库CalcMac.so。

二、c层接口封装

这是关键的一步,需要处理好java代码和c代码之间的类型转换。关于java层和c层接口参数转换的知识,可以自行查阅资料或查看头文件,后面有机会单独总结下。Native层的c代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// Native层接口封装
static jbyteArray Jni_CalcDesMac64(JNIEnv *env, jobject obj, jbyteArray key,jbyteArray data,jint len){
	
	U08 mac[8];
	jbyte * pkey = NULL; 
	jbyte * pbuf = NULL; 
	
	pkey = (jbyte *)(*env)->GetByteArrayElements(env,key, NULL);
	pbuf = (jbyte *)(*env)->GetByteArrayElements(env,data, NULL);

    //c代码接口调用
CurCalc_DES_MAC64( (SINGLE_DES_ENCRYPTION|ZERO_CBC_IV), (U08*)pkey, 0, (U08*)pbuf, len ,  mac );
	
	jbyteArray jarrMac =(*env)->NewByteArray(env,8);

	(*env)->SetByteArrayRegion(env,jarrMac, 0,8,(jbyte*)mac);

	return jarrMac;

}

// Native层接口封装
static jlong Jni_Test(JNIEnv *env, jobject obj)
{
	U32 rcode = 0;

	LOGD(">>> ..%s..%d..enter",__FUNCTION__,__LINE__);

	LOGD(">>> ..%s..%d.exit",__FUNCTION__,__LINE__);
	return rcode;

}

这样就完了吗?肯定不行啦,至少我们需要的CalcMac.so还没有生成。不过以下的步骤都是模板套路了,按照格式书写就行了。

三、接口注册

这一步也是很关键的部分,没有注册上层是无法调用底层接口的。这部分内容其实也很简单,就是模板套路,按照一定的要求书写就行了。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//定义批量注册的数组,是注册的关键部分
static const JNINativeMethod gMethods[] = { 
    { "Native_JniTest","()J",	(void*)Jni_Test},
	{ "Native_JniCalcDesMac64","([B[BI)[B",	(void*)Jni_CalcDesMac64}
};

// extern "C" {
	JNIEXPORT jint JNI_OnLoad(JavaVM* vm,void *reserved)
{
	JNIEnv *env =NULL;
	jint result = -1;
	static const char* kClassName= "com/mypackage/jni/CalcMac";
	jclass clazz;
	
	debug_level = 5;
	
	LOGD(">>> ..%s..%d..enter",__FUNCTION__,__LINE__);
	
	if( (*vm)->GetEnv(vm,(void**)&env,JNI_VERSION_1_4) != JNI_OK )
	{
		return result;
	}

	clazz = (*env)->FindClass(env,kClassName);
	if( clazz == NULL )
	{
		LOGE("%d..Can't find class %s!\n",__LINE__, kClassName);
		return -1;
	}

	//FindTradeInfoFields(env);

	if( (*env)->RegisterNatives( env,clazz, gMethods, sizeof(gMethods)/sizeof(gMethods[0]) ) != JNI_OK )
	{
		LOGE("Failed registering methods for %s!\n", kClassName);
		return -1;
	}
	LOGD(">>> ..%s..%d.exit",__FUNCTION__,__LINE__);
	return JNI_VERSION_1_4;
}
// }

四、脚本编译

这一步也很关键,没有它前面步骤的努力也白费,通过这一步方能最终实现我们要的CalcMac.so,这一步也是模板套路。有些时候之所以觉得难,是因为你没有经历过。其实没什么特别难的事。

把需要编译的c代码或需要链接的三方库,写到编译脚本里组织下。

Android.mk文件如下:

Application.mk 文件内容如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
APP_BUILD_SCRIPT := Android.mk
APP_ABI := armeabi-v7a
APP_PLATFORM := android-22

编译脚本如下,就一行指令:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
ndk-build NDK_PROJECT_PATH=. NDK_APPLICATION_MK=Application.mk

--copy--
cp ./libs/arm64-v8a/libCalcMac.so D:\GitAsWork\MyAppPrj\app\src\main\jniLibs\arm64-v8a\libCalcMac.so

经过以上步骤,如果没有编译错误的话能够成功生成我们需要的libCalcMac.so 。如何使用?肯定不能随便放一个目录位置了,需要放置到特定的目录里。 

五、如何使用

如果上述步骤成功生成了对应平台需要的so动态库,接下来使用就简单啦。把so库放置到对应的目录,让项目代码整体编译通过。so库放置的位置是有要求的,剩下都是一些配置的工作。

六、build.gradle中的配置

已经打好的so库文件或者以来第三方库的so文件,首先需要将so库文件放置在libs目录或者自定义的目录中(如有些人喜欢放在src目录下的jniLibs目录中),然后再module下的build.gradle中引用so库,具体如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
android {
    //...
    defaultConfig {
       //version,versioncode,applicationID等信息
        ndk {
            //针对自己项目的架构对应添加相应的so目录
            //目前的手机架构基本上都是arm架构,x86的基本上没有,基本上是平板
            abiFilters "armeabi-v7a",//arm架构的32位
                    "armeabi",//十年前的手机CPU架构,基本上已经不存在了
                    "arm64-v8a",//arm架构的64位
                    "x86",//x86架构的 32位
                    "x86_64"//x86架构的64位
        }
    }
      //省略其他配置...
    sourceSets {
        main {
            //这里的libs需要替换成你放置so库的目录,比如jniLibs
            jniLibs.srcDirs = ['libs']
        }
    }
}

dependencies {
   //省略其他配置....
}

需要注意的是看你的android系统的平台版本和内核版本,比如是32位的还是64位的,是armeabi-v7a还是arm64-v8a,这些都是有区别的,不同的类别编译出来的动态库不通用。以上示例,把libCalcMac.so放置到myappprj/ app / libs / armeabi目录下,就可以编译打包通过啦。

七、其他说明和注意事项

需要注意的地方,定义批量注册的数组是注册的关键部分。

其中的 "()I" 是干啥的?如果接口不带参数,所以签名是()I,如果我的接口方法带两个参数,这里签名应该是 (II)I, I表示的是int类型,否则java层通过JNI调用时,会报找不到方法。括号里面的是参数类型对应的符号,括号外面的返回值类型对应的符号。

JNI_Onload函数,当启动程序的时候会加载动态库文件,就会调用这个函数。接着在onload函数中,注册了nativemethods。 methods数组中第一个和第三个参数比较好理解,那么第二个参数呢?

其实第二个参数可以参考头文件,一模一样拉过来就好了。其中的意思就是()里的表示函数的参数,()表示没有参数,(II)表示两个参数,都是int型。后面跟的Ljava/lang/String表示返回值是String类型的,需要注意的是long类型对应的符号是"J",可不是想当然的"L",I表示的是int类型。关于这块儿文末链接里有对照表文章可以查看。

还有个地方要注意了,包名一定不能错,(*env)->FindClass(env,kClassName)这里kClassName包名一定得对应。

另外一点需注意的是上层应用层注意load顺序,先load第三方库,再load自己的库。

底层完整代码实现

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// jni_CalcMac.c
#include "CurCalc_DES.h"
#include <jni.h>
#include <android/log.h>
static const char *TAG =	"CalcMac_JNI";

static unsigned int debug_level = 5;
//#define PATH_CLASS_NAME    "com/mypackage/jni/CardNc"


#define LOGD(fmt, args...) \
		do{ if (debug_level >= 3) __android_log_print(ANDROID_LOG_DEBUG,  TAG, fmt, ##args); } while(0)

#define LOGI(fmt, args...) \
		do{ if (debug_level >= 2) __android_log_print(ANDROID_LOG_INFO,  TAG, fmt, ##args); } while(0)

#define LOGE(fmt, args...) \
		do{ if (debug_level >= 1) __android_log_print(ANDROID_LOG_ERROR,  TAG, fmt, ##args); } while(0)

#define LOGA(fmt, args...) \
		do{ if (debug_level >= 0) __android_log_print(ANDROID_LOG_ERROR,  TAG, fmt, ##args); } while(0)
			

//unsigned int debug_level = 5;

static jbyteArray Jni_CalcDesMac64(JNIEnv *env, jobject obj, jbyteArray key,jbyteArray data,jint len){
	
	U08 mac[8];
	jbyte * pkey = NULL; 
	jbyte * pbuf = NULL; 
	
	pkey = (jbyte *)(*env)->GetByteArrayElements(env,key, NULL);
	pbuf = (jbyte *)(*env)->GetByteArrayElements(env,data, NULL);

    CurCalc_DES_MAC64( (SINGLE_DES_ENCRYPTION|ZERO_CBC_IV), (U08*)pkey, 0, (U08*)pbuf, len ,  mac );
	
	jbyteArray jarrMac =(*env)->NewByteArray(env,8);

	(*env)->SetByteArrayRegion(env,jarrMac, 0,8,(jbyte*)mac);

	return jarrMac;

}

static jlong Jni_Test(JNIEnv *env, jobject obj)
{
	U32 rcode = 0;

	LOGD(">>> ..%s..%d..enter",__FUNCTION__,__LINE__);

	LOGD(">>> ..%s..%d.exit",__FUNCTION__,__LINE__);
	return rcode;

}


//定义批量注册的数组,是注册的关键部分
static const JNINativeMethod gMethods[] = { 
    { "Native_JniTest","()J",	(void*)Jni_Test},
	{ "Native_JniCalcDesMac64","([B[BI)[B",	(void*)Jni_CalcDesMac64}
};



// extern "C" {
	JNIEXPORT jint JNI_OnLoad(JavaVM* vm,void *reserved)
{
	JNIEnv *env =NULL;
	jint result = -1;
	static const char* kClassName= "com/mypackage/jni/CalcMac";
	jclass clazz;
	
	debug_level = 5;
	
	LOGD(">>> ..%s..%d..enter",__FUNCTION__,__LINE__);
	
	if( (*vm)->GetEnv(vm,(void**)&env,JNI_VERSION_1_4) != JNI_OK )
	{
		return result;
	}

	clazz = (*env)->FindClass(env,kClassName);
	if( clazz == NULL )
	{
		LOGE("%d..Can't find class %s!\n",__LINE__, kClassName);
		return -1;
	}

	//FindTradeInfoFields(env);

	if( (*env)->RegisterNatives( env,clazz, gMethods, sizeof(gMethods)/sizeof(gMethods[0]) ) != JNI_OK )
	{
		LOGE("Failed registering methods for %s!\n", kClassName);
		return -1;
	}
	LOGD(">>> ..%s..%d.exit",__FUNCTION__,__LINE__);
	return JNI_VERSION_1_4;
}
// }

引用

Android NDK(一)- 认识 NDK - 简书

android ndk_百度百科

NDK 使用入门  |  Android NDK  |  Android Developers

Android NDK开发(一) - 简书

Android NDK编程_Karson Tiger的博客-CSDN博客

JNI 数据类型与 Java 数据类型的映射关系_Martin89的博客-CSDN博客

JNI的数据类型及映射关系详解_普通网友的博客-CSDN博客_jni映射

Android NDK 从入门到精通(汇总篇)_阿飞__的博客-CSDN博客

JNI基础:JNI数据类型和类型描述符_阿飞__的博客-CSDN博客

java调用JNI总结_特立独行的猫a的博客-CSDN博客

支付宝二维码脱机认证库在android的app下测试过程记录_特立独行的猫a的博客-CSDN博客

安装及配置 NDK 和 CMake  |  Android 开发者  |  Android Developers

armeabi-v7a armeabi arm64-v8a区别_ChampionDragon的博客-CSDN博客_armeabi-v7a

字节跳动总监知乎1716赞的AndroidFramework开发笔记+腾讯技术团队出品的《Android Framework 开发揭秘》免费领取

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-11-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
华为模拟器ensp怎么安装_华为模拟器怎么安装
eNSP是一款由华为提供的免费的图形化网络仿真工具平台,它将完美呈现真实设备实景(包括华为最新的ARG3路由器和X7系列的交换机),支持大型网络模拟,让你有机会在没有真实设备的情况下也能够实验测试,学习网络技术。
全栈程序员站长
2022/09/20
4.4K0
华为模拟器ensp怎么安装_华为模拟器怎么安装
华为模拟器ensp怎么安装_华为游戏模拟器电脑版
分别以管理员身份安装VirtualBox-5.2.26-128414、WinPcap_4_1_3、Wireshark-win64-1.12.4三款软件,默认安装到C:\Program Files目录下即可;
全栈程序员站长
2022/11/07
3.3K0
华为模拟器ensp怎么安装_华为游戏模拟器电脑版
eNSP华为网络设备仿真平台下载与安装
eNSP 作为模拟器主体,需要对应版本的 VirtualBox 和 WinPcap 提供虚拟环境,Wireshark 用于实验当中测试抓取数据包使用。故必须先安装好WinPcap、Wireshark 、VirtualBox 后才能安装eNSP。
90后小陈老师
2024/04/19
1.6K0
eNSP华为网络设备仿真平台下载与安装
华为模拟器ensp怎么安装_模拟器下载手机版
ENSP安装,打开图中标识的文件夹找到eNSP_Setup安装包,以管理员方式打开,选择语言,点击确定
全栈程序员站长
2022/11/04
5.6K0
华为模拟器ensp怎么安装_模拟器下载手机版
华为模拟器eNSP下载与安装教程(面向小白)「建议收藏」
链接:https://pan.baidu.com/s/1XqSfHetChnmiaNtHpjS1oA 提取码:4455
全栈程序员站长
2022/11/04
40.2K0
超详细的ENSP安装教程附下载地址「建议收藏」
下载完成后解压,可以得到如下图四个文件, 下载地址:链接:链接:https://pan.baidu.com/s/1NSlijNe1QsnCqv8BAkQC-w?pwd=p04e 提取码:p04e
全栈程序员站长
2022/11/04
11.6K1
华为模拟器 eNSP安装教程「建议收藏」
随着华为网络设备的日渐普遍,华为网络设备的使用越来越多,学习华为网络路由知识的人也越来越多,华为提供的eNSP模拟软件,能够很好的为初学者提供很好的模拟学习软件。
全栈程序员站长
2022/11/04
3.2K0
华为模拟器 eNSP安装教程「建议收藏」
Win11 安装eNSP避坑指南
关于Windows11 24H2无法使用ENSP中AR报错40问题解决,需要升级到26100.3624的版本。两个升级办法: 1、如果网络条件好,在线获取更新即可 2、如果在线获取有问题,可以去微软下载离线更新包
TechByte
2025/04/09
6630
Win11 安装eNSP避坑指南
如何在Windows 10的VirtualBox中安装macOS High Sierra
无论您是想偶尔在Safari中测试网站还是在Mac环境中试用一些软件,访问虚拟机中最新版本的macOS都是很有用的。不幸的是,您实际上不应该执行此操作,因此,至少可以说,在VirtualBox中运行macOS很难。
程序那些事儿
2023/03/07
5.2K0
如何在Windows 10的VirtualBox中安装macOS High Sierra
Ensp 启动设备AR1失败 错误代码40 41(已解决问题)详细解决过程
今天出一期解决错误代码40 41的问题 相信刚安装的Ensp的小伙伴 很多都出现了这样的问题.
神秘泣男子
2024/06/03
4.3K0
华为ensp安装步骤_网购200多的平板电脑靠谱吗
1.准备四个软件安装包 分别为:WinPcap、VirtualBox、Wireshark、eNSP 有需要的请点击这个连接下载自行下载: https://pan.baidu.com/s/1kOX8o
全栈程序员站长
2022/11/04
1.9K0
华为ensp安装步骤_网购200多的平板电脑靠谱吗
模拟器 | 如何安装ENSP,附上最详细的步骤,含安装软件!
今天给大家带来的是ensp安装相关的资料,因为这两天一直有人在问我有没有下载的链接,虽然我让他们去官网下了,但是说完我还是有点心理负担的,感觉没有满足他的需求。所以今天索性就分享一下吧!
网络技术联盟站
2020/04/30
15.5K2
模拟器 | 如何安装ENSP,附上最详细的步骤,含安装软件!
华为eNSP模拟器设备启动失败解决方案(全)
1.1 VirtualBox主机网卡是否存在、主机网卡的ip段是否为192.168.56.0/255.255.255.0
网络工程师笔记
2022/04/08
15.8K0
华为eNSP模拟器设备启动失败解决方案(全)
安装ensp操作步骤_飞机gps信号受到干扰
前言 最近上课用到eNSP,回到寝室想自己安装一个,加上有同学说安装不上,用不了实验室拿过来的,所以就自己试了一下,现已成功 那就简要的分享下整个的流程 依赖软件
全栈程序员站长
2022/09/30
5060
安装ensp操作步骤_飞机gps信号受到干扰
《熬夜整理》保姆级系列教程-玩转Wireshark抓包神器教程(2)-Wireshark在Windows系统上安装部署
1.宏哥的环境是Windows 10版本 64位系统(32位的同学自己想办法哦),其实宏哥觉得无论在什么平台,多少位,其实安装都是类似的,非常easy的。如下图所示:
北京-宏哥
2023/11/13
1.4K0
华为官网ensp安装包在哪?_手机安装华为路由器的步骤图解
链接:https://pan.baidu.com/s/1d-8dd9qBhpAwxc0UBL8vdg 提取码:df6m
全栈程序员站长
2022/11/04
1.3K0
华为模拟器ensp学习笔记
2.打开vbox中左上角菜单->管理虚拟介质管理,将里面的文件删除(如下图),如果删除不掉就先删除下拉箭头的类似:29a2d98c-4da0-4e86-8e4e-768cc7f81b61.vdi,另外出现任何报错都不要在意,确定即可。(如果虚拟介质管理是空的则跳过这一步)
MIKE笔记
2023/03/23
1.9K0
华为模拟器ensp学习笔记
如何从Windows切换到Linux
微软已经马上准备在2020年1月份终止对Windows 7的支持,这意味着您将不再获得bug修复或安全更新。如果您是Windows 7的最终支持者之一,并且不想陷入一个不安全的系统,则可以选择:升级到Windows 10或完全切换到其他版本。
用户6543014
2020/02/10
4K0
win10锁定计算机命令,锁定Windows 10 PC的10种方法
离开时,锁定Windows 10 PC是保护计算机安全的最佳方法。这不会退出或中断任何正在运行的应用程序,您必须输入PIN或密码才能通过锁定屏幕。您可以通过以下10种方式锁定计算机。
全栈程序员站长
2022/09/16
6.3K0
win10锁定计算机命令,锁定Windows 10 PC的10种方法
如何使用SecureCRT连接华为eNSP模拟器
5、单击“Telnet”选项,勾选“强制每次一个字符模式(R)”,否则无法使用tab键进行补全操作,接着单击“确定”。
宝耶需努力
2022/12/13
4.3K1
如何使用SecureCRT连接华为eNSP模拟器
推荐阅读
相关推荐
华为模拟器ensp怎么安装_华为模拟器怎么安装
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验