news 2026/9/1 14:55:41

Android平台libredwg交叉编译与JNI集成实践指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Android平台libredwg交叉编译与JNI集成实践指南

简介:本资源是一个基于 Android Studio 的 libredwg 库交叉编译工程,面向 Android 开发者及嵌入式 C/C++ 工程师,解决在安卓平台解析 DWG 文件的核心需求——无需从零配置 NDK 与 CMake 工具链,即可快速生成适配 arm64-v8a、armeabi-v7a、x86 和 x86_64 架构的 native 动态库。压缩包共 290 个文件,含 71 个头文件(.h)、49 个文本说明(.txt)、40 个 C 源码(.c)、20 个构建规范(.spec)及 8 个已编译 SO 库,另有 CMakeLists.txt、Android.mk、gradle 配置等关键构建脚本,完整呈现跨平台编译的目录结构与依赖组织逻辑。已有 59970 人学习下载,资源附带可直接运行的示例工程,支持一键 Build 生成目标架构库;同时提供通用化替换模板——用户只需替换 cpp 目录下的源码并修改 CMakeLists.txt,即可复用于其他 C/C++ 第三方库的 Android 交叉编译任务,显著降低 NDK 编译门槛。 搞Android开发的老哥们应该遇到过这种需求:要在手机上读取DWG图纸。DWG是AutoCAD的私有二进制格式,想在移动端处理它,最常见也最靠谱的做法就是找一个能解析DWG的C/C++库,然后用JNI调过去。libredwg就是一个非常合适的开源选择,但问题来了:它是个标准的Autotools项目,默认编出来是x86_64平台的库,而Android手机是ARM架构,所以必须走交叉编译这条路。

这篇博文以我自己的一次完整实践为主线,从工具链选型、依赖库处理、configure参数设置,到Android Studio里的CMake集成和JNI封装,把整个流程走了一遍。内容面向两类读者:一类是准备在Android项目里用libredwg的开发者,另一类是纯粹想搞明白“C库怎么交叉编译到Android”这个通用问题的朋友。无论你是哪一类,这篇文章都能让你少踩几个坑。

1. 为什么要交叉编译libredwg:方案选型与整体思路

1.1 libredwg的核心能力与移动端应用场景

libredwg是GNU旗下的一个开源C库,专门用来读取DWG文件内容。它的亮点在于不依赖AutoCAD环境,就能解析DWG里的图层、块、实体、标注等结构化数据。对于移动端来说,最常见的诉求是在Android设备上做一个DWG图纸浏览器,用户选一个文件,app解析出里面的图形对象,再通过OpenGL或自定义View渲染出来。

我这里的需求更聚焦:只是读取DWG文件里的图层列表、块定义和基础实体信息,然后展示给用户。DWG格式有多复杂?从AutoCAD R13到最新版本,格式一直在变,libredwg是目前开源社区里兼容性做得最好的解析库之一,它比libdxfrw这类库的格式覆盖更全。所以我从一开始就锁定了libredwg。

但是直接用现成的预编译库不可能,因为Android生态里根本没有“通用版”的libredwg预编译包。你必须自己动手,用Android NDK里的交叉编译器,在开发机上把libredwg编译成target为ARM架构的.so或.a文件。这个过程就叫交叉编译。

1.2 工具链方案:NDK内置工具链 vs 独立工具链

交叉编译的第一步是选工具链。Android官方推荐的方式就是直接用NDK包里的工具链。旧版本NDK(r17及以下)提供过一个make-standalone-toolchain脚本,用来生成独立工具链,很多老教程会让你这么干。但从NDK r18开始,官方彻底移除了gcc,全面转向clang,并且不推荐再手动生成独立工具链了。

我实测下来的结论是:直接用NDK自带的toolchain目录,配合环境变量,是最省事、最不容易出错的方案。不要自己折腾独立工具链,因为配置SYSROOT、CROSS_COMPILE这些变量很容易搞混,而且不同NDK版本之间的路径结构有差异。

那怎么调用NDK里的交叉编译器?核心是设置这么几个环境变量:

export NDK=/opt/android-ndk-r23c export TOOLCHAIN=$NDK/toolchains/llvm/prebuilt/linux-x86_64 export TARGET=aarch64-linux-android export API=24 export AR=$TOOLCHAIN/bin/llvm-ar export CC=$TOOLCHAIN/bin/$TARGET$API-clang export AS=$TOOLCHAIN/bin/llvm-as export CXX=$TOOLCHAIN/bin/$TARGET$API-clang++ export LD=$TOOLCHAIN/bin/ld export RANLIB=$TOOLCHAIN/bin/llvm-ranlib export STRIP=$TOOLCHAIN/bin/llvm-strip export SYSROOT=$TOOLCHAIN/sysroot

注意这里的$TARGET$API-clang不是随便拼的,NDK在r23c里确实同时提供了aarch64-linux-android24-clang和aarch64-linux-android24-clang++这两个可执行文件。API=24对应Android 7.0,如果你的app最小支持版本更低,可以改成21,但要注意Android 5.0之前不支持64位arm64指令集,所以如果选择armeabi-v7a,得用armv7a-linux-androideabi$API-clang。

1.3 交叉编译的整体链路设计

交叉编译libredwg不是编译一个库就完事,它有一整条依赖链。libredwg有个可选的依赖项是libxml2,用来支持读取XML格式的DWG(DWG文件其实有个XML伴生格式)。如果你只是读取纯二进制DWG,理论上可以不编libxml2,但libredwg在configure时如果找到libxml2,会启用额外的XML读写功能;如果没有,也能编译,只是部分API不可用。

我建议还是把libxml2编上,因为DWG文件的组成比很多人想象的复杂——它里面除了图形数据,还有属性、扩展数据、代理实体等,这些信息用XML接口读取会更方便。而且libxml2本身也是c库,同样需要交叉编译。再加上libxml2又依赖zlib,所以完整的依赖链是:zlib → libxml2 → libredwg。

这个依赖链看起来长,但每层都是标准Autotools项目,套路一模一样:运行configure指定交叉编译参数,然后make,再make install到一个统一的前缀目录。把这个流程跑通一次,后面任何“C库交叉编译到Android”的需求都能照方抓药。

2. 环境准备:NDK版本选型、ABI确定与依赖库梳理

2.1 NDK版本怎么选:优先r23c,兼容性与稳定性之间取平衡

NDK版本的选择直接影响交叉编译的顺利程度。太老的版本(r17之前)用gcc,虽然有些人觉得gcc的生态更熟,但官方已经停止维护,编译新版libredwg时会出现各种“编译器不支持某特性”的报错。太新的版本(r25、r26)又可能对老项目不友好,因为新版NDK默认的minSdkVersion和libc++版本都有调整。

我自己用的是r23c,这个版本有几个优点:第一,它是最后一个同时保留“通用sysroot”结构的版本,配置起来不折腾;第二,clang版本是12.0.5,对Autotools项目的兼容性非常好;第三,网上遇到问题时,r23c对应的资料最多。如果你用r26、r27,会发现sysroot路径和链接器参数都变了,很多老教程的步骤直接失效。

安装NDK的方式也很简单,Android Studio自带的SDK Manager里就能装,也可以直接去官方页面下载压缩包。我建议直接下载压缩包解压到一个纯英文路径,路径里千万别有中文和空格,否则configure脚本极容易因为路径解析失败报错。项目里也别把NDK放在C:\Program Files这类有空格的位置,这是用血的教训换来的建议。

2.2 ABI选型:只编arm64-v8a还是三架构全上?

Android的ABI有armeabi-v7a、arm64-v8a、x86、x86_64四种,现代设备的实际情况是:arm64-v8a已经覆盖了99%的真机,x86/x86_64主要给模拟器用,armeabi-v7a是给老设备用的。

如果你只是做内部工具或者Demo,建议只编arm64-v8a。原因很简单:每多编一个ABI,编译时间、APK体积都会翻倍。如果以后要上Google Play,Play要求必须包含arm64-v8a,其他ABI看需求加。但要注意,只在APK里打arm64-v8a,会导致x86模拟器上装不了应用,调试时需要用ARM镜像或者真机。

交叉编译时对应的TARGET环境变量:

ABITARGET值说明
arm64-v8aaarch64-linux-android现代手机主流,API 21起支持
armeabi-v7aarmv7a-linux-androideabi32位ARM老设备
x86_64x86_64-linux-android64位模拟器
x86i686-linux-android32位模拟器

我这次的实践只针对arm64-v8a,即TARGET=aarch64-linux-android,这也是最典型、最值得优先掌握的一种。

2.3 依赖库清单:libxml2和zlib为什么要先搞定

libredwg在configure阶段会通过pkg-config或者直接查找头文件的方式探测libxml2。如果探测不到,它不会报错,只是禁用XML相关功能。这会导致什么后果?DWG里的Extensible Data(XDATA)和某些字符串信息读不出来,对于多数场景来说可能无所谓,但如果你做的是图纸数据导出工具,这就有问题了。

所以我的建议是:既然目标是做一个能真正解析DWG的Android库,就老老实实把依赖编全。需要准备两份源码:

  • zlib:太常见了,Android源码里有内置版本,但交叉编译时还是要自己编一份,因为libxml2链接时需要它的头文件和静态库。zlib版本用1.2.13以上即可。
  • libxml2:版本用2.11.x系列较稳,新版2.12+对Autotools的configure参数有调整,容易踩坑。

这两份源码都要交叉编译成arm64版本的产物,安装到同一个前缀目录(比如/home/user/android-libs/),这样后面libredwg的configure就能顺着CPPFLAGS和LDFLAGS找到它们。

3. libredwg交叉编译完整实操记录

3.1 第一步:交叉编译zlib

zlib是三者中结构最简单的,编译起来也最快。它的configure不是Autotools的configure,而是zlib自定义的脚本,需要一点特殊处理。

tar -xzf zlib-1.2.13.tar.gz cd zlib-1.2.13 export CC=aarch64-linux-android24-clang export AR=llvm-ar export RANLIB=llvm-ranlib export CFLAGS="-O3 -fPIC -I$SYSROOT/usr/include" ./configure --prefix=/home/user/android-libs/zlib --static make -j8 make install

注意几个点:CC一定要指到带API版本的clang,不要只写aarch64-linux-android-clang,否则会找不到默认sysroot;CFLAGS里必须加-fPIC,因为后面要编进.so,没有位置无关代码会链接失败;--static只生成静态库,避免生成.so时又附带交叉编译的动态链接麻烦。zlib编译通常不用改源码,一次就能过。

如果make时报错“cannot find -lc”,说明SYSROOT没有配置正确或者TARGET和API组合有问题。检查一下环境变量,确保c库路径能通过llvm的搜索机制找到,最简单的办法是打印编译命令行,看它执行的到底是什么。

3.2 第二步:交叉编译libxml2

libxml2比zlib复杂不少,因为它的configure会去检测很多系统特性,比如iconv库。Android的Bionic libc本身没有完整的iconv实现,libxml2又需要iconv来处理多字节编码,所以这里经常出问题。

我尝试过两种方案:第一种是给libxml2加--without-iconv,让它强制不启用iconv,但这样会导致解析UTF-8以外的编码时出错。第二种是单独交叉编译一个libiconv,然后让libxml2链接它。这两个方案我都试过,最终建议用第二种,因为DWG文件里的字符串往往是本地编码(比如国标码),没有iconv会读乱码。

# 先编译libiconv tar -xzf libiconv-1.17.tar.gz cd libiconv-1.17 ./configure --host=aarch64-linux-android --prefix=/home/user/android-libs/iconv --disable-rpath --enable-static make -j8 make install # 再编译libxml2 tar -xzf libxml2-2.11.5.tar.gz cd libxml2-2.11.5 export CC=aarch64-linux-android24-clang export CPPFLAGS="-I/home/user/android-libs/iconv/include -I/home/user/android-libs/zlib/include" export LDFLAGS="-L/home/user/android-libs/iconv/lib -L/home/user/android-libs/zlib/lib" ./configure --host=aarch64-linux-android --prefix=/home/user/android-libs/libxml2 \ --with-iconv=/home/user/android-libs/iconv \ --with-zlib=/home/user/android-libs/zlib \ --without-python --without-lzma --disable-shared --enable-static make -j8 make install

