lpsolve-android
github原文: https://github.com/leonardodalinky/lpsolve-android
将lp_solve
线性规划工具从c和java移植到android安卓平台上。
lp_solve
版本: 5.5.2.5
背景
几天前, 当我正在为《公主连接》游戏写基于线性规划的装备计算器时,我发现常用的纯Java实现的org.apache.commons:commons-math3
库,在装备项数和约束方程很大的情况下,表现得非常差。
通过在谷歌之后,发现lp_solve
这个开源工具包有着更好的表现,但是由于其并未直接提供Android版本的库文件,因而使我萌生了移植代码的念头。
经过lp_solve
与org.apache.commons:commons-math3
库进行比较后,发现在大规模线性规划问题下,前者有着更好的准确度和更快的速度,可见lp_solve
更加适合安卓平台线性规划工具的开发。
移植遇到的问题
有人可能会问,为什么我们不直接使用他提供的Jar包?Android不是可以直接读取Java的.Class文件吗?
对于安卓平台而言,由于cpu可能采用不同的架构,例如arm等cpu架构,因此不能直接使用已有的jar包,因为这些jar包内部也是通过动态链接库加上JNI所实现的。
所以我们要做的,就是查清楚其MakeFile的过程,并在Android Studio上复现,并且去除平台不兼容所带来的bug。
破寻原有的Build Up过程
首先,我们先得明确:lp_solve
内核是由C语言实现,Java只是为其提供了JNI的接口。
$c -fpic $INCL -c $SRC_DIR/lpsolve5j.cpp
$c -shared -Wl,-soname,liblpsolve55j.so -o $PLATFORM/liblpsolve55j.so lpsolve5j.o -L../../../lpsolve55/bin/$PLATFORM -lc -llpsolve55
在lp_solve
的JNI Wrapper的生成中(上述代码),我们可以发现其调用了其C语言源码库.../lpsolve55
文件夹中的binary文件,可知其C语言的核心先在该文件夹中编译为静态库。
之后,我们在.../lpsolve55
文件夹中查阅其静态库的生成方式:
src='../lp_MDO.c ../shared/commonlib.c ../shared/mmio.c ../shared/myblas.c ../ini.c ../fortify.c ../colamd/colamd.c ../lp_rlp.c ../lp_crash.c ../bfp/bfp_LUSOL/lp_LUSOL.c ../bfp/bfp_LUSOL/LUSOL/lusol.c ../lp_Hash.c ../lp_lib.c ../lp_wlp.c ../lp_matrix.c ../lp_mipbb.c ../lp_MPS.c ../lp_params.c ../lp_presolve.c ../lp_price.c ../lp_pricePSE.c ../lp_report.c ../lp_scale.c ../lp_simplex.c ../lp_SOS.c ../lp_utils.c ../yacc_read.c'
......
$c -fpic -s -c -I.. -I../shared -I../bfp -I../bfp/bfp_LUSOL -I../bfp/bfp_LUSOL/LUSOL -I../colamd -I. $opts $NOISNAN -DYY_NEVER_INTERACTIVE -DPARSER_LP -DINVERSE_ACTIVE=INVERSE_LUSOL -DRoleIsExternalInvEngine $src
$c -shared -Wl,-Bsymbolic -Wl,-soname,liblpsolve55.so -o bin/$PLATFORM/liblpsolve55.so `echo $src|sed s/[.]c/.o/g|sed 's/[^ ]*\///g'` -lc -lm -ldl
通过上面的代码,我们能够确定其产生静态库的重要文件。
改写CMake
由于Android Studio使用NDK需要自己配置CMakeLists.txt
,因此在调整文件结构后,根据上面的重要信息,写出CMakeLists.txt
#/**
#* @ FileName: CMakeLists.txt
#* @ Function: The CMakeList to build up the dynamic library for lp_solve
#* @ Author: AyajiLin & YesunHuang
#* @ Mail: 493987054@qq.com
#* @ Github: https://github.com/leonardodalinky
#* @ Data: May 10, 2020
#*
#*/
# 确定CMake最小版本
cmake_minimum_required(VERSION 3.4.1)
# 包含头文件
include_directories(java_src)
include_directories(c_src)
include_directories(c_src/colamd)
include_directories(c_src/shared)
include_directories(c_src/bfp)
include_directories(c_src/bfp/bfp_LUSOL)
include_directories(c_src/bfp/bfp_LUSOL/LUSOL)
# 增加JNI中的源码
aux_source_directory(java_src DIR_JAVA_SRCS)
# 设置编译器参数(由上一节中顺带得到)
SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3 -lc -lm -ldl -DYY_NEVER_INTERACTIVE -DPARSER_LP -DINVERSE_ACTIVE=INVERSE_LUSOL -DRoleIsExternalInvEngine -DNOLONGLONG -DCLOCKTIME -dy")
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -frtti -fexceptions -O3 -lc -lm -ldl -DYY_NEVER_INTERACTIVE -DPARSER_LP -DINVERSE_ACTIVE=INVERSE_LUSOL -DRoleIsExternalInvEngine -DNOLONGLONG -DCLOCKTIME -dy")
# 定义所需的C语言源码
SET(C_SRCS c_src/lp_MDO.c c_src/shared/commonlib.c c_src/shared/mmio.c c_src/shared/myblas.c c_src/ini.c c_src/fortify.c c_src/colamd/colamd.c c_src/lp_rlp.c c_src/lp_crash.c c_src/bfp/bfp_LUSOL/lp_LUSOL.c c_src/bfp/bfp_LUSOL/LUSOL/lusol.c c_src/lp_Hash.c c_src/lp_lib.c c_src/lp_wlp.c c_src/lp_matrix.c c_src/lp_mipbb.c c_src/lp_MPS.c c_src/lp_params.c c_src/lp_presolve.c c_src/lp_price.c c_src/lp_pricePSE.c c_src/lp_report.c c_src/lp_scale.c c_src/lp_simplex.c c_src/lp_SOS.c c_src/lp_utils.c c_src/yacc_read.c)
# 选择编译为动态链接库
add_library( # Sets the name of the library.
lpsolve55j
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
${C_SRCS} ${DIR_JAVA_SRCS}
)
...后略
部署过程的问题
在部署过程中,由于自身对CMake的不熟悉,导致花费了很多时间。
除此以外,还曾经遇到cannot initialize a parameter of type 'JNIEnv **' (aka '_JNIEnv **') with an rvalue of type 'void **'
的错误。经过排查,发现其源码中存在着不恰当的类型转换,将JNIEnv **
类型强制转换为void **
类型,而传给一个需要JNIEnv **
类型变量作为参数的函数,因此被JNI规范发现并报错。
修改其源码后,部署成功。
以下为Android Studio上的部署教程,其他IDE流程未尝试过,待补充。英文水平较渣,就不带翻译了。此处为github原文
How to Use
Before we go on, we shall check some prerequisites:
- Android Studio
- CMake
After that, download the latest lpsolve_android_release.zip
in release page and unzip it. The directory structure is listed below
app
- build.gradle
- src
- main
- cpp
- CMakeLists.txt
- ...More
- java
- lpsolve
- ...More
lpsolve_bin
- lib
- arm64-v8a
- armeabi-v7a
- x86
- x86_64
The directories inside lpsolve_bin/lib
contain the .so
libraries for each platforms.
There are 2 different ways to lay out the lp_solve
libraries.
Way 1: Build A New NDK Project
First, build a NDK project in AS.
The file structure in directory app
correspond to the real structure in ndk project of Android Studio. Then:
- Put the files in directory
app/src
in the corresponding location of the ndk project. Make sure thelpsolve
package is the subdirectory ofapp/src/main/java
and thecpp
dir is the subdirectory ofapp/src/main
, or it may cause errors. - Modify the
build.gradle
inapp
as below:
android{
...
defaultConfig{
...
# Here define the parameters which control the cmake
externalNativeBuild {
cmake {
cFlags ""
cppFlags ""
}
}
}
...
# Here is the relative path of the CMakeList.txt
externalNativeBuild {
cmake {
path "src/main/cpp/CMakeLists.txt"
}
}
}
Way 2: Add the lp_solve
libraries to an existing project
-
Start an existing android project with AS.
-
Put the files in directory
app/src
in the corresponding location of the NDK project. Make sure thelpsolve
package is the subdirectory ofapp/src/main/java
and thecpp
directory is the subdirectory ofapp/src/main
, or it may cause errors. -
Open the Project pane from the left side of the IDE and select the Android view.
-
Right-click on the module you would like to link to your native library, such as the app module, and select Link C++ Project with Gradle from the menu.
-
From the drop-down menu, select CMake. Then, use the field next to Project Path to specify the
src/main/cpp/CMakeLists.txt
script file for your external CMake project. -
Click OK.
Above steps could be referred to Link Gradle to your native library on the website of Android Developers.
When everything is done, you could import the lpsolve
package the same as what we do with Java.
And every time the .apk
file is compiled and built, the lpsolve
would be packed in it.
API Reference
License
Credits
The project relies on: