前言
由于公司最近业务需要,找第三方开发了C++库。而我作为对接人需要提前了解如何去对接C++的接口,于是便有了这篇文章。
说实话,我不太会写概念性的描述,所以便从网上找了一些相关介绍粘贴出来(罪过)。
● NDK
Native Development Kit(NDK)是一系列工具的集合。它提供了一系列的工具,帮助开发者快速开发C/C++的动态库,并能自动将so和Java一起打包成apk。
● JNI
Java Native Interface(JNI)标准是java平台的一部分,JNI是Java语言提供的Java和C/C++相互沟通的机制,Java可以通过JNI调用C/C++代码,C/C++的代码也可以调用java代码。
● JNI与NDK的关系
NDK可以为我们生成了C/C++的动态链接库,JNI是java和C/C++沟通的接口,两者与Android没有半毛钱关系,只因为安卓是java程序语言开发,然后通过JNI又能与C/C++沟通,所以我们可以使用NDK+JNI来实现“Java+C”的开发方式。
● 为什么要NDK开发
NDK开发具有以下优点:
1. 项目需要调用底层的一些C/C++的一些东西(java无法直接访问到操作系统底层(如系统硬件等)),或者已经在C/C++环境下实现了功能代码(大部分现存的开源库都是用C/C++代码编写的。),直接使用即可。NDK开发常用于驱动开发、无线热点共享、数学运算、实时渲染的游戏、音视频处理、文件压缩、人脸识别、图片处理等。
2. 为了效率更加高效些。将要求高性能的应用逻辑使用C/C++开发,从而提高应用程序的执行效率。但是C/C++代码虽然是高效的,在java与C/C++相互调用时却增大了开销;
3. 基于安全性的考虑。防止代码被反编译,为了安全起见,使用C/C++语言来编写重要的部分以增大系统的安全性,最后生成so库(用过第三方库的应该都不陌生)便于给人提供方便。(任何有效的代码混淆对于会smail语法反编译你apk是分分钟的事,即使你加壳也不能幸免高手的攻击)
4. 便于移植。用C/C++写得库可以方便在其他的嵌入式平台上再次使用。
正文
接下来就直接开始demo的过程吧!
工欲善其事必先利其器,在开发开始的第一步当然是搭建好开发环境,由于现在android开发大部分使用的是android studio(AS),在AS上配置NDK的开发环境是非常简洁的
环境的搭建 ##
- step1
下载NDK开发相关的编译器和组件
- step2
选用NDK
选择好后,AS会在local.properties文件中加入
ndk.dir=/Users/temp/Library/Android/sdk/ndk-bundle //增加
sdk.dir=/Users/temp/Library/Android/sdk
环境基本上是搭建好了,就可以开始开发了。
开发
先写一个java文件
package zhaozx;
/**
* Created by zhaozx on 2017/6/6.
* desc:
*/
public class JniTest {
/**
* 将用C++代码实现,在android代码中调用的方法:获取当前app的包名
* @return
*/
public static native String getPackname();
/**
* 加载so库或jni库,在使用到该库之前加载就行,不一定非要写在这个类内
* 系统自己会判断扩展名是dll还是so,这里加载libapp.so
*/
static {
System.loadLibrary("app");
}
}
接下来是比较重要的步骤,使用javah根据java文件生成c/c++的.h头文件
1.进入app/src/main目录
2.使用 javah -d jni -classpath ./Java 包名+类名(我的是zhaozx.JniTest),生成zhaozx_JniTest.h文件, 此文件在main/jni的目录下, -d jni 就是在当前目录(main)下创建jni文件夹。
我的zhaozx_JniTest.h文件的内容为
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class zhaozx_JniTest */
#ifndef _Included_zhaozx_JniTest
#define _Included_zhaozx_JniTest
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: zhaozx_JniTest
* Method: getPackname(方法名)
* Signature: (Ljava/lang/Object;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_zhaozx_JniTest_getPackname
(JNIEnv *, jclass);
#ifdef __cplusplus
}
#endif
#endif
参照zhaozx_JniTest.h文件在jni目录下创建对应的zhaozx_JniTest.cpp文件,我的文件内容为
//
// Created by Apple on 2017/6/6.
//
#include <stdio.h>
#include <jni.h>
#include <stdlib.h>
#include "zhaozx_JniTest.h"
JNIEXPORT jstring JNICALL Java_zhaozx_JniTest_getPackname (JNIEnv *env, jclass cls){
return (env)->NewStringUTF((char *)"Hello from JNI !");
/**
此处根据文件后缀写法不同
.cpp的写法return (env)->NewStringUTF((char *)"Hello from JNI !");
.c的写法return (*env)->NewStringUTF(env, "Hello from JNI !");
*/
}
在app/build.gradle文件中加入
defaultConfig {
applicationId "com.learnrn"
minSdkVersion 16
targetSdkVersion 22
versionCode 1
versionName "1.0"
// 生成不同平台的.so文件
+ndk {
+ abiFilters "armeabi-v7a", "x86"
+}
}
在jni目录中还需要加入两个文件Android.mk和Application.mk。
Android.mk的内容
LOCAL_PATH := $(call my-dir) // 设置当前的编译目录(Android.mk所在的目录)
include $(CLEAR_VARS) // 清除LOCAL_XX变量(LOCAL_PATH除外)
LOCAL_MODULE := TEST_JNI // 指定当前编译模块的名称(不过貌似没用额,总是生成libapp.so文件)
LOCAL_SRC_FILES := jnitest.cpp // 编译模块需要的源文件
include $(BUILD_SHARED_LIBRARY) // 指定编译出的库类型,BUILD_SHARED_LIBRARY:动态库;//BUILD_STATIC_LIBRARY:静态库, BUILD_EXECUTEABLE指:可执行文件
Application.mk的内容
APP_ABI := all
做完这些后就大工告成,在AS中rebuild项目。
会发现这个目录下会多出.so文件。
使用
.so文件已经生成,接下来就是如何使用了,在需要调用的地方创建对象并使用方法即可,如:
JniTest j = new JniTest();
Log.e("我要成功", j.getPackname());
结果
总结
整个过程结束了,但是还是有很多需要注意的地方
1.生成.h文件的过程(主要是路径要对)
2.根据.h文件编写相对应的C/C++文件
3.C/C++文件里面相对应实现java方法的写法是有差异的
4.static {
System.loadLibrary("XXX");
}"XXX"的名字必须是生成的libXXX.so,但当XXX自己以lib打头时也可以。
目前想到的就这么多了,谢谢大家哈!