--without-python和--without-lzma是必须的:Python交叉编译到Android纯粹是自找麻烦,lzma同样需要额外依赖。--disable-shared --enable-static告诉它只编静态库,这也是本次实践的关键决定——libredwg最终要编成一个.so,所有依赖链都用静态库,能极大简化JNI层的链接问题。动态库嵌套会疯狂拉长加载时间,而且搞不好就出现dlopen找不到符号的问题。

3.3 第三步:libredwg本体configure与make

依赖库就绪后,libredwg的交叉编译就顺畅多了。首先从GitHub拉源码:

git clone https://github.com/LibreDWG/libredwg.git cd libredwg sh autogen.sh

autogen.sh会生成configure脚本,这一步需要本机装了autoconf、automake、libtool。如果你本机版本太旧,可能生成失败,建议先把这些工具升级到最新版本。

重点看configure参数:

export CC=aarch64-linux-android24-clang export CPPFLAGS="-I/home/user/android-libs/libxml2/include -I/home/user/android-libs/iconv/include -I/home/user/android-libs/zlib/include" export LDFLAGS="-L/home/user/android-libs/libxml2/lib -L/home/user/android-libs/iconv/lib -L/home/user/android-libs/zlib/lib" export LIBS="-lxml2 -liconv -lz" ./configure --host=aarch64-linux-android \ --prefix=/home/user/android-libs/libredwg \ --enable-shared --disable-static \ --disable-bindings --disable-python \ --without-libmagic

有几个参数专门解释一下:

  • --host=aarch64-linux-android是交叉编译的核心标志,告诉configure“我编译出来的程序跑在Android上”。
  • --enable-shared --disable-static:libredwg本体编成动态库,因为最终要打包成.so给JNI用。依赖库是静态库,libredwg是动态库,这样最终产物只有一个libredwg.so。
  • --disable-bindings:禁用swig生成的各语言绑定,我们在Android上只需要C API。
  • --without-libmagic:magic库用于文件类型识别,Android上没有,必须关掉。
  • LIBS="-lxml2 -liconv -lz"是给最终链接用的,如果不在configure阶段指定,后面make的时候可能会因为链接顺序问题找不到符号。

configure成功后会生成Makefile,然后make编译。这里建议加上-j8参数加快速度:

make -j8 make install

整个编译过程大概两三分钟,取决于机器性能。编译完成后检查产物:

file /home/user/android-libs/libredwg/lib/libredwg.so

如果输出显示“ELF 64-bit LSB shared object, ARM aarch64”,说明交叉编译成功。如果显示x86-64,那一定是configure时--host没生效,回去检查环境变量。

3.4 产物检查:用readelf验证依赖关系和ABI

编译完成后,还需要验证几个关键点:依赖的动态库、ABI架构、以及是否有undefined symbol。

$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-readelf -d libredwg.so

主要看NEEDED里是否有libxml2.so、libc.so之外的东西。因为libxml2、iconv、zlib都是静态链进libredwg.so的,所以NEEDED应该只包含libc.so、libm.so、libdl.so这类Android系统自带的库。如果出现libxml2.so,说明链接方式搞错了,将来在Android上会找不到这个库。

再检查UndeFined symbols:

$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-nm -D libredwg.so | grep " U "

正常情况下undefined符号只应该涉及标准C库函数,如果有xmlParseFile这样明显来自libxml2的符号,就说明静态链接没生效。这种问题通常是因为libxml2编译时没编成静态库,或者链接顺序不对——静态库必须放在依赖它的动态库之后,这个顺序在Makefile里是固定的,很难手动改,所以最好的解决办法就是从一开始就保证libxml2的库文件里.a存在且.so不存在,这样链接器只能选静态库。

