本文记录了自己从写C/C++源码到so的全过程,并应用到项目中。本篇文章不会深入NDK编程,但是我会把涉及到的内容简单说下,在开始之前,先把整个架构简单说下,对于理解下面的东西要容易点。
整个过程如下:
java->JNI->C/C++,
从这个关系可以看出来,JNI就是native和java的桥梁,流程如下:
- 1.java代码。
- 2.实现功能的源码(C/C++)
- 3.实现JNI代码。
- 4.编写.mk脚本。
- 5.采用ndk构建命令构建
我记录了自己编译so包的整个过程,如何写构建脚本,如何构建。当然你需要有一定的C/C++基础,需要了解一下JNI,还需要了解.mk文件编写。我会用一个加减乘除为例来讲述整个过程,写此文有两个目的,其一,让自己理清整个流程,其二,希望能能给有需要的一点启发。
准备工作
首当其冲就是NDK环境的配置,自己百度一下环境配置,很容易的。
ndk-build 构建方式
- 1.ndk-build 的配置工作
配置路径网上有很多教程,或者直接从Android Studio下载,可以参考一步步编译Android so包 - 2.mk文件的预备知识
本文主要从Android.mk和Application.mk两个文件讲,因为我参考的项目中提供了这两个文件,方便我们直接构建。下面两个文件的内容 - Android.mk具体内容
LOCAL_PATH := $(call my-dir)//表示当前目录下
include $(CLEAR_VARS)//
LOCAL_MODULE := p7zipUtil//so包的名称
LOCAL_SRC_FILES := Calculator.cpp//源文件,要是有多个文件,通过/链接
LOCAL_LDLIBS := -L$(SYSROOT)/usr/lib -llog
include $(BUILD_SHARED_LIBRARY) //构建共享库,当然还有其他几种方式,如可执行的库,Android需要的就是共享库
- Application.mk具体内容
# The ARMv7 is significanly faster due to the use of the hardware FPU
APP_ABI := armeabi # armeabi-v7a//设置CPU架构
#APP_PLATFORM := android-8//设置Android版本
开始编译
按照上面的构建流程来,实现加减乘除功能
1.编写 Java代码:
说实话这个没什么好讲的,注意两点:
- 1.如何加载so包
通过一个static块实现加载,路径写的时候,写的是模块名,而不是文件名字。生成的so文件的名字一定是lib+模块名+.so组成,但是在System.loadLibrary(“模块名”)。 - 2.如何写函数
这个函数与普通的函数多个native关键字。
详见代码
public class Zip7Utils {
static {
System.loadLibrary("p7zipUtil");
}
public static native int add(int var0, int var1);//加法
public static native int minus(int var0, int var1);//减法
public static native int multiple(int var0, int var1);//乘法
public static native int divide(int var0, int var1);//除法
}
2.生成JNI代码
通过javac -encoding utf8 -h .\jni path这个命令生产**.h**文件,这个命令执行时,注意切换目录。
如下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_haochen_a7ziputile_Zip7Utils */
#ifndef _Included_com_haochen_a7ziputile_Zip7Utils
#define _Included_com_haochen_a7ziputile_Zip7Utils
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_haochen_a7ziputile_Zip7Utils
* Method: add
* Signature: (FF)F
*/
JNIEXPORT jfloat JNICALL Java_com_haochen_a7ziputile_Zip7Utils_add
(JNIEnv *, jclass, jfloat, jfloat);
/*
* Class: com_haochen_a7ziputile_Zip7Utils
* Method: minus
* Signature: (FF)F
*/
JNIEXPORT jfloat JNICALL Java_com_haochen_a7ziputile_Zip7Utils_minus
(JNIEnv *, jclass, jfloat, jfloat);
/*
* Class: com_haochen_a7ziputile_Zip7Utils
* Method: multiple
* Signature: (FF)F
*/
JNIEXPORT jfloat JNICALL Java_com_haochen_a7ziputile_Zip7Utils_multiple
(JNIEnv *, jclass, jfloat, jfloat);
/*
* Class: com_haochen_a7ziputile_Zip7Utils
* Method: divide
* Signature: (FF)F
*/
JNIEXPORT jfloat JNICALL Java_com_haochen_a7ziputile_Zip7Utils_divide
(JNIEnv *, jclass, jfloat, jfloat);
#ifdef __cplusplus
}
#endif
#endif
可能对于初学者来说,为啥要生成.h文件呢,其实这就是JNI了,需要编写native代码时引入。可能看到一脸懵,什么jfloat,什么jclass,我们先了解一下这些JNI基础知识。
- JNI基本类型
java中以基本类型,同样JNI也有,只是和java中略有区别。
| java基本类型 | JNI基本类型 |
|---|---|
| Boolean | jboolean |
| byte | jbyte |
| char | jchar |
| short | jshort |
| int | jint |
| long | jlong |
| float | jfloat |
| double | jdouble |
看完两者的对比关系,有没发现一个规律,只需要在java基础类型前面加上一个j,就变成了JNI基础类型。再回头看之前生成的代码是不是有点眉目了。
除了基础类型之外,我们还要了解一下引用类型:

3.编码C++文件
生成.h文件之后,我们才真正开始编写c++文件,实现具体功能如下:
#include <jni.h>
#include "com_haochen_a7ziputile_Zip7Utils.h"
JNIEXPORT jfloat JNICALL Java_com_haochen_a7ziputile_Zip7Utils_add(JNIEnv *env, jclass clazz, jfloat add1, jfloat add2) {
return add1 + add2;
}
JNIEXPORT jfloat JNICALL Java_com_haochen_a7ziputile_Zip7Utils_minus(JNIEnv *env, jclass clazz, jfloat m1, jfloat m2) {
return m1 - m2;
}
JNIEXPORT jfloat JNICALL Java_com_haochen_a7ziputile_Zip7Utils_multiple(JNIEnv *env, jclass clazz, jfloat p1, jfloat p2) {
return p1 * p2;
}
JNIEXPORT jfloat JNICALL Java_com_haochen_a7ziputile_Zip7Utils_divide(JNIEnv *env, jclass clazz, jfloat d1, jfloat d2) {
if (d2 == 0) {
return -1;
} else {
return d1 / d2;
}
}
到此我们已经将C++ 代码写完,是不是很简单,我们将要进入构建脚本编写阶段。
4.编写构建脚本
在main文件夹下新建一个jni文件夹,切记名字一定要是jni,否则报错,把上面两个文件放入进去。
我的项目工程的文件的目录如下:

Android.mk内容:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := p7zipUtil
LOCAL_SRC_FILES := Calculator.cpp//源文件,要是有多个文件,通过/链接
LOCAL_LDLIBS := -L$(SYSROOT)/usr/lib -llog
include $(BUILD_SHARED_LIBRARY)//一定要是这个
Application.mk内容:
# The ARMv7 is significanly faster due to the use of the hardware FPU
APP_ABI := armeabi # armeabi-v7a
#APP_PLATFORM := android-8
到此构建脚本算是写完了。
5.用构建命令构建
从控制台切换到jni目录下,也就是包含Android.mk和Application.mk的目录,执行ndk-build命令,成功后可以在libs文件夹下找到。
进入命令行界面,切换到jni目录下,键入命令ndk-build,如下图:

若是代码没有问题,则会出现上面的结果。构建成功后会生成两个目录:

libs目录下,就是你构建好的so文件,直接可以被apk代码所使用的。
obj这个是中间目录,对于我们来说,不用关心。
总结
编译第三方源码的感悟,我一开始就在想,如何使用源码?源码如何被Android使用?如何编译源码?如何编译源码这篇文章能给你一个大致的了解,但是如何使用源码?主要还是依靠开源项目的文档,会提供如何使用源码的教程
源码
本文详细记录了从C/C++源码到SO包的编译全流程,并将其应用于Android项目中。涵盖JNI编程、mk脚本编写及ndk-build构建流程,适合初学者快速上手。

8484

被折叠的 条评论
为什么被折叠?