4. Android Studio集成:CMake配置与JNI封装细节

4.1 新建项目与CMakeLists.txt编写

交叉编译出来的.so,最终要放进Android项目里。这里有一个关键问题:libredwg的C API非常复杂,暴露给Java层的接口必须自己封装一层JNI。直接在Android Studio里写JNI C代码,再用CMake打包成另一个.so,链接libredwg.so,这是最清晰的结构。

在Android Studio中新建一个Native C++项目,然后在app/src/main/cpp/目录下放两个文件:CMakeLists.txt和native-lib.cpp(名字可以自定义)。

CMakeLists.txt的关键内容:

cmake_minimum_required(VERSION 3.18.1) project("dwgdemo") set(LIBREDWG_DIR ${CMAKE_SOURCE_DIR}/libredwg) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIC") add_library(libredwg SHARED IMPORTED) set_target_properties(libredwg PROPERTIES IMPORTED_LOCATION ${LIBREDWG_DIR}/${ANDROID_ABI}/libredwg.so ) add_library(native-lib SHARED native-lib.cpp) target_link_libraries(native-lib libredwg) find_library(log-lib log) target_link_libraries(native-lib ${log-lib})

注意这里的${ANDROID_ABI}是CMake自动注入的变量,在app/build.gradle里配置好ABI筛选:

android { defaultConfig { externalNativeBuild { cmake { cppFlags "-std=c++17" // 这里只需要arm64-v8a,因为交叉编译只编了这个架构 abiFilters "arm64-v8a" } } } externalNativeBuild { cmake { path "src/main/cpp/CMakeLists.txt" } } }

把编译好的libredwg.so放到app/src/main/cpp/libredwg/arm64-v8a/目录下,Android Studio构建时就会把.so文件打包进APK。

4.2 JNI接口设计:要暴露哪些功能给Java层

libredwg的C API头文件是dwg.h,它的核心数据结构是dwg_struct和dwg_data。用dwg_read_file读取一个DWG文件后,就能遍历图层、块、实体。

但C API不适合直接暴露给Java,因为指针管理和内存释放很容易出问题。我的设计思路是:把复杂的C数据对象“拍平”成基础类型,通过JNI返回给Java层。例如,只暴露三个核心方法:

extern "C" JNIEXPORT jstring JNICALL Java_com_example_dwgdemo_DwgParser_loadFile(JNIEnv *env, jobject, jstring path); extern "C" JNIEXPORT jstring JNICALL Java_com_example_dwgdemo_DwgParser_getLayerNames(JNIEnv *env, jobject, jstring jsonStr); extern "C" JNIEXPORT void JNICALL Java_com_example_dwgdemo_DwgParser_close(JNIEnv *env, jobject);

loadFile负责打开DWG文件并解析,getLayerNames返回一个JSON字符串,JSON里是图层名数组。用JSON作为JNI层的数据交换格式,能避免在C++和Java之间传自定义对象时的大量代码,缺点是序列化反序列化有一定开销,但图层名这种小数据量完全无压力。

native-lib.cpp里的大致逻辑:

#include <jni.h> #include <string> #include "dwg.h" static Dwg_Data *g_dwg = nullptr; extern "C" JNIEXPORT jboolean JNICALL Java_com_example_dwgdemo_DwgParser_loadFile(JNIEnv *env, jobject, jstring path) { const char *pathStr = env->GetStringUTFChars(path, nullptr); g_dwg = (Dwg_Data *)malloc(sizeof(Dwg_Data)); int error = dwg_read_file(pathStr, g_dwg); env->ReleaseStringUTFChars(path, pathStr); return error == 0 ? JNI_TRUE : JNI_FALSE; } extern "C" JNIEXPORT jstring JNICALL Java_com_example_dwgdemo_DwgParser_getLayerNames(JNIEnv *env, jobject) { if (!g_dwg) return env->NewStringUTF("[]"); std::string result = "["; // 遍历图层列表,dwg_data结构里通过object链表访问 // 具体遍历方式取决于libredwg版本的API,这里只写思路 result += "]"; return env->NewStringUTF(result.c_str()); }

注意dwg_read_file的具体API在不同版本有差异,有的是dwg_read_file(path, dwg),有的是dwg_read_file(path, dwg, &opts)。我用的git master版本的API是dwg_read_file(const char *filename, Dwg_Data *dwg),返回int。在使用前先查一下头文件的函数声明,这个细节别搞错。

4.3 Java层调用与全局状态管理

Java层定义一个DwgParser类,用静态native方法包装:

public class DwgParser { static { System.loadLibrary("native-lib"); } public static native boolean loadFile(String path); public static native String getLayerNames(); public static native void close(); public static List<String> parseDwg(File file) { boolean ok = loadFile(file.getAbsolutePath()); if (!ok) return Collections.emptyList(); String json = getLayerNames(); close(); // 用JSONArray解析后返回 return new ArrayList<>(); } }

这里有个全局状态管理的隐患:g_dwg是全局指针,如果同一个app里多个地方同时调用parseDwg,会数据竞争。实际项目中应该用synchronized或者做成单线程调用。DWG解析本来就不是高频操作,加个锁问题不大。

释放内存同样重要,dwg_free(g_dwg)必须在解析完后调用,否则一个大型DWG文件可能占几百MB内存,很容易把app搞崩。close方法里做:

if (g_dwg) { dwg_free(g_dwg); free(g_dwg); g_dwg = nullptr; }

5. 常见问题与排查技巧实录

5.1 configure阶段报错“cannot run C compiled programs”

这是交叉编译最经典的问题,因为configure脚本默认会尝试运行刚编译出来的测试程序,但运行环境是x86架构,根本跑不了ARM程序。解决方法是给configure加--host参数,或者设置环境变量ac_cv_exeext=

export ac_cv_exeext=

大多数情况下只要--host写对了,configure自动会跳过运行步骤。如果还报这个错,检查configure命令有没有加--host=aarch64-linux-android,我遇过很多次写错了TARGET值导致configure认为cross-compile模式没开启。

5.2 编译时找不到头文件或库文件

这种现象一般是CPPFLAGS和LDFLAGS没设对。一个非常稳的经验是:在configure之前,手动用交叉编译器编一个简单的C文件,验证环境是否正常:

echo 'int main(){return 0;}' > test.c $CC test.c -o test

如果这一步就失败,说明工具链本身有问题,后面所有操作都会失败;如果成功,再试一次带-I和-L的编译,验证依赖库路径是否顺畅。所有配置工作都在configure之前完成,编环境的时间不要省。

5.3 运行时UnsatisfiedLinkError问题

编译配置一切都正常,但app一运行就报java.lang.UnsatisfiedLinkError: dlopen failed: library "libxml2.so" not found。这个问题的根源就是libredwg.so对libxml2做了动态链接,但我们没有把libxml2.so打进APK。

排查方法:

$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-readelf -d libredwg.so | grep NEEDED

如果看到libxml2.so,就需要回头重新链接。一个快速的补救方案是:把libxml2.so也放到CMake的导入库列表里,一起打包进APK。但这种方式在多个.so互相依赖时容易出问题,不如重编干净。

最彻底的解决方式:确保依赖库都以静态库形式存在,configure时指定LIBS=-lxml2等,且对应lib目录下不要放.so文件。我当时就在libxml2的lib目录下同时有libxml2.a和libxml2.so,链接器默认选.so,后面清理掉.so只留.a,问题一次性解决。

5.4 编译速度太慢和内存溢出

libredwg的源码量不小,不加-j参数单线程编译,能在开发机上磨蹭十几分钟。但-j参数也别贪多,我试过-j16,结果内存不够直接OOM崩溃。风险控制建议是-j8,同时确保本机RAM不低于8G。如果想进一步减少编译压力,可以修改configure参数,只编译需要的模块:

--disable-bindings --disable-python --disable-examples

这些参数确实能减少编译时间,后面的dwg2dxf、dwgread这些命令行工具也是编译目标,但Android上用不到,全关掉。

5.5 DWG文件解析失败但又不报错

这个现象比较隐蔽:loadFile返回true,但解析出的图层和数据是空的。后来排查发现,libredwg默认开启了STRICT模式,遇到格式不标准的DWG文件会拒绝解析。针对这种情况,可以在loadFile时设置更宽松的选项:

Dwg_Read_Opts opts = {0}; opts.dxf_version = INVALID; opts.test = false; int error = dwg_read_file_opts(pathStr, &opts, g_dwg);

注意dwg_read_file_opts这个函数在新版本里才有,老版本只有dwg_read_file。这本质上是个API版本兼容问题,建议尽量用git master最新代码,然后锁定提交哈希。

6. 从交叉编译到集成:最后的经验沉淀

整个流程走下来,最大的体会是:交叉编译的难点其实不在编译本身,而在依赖关系和ABI意识。libredwg这种标准的Autotools项目,只要工具链环境配置对了,configure和make一条路走到底,反而是在Android Studio集成阶段,CMake配置和JNI封装的一些小细节让人反复折腾。

如果让我重新做一遍,我会提前想清楚三件事:第一,最终产物选择静态库还是动态库,这决定依赖库的编译方式;第二,ABI只选arm64-v8a还是全架构,这决定后续调试和发布的成本;第三,JNI层的数据交换格式用JSON还是用自定义结构体,这决定封装代码的复杂度。

这三件事想明白,交叉编译就是按部就班的操作。我个人的建议始终是:先做一个最小的demo,native层只调用一个函数,验证.so能加载、能跑通,再逐步增加功能。别一上来就把所有API都封装完,否则任何一个环节出问题,排查范围会大得让你崩溃。

最后再分享一个小技巧:把交叉编译的所有configure脚本和命令整理成一个shell脚本,下次在另一台机器上只需要修改路径就能直接跑。这段经历本身也通用——编译zlib、libxml2、libredwg本质上都是同一套动作,掌握一次,以后想往Android上移植OpenSSL、FFmpeg、libcurl这类C库,思路完全一致。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/1 14:49:22

D2C与Figma AI企业级落地:从设计稿到可维护前端代码的工程化实践

最近和一个前端负责人聊天&#xff0c;他说团队正在把后台管理系统从设计稿到代码的流程重做一遍。原因是设计师改一个间距&#xff0c;前端要花半天改十几个页面&#xff1b;UI 走查提意见&#xff0c;开发只能在浏览器里用肉眼猜设计意图。他问我&#xff1a;要不要引入 D2C&…

作者头像 李华
网站建设 2026/9/1 14:48:37

腾讯音乐运维开发笔试复盘:Linux排查与场景题作答思路

2023年腾讯音乐春招业务运维开发岗第二批笔试的通知下来时&#xff0c;我其实有点意外。因为第一批笔试刚结束没多久&#xff0c;网上能搜到的信息有限&#xff0c;大家都还在猜这个岗位到底考什么。我也算临时抱佛脚&#xff0c;把Linux命令、Python脚本、网络基础这些翻了一遍…

作者头像 李华
网站建设 2026/9/1 14:46:47

Excel XLOOKUP函数多条件查询实战:从原理到批量应用

这类工具最值得先看的不是功能列表&#xff0c;而是能不能在普通环境里稳定跑起来。XLOOKUP 函数在 Excel 里解决的就是一个非常具体的问题&#xff1a; 如何根据一个或多个条件&#xff0c;从一堆数据里精准地找到并返回你想要的那个值 。很多人还在用 VLOOKUP 的数组公式或…

作者头像 李华
网站建设 2026/9/1 14:46:27

Cobalt 视频下载完整指南:Docker 自建实例到 API 调用的实操教程

Cobalt 视频下载完整指南&#xff1a;Docker 自建实例到 API 调用的实操教程 【免费下载链接】cobalt best way to save what you love 项目地址: https://gitcode.com/GitHub_Trending/cob/cobalt 凌晨刷到一条很对味的教程视频&#xff0c;想把它转成 mp3 存进手机通勤…

作者头像 李华