在tensorflow源码环境里,编译出独立的jni.so,避免依赖libtensorflowlite.so,从而实现apk体积最小化

需要在APP里使用tensorflow lite来运行PC端训练的model.tlite,又想apk的体积最小,尝试了如下方法:

1. 在gradle里配置

implementation("org.tensorflow:tensorflow-lite:2.16.1")

这样会引入tensorflow.jar,最终apk的size增加大约2.2M

2. 根据tensorflow官方的优化编译教程

https://www.tensorflow.org/lite/android/lite_build?spm=5176.28103460.0.0.73711db8niy7UE&hl=zh-cn

针对我们的模型,构建出针对性的TensorFlow Lite AAR,最后集成到apk里,体积增加约1.5M

分析TensorFlow Lite AAR的实现,发现其本质还是通过JNI调用了libtensorflowlite.so,

而这个libtensorflowlite.so,包含了tensorflow lite几乎所有核心framework代码,因此肯定很大。

3. 因为我们仅需要用到tensorflow lite里model 初始化,interpreter推理等基础功能,并不需要tensorflow lite里的其他功能,因此,想要最小,直接在我们的JNI文件里,集成tensorflow lite相关类的源码进行编译,应该就能使得体积增加最小化了。

把我们JNI文件依赖的类,比如

#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/model.h"

等.h和.cc引入我们的JNI里,一起编译就行了。

一开始是在android studio里,导入tensorflow lite的源码, 修改CmakeLists.txt,尝试编译出可以独立运行的JNI so, 但是总是失败。

最后,把JNI文件,放到tensorflow lite的源码目录里,利用tensorflow的编译工具bazel,编译成功。然后把生成的milc_jni.so放到app的jniLibs里,成功:

a. 在tensorflow/lite/下创建milc_jni/这个目录,目录下创建BUILD,milc_jni.cc, custom_op_resolver.h和custom_op_resolver.cc

b. 根据我们的模型文件model.tflite里用到的算子,比如,我只用了FULLY_CONNECTED,RELU, LOGISTIC这3个算子,定制精简算子的Resolver类

custom_op_resolver.h

#ifndef TENSORFLOW_LITE_CUSTOM_OP_RESOLVER_H_
#define TENSORFLOW_LITE_CUSTOM_OP_RESOLVER_H_

#include "tensorflow/lite/mutable_op_resolver.h"

namespace tflite {

class MinimalOpResolver : public MutableOpResolver {
 public:
  MinimalOpResolver();
};

}  // namespace tflite

#endif  // TENSORFLOW_LITE_CUSTOM_OP_RESOLVER_H_

custom_op_resolver.cc

#include "tensorflow/lite/milc_jni/custom_op_resolver.h"
#include "tensorflow/lite/kernels/builtin_op_kernels.h"

namespace tflite {
MinimalOpResolver::MinimalOpResolver() {
  // 使用 kernels::builtin:: 命名空间下的注册函数
  AddBuiltin(BuiltinOperator_FULLY_CONNECTED, 
             tflite::ops::builtin::Register_FULLY_CONNECTED());
  AddBuiltin(BuiltinOperator_RELU, 
             tflite::ops::builtin::Register_RELU());
  AddBuiltin(BuiltinOperator_LOGISTIC, 
             tflite::ops::builtin::Register_LOGISTIC());
}
}  // namespace tflite

c. 创建JNI文件milc_jni.cc

#include <jni.h>
#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/model.h"
#include "tensorflow/lite/milc_jni/custom_op_resolver.h"
#include <android/log.h>

#define LOG_TAG "TensorFlowLiteJNI"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)

// 移除所有日志输出
//#define LOGI(...)
//#define LOGE(...)

extern "C" JNIEXPORT jfloat JNICALL
Java_com_xm_j_milc_predictJNI(JNIEnv* env, jobject /* this */, jstring modelPath, jfloatArray inputArray) {
    const char* modelPathStr = env->GetStringUTFChars(modelPath, nullptr);

    // 获取输入数组
    jfloat* inputElements = env->GetFloatArrayElements(inputArray, nullptr);
    jsize inputLength = env->GetArrayLength(inputArray);

    if (inputLength != 31) {
        LOGE("Input array length must be 31");
        env->ReleaseStringUTFChars(modelPath, modelPathStr);
        env->ReleaseFloatArrayElements(inputArray, inputElements, JNI_ABORT);
        return -1.0;
    }

    // 加载 TensorFlow Lite 模型
    std::unique_ptr<tflite::FlatBufferModel> model = tflite::FlatBufferModel::BuildFromFile(modelPathStr);
    if (!model) {
        LOGE("Failed to load model from %s", modelPathStr);
        env->ReleaseStringUTFChars(modelPath, modelPathStr);
        env->ReleaseFloatArrayElements(inputArray, inputElements, JNI_ABORT);
        return -1.0;
    }

    // 创建解释器
    //tflite::ops::builtin::BuiltinOpResolver resolver;
    tflite::MinimalOpResolver resolver;
    std::unique_ptr<tflite::Interpreter> interpreter;
    tflite::InterpreterBuilder(*model, resolver)(&interpreter);
    if (!interpreter) {
        LOGE("Failed to create interpreter");
        env->ReleaseStringUTFChars(modelPath, modelPathStr);
        env->ReleaseFloatArrayElements(inputArray, inputElements, JNI_ABORT);
        return -1.0;
    }

    // 分配张量
    if (interpreter->AllocateTensors() != kTfLiteOk) {
        LOGE("Failed to allocate tensors");
        env->ReleaseStringUTFChars(modelPath, modelPathStr);
        env->ReleaseFloatArrayElements(inputArray, inputElements, JNI_ABORT);
        return -1.0;
    }

    // 设置输入
    float* input = interpreter->typed_input_tensor<float>(0);
    for (int i = 0; i < inputLength; ++i) {
        input[i] = inputElements[i];
    }

    // 运行推理
    if (interpreter->Invoke() != kTfLiteOk) {
        LOGE("Failed to invoke interpreter");
        env->ReleaseStringUTFChars(modelPath, modelPathStr);
        env->ReleaseFloatArrayElements(inputArray, inputElements, JNI_ABORT);
        return -1.0;
    }

    // 获取输出
    // 5. 获取输出结果
    float* outputTensor = interpreter->typed_output_tensor<float>(0);


    // 释放资源
    env->ReleaseStringUTFChars(modelPath, modelPathStr);
    env->ReleaseFloatArrayElements(inputArray, inputElements, JNI_ABORT);
    return outputTensor[0];  // 直接返回标量值
}

d. 创建BUILD文件

# 自定义操作解析器(仅包含必要算子)
cc_library(
    name = "custom_op_resolver",
    srcs = ["custom_op_resolver.cc"],
    hdrs = ["custom_op_resolver.h"],
    deps = [
        "//tensorflow/lite/kernels:builtin_ops",
    ],
)

cc_binary(
    name = "milc_jni.so",
    srcs = ["milc_jni.cc"],
    linkshared = True,
    linkstatic = True,  # 静态链接所有依赖
    deps = [
	    ":custom_op_resolver",
    	"//tensorflow/lite:framework",
        "//tensorflow/lite/kernels:builtin_ops",
    	"@flatbuffers//:flatbuffers",
    ],
    copts = [
    	"-Oz",
	    "-flto=thin",
        "-ffunction-sections",
        "-fdata-sections",
        "-fvisibility=hidden",
    	"-fvisibility-inlines-hidden",
        "-DFLATBUFFERS_RELEASE",
    	"-DTF_LITE_STRIP_ERROR_STRINGS=1",
        "-DNDEBUG",
	    "-DFORCE_MINIMAL_LOGGING",
    	"-fno-exceptions",
        "-fno-rtti",
        "-fno-unwind-tables",
        "-fno-asynchronous-unwind-tables",
        "-ffreestanding",
    ],
    linkopts = [
	    "-flto=thin",
        "-Wl,--gc-sections",
        "-Wl,--exclude-libs,ALL",
        "-s", 
    	"-Wl,--as-needed",
	    "-Wl,-z,norelro",
    	"-Wl,--build-id=none",  # 移除构建ID
        "-Wl,--strip-all",  # 彻底去除符号
	    "-nostdlib",
        "-lc",
        "-Wl,--hash-style=gnu",  # 更小的哈希表
        "-Wl,--compress-debug-sections=zlib",  # 压缩调试节
    ],
    features = [
    	"-layering_check",
    ],
)

e. 在tensorflow的源码目录里,初始化好环境,AndroidNDK之类的,然后执行编译

bazel build -c opt   --config=android_arm64   --copt="-DFORCE_DISABLE_ALL_OPS"   --linkopt="-Wl,--gc-sections"   --linkopt="-Wl,--exclude-libs,ALL"   --linkopt="-s" --define=tflite_with_xnnpack=false  --copt="-Os" --copt="-fomit-frame-pointer"   --copt="-ffunction-sections"   --copt="-fdata-sections"   --copt="-fvisibility=hidden"   --copt="-g0" --copt="-DFLATBUFFERS_RELEASE"  //tensorflow/lite/milc_jni:milc_jni.so

然后,就会生成一个milc_jni.so,大约500K,它是可以独立运行的,不用依赖libtensorflowlite.so,因此,APK的size,也就只会增加约500K。

f.针对生成的milc_jni.so,进一步压缩优化

sudo apt-get install upx
upx --android-shlib --best --lzma milc_jni.so -o milc_jni_upx.so

最终的milc_jni_upx.so大约200K,因此,APK的size,也就只会增加约200K。

<think>好的,我现在需要帮助用户解决在Docker中运行TrafficMon时现的libfreetype.so.6缺失的问题。用户之前已经询问过如何在普通Linux系统中解决这个问题,现在转到了Docker环境,可能需要不同的方法。 首先,我要回忆之前的解决方案。在普通系统中,用户需要安装libfreetype6或者手动链接库文件。但在Docker中,可能需要修改Dockerfile或者在容器内安装依赖。用户提供的引用[1]中提到安装其他库(如libGL.so.1)时使用了apt-get命令,这可能提示在Docker镜像构建过程中需要添加相应的包。 接下来,我需要考虑用户的具体情况。他们可能在构建Docker镜像时没有包含必要的依赖库,或者在运行容器时环境变量没有正确设置。可能的解决方案包括:在Dockerfile中添加安装libfreetype6的步骤,或者在运行容器时挂载库文件。不过挂载库可能不太安全,推荐在构建阶段安装依赖。 另外,用户提到的TrafficMon可能是一个特定的应用程序,需要确认libfreetype的版本是否兼容。比如,某些应用可能需要特定版本的libfreetype,可能需要从源码编译安装,但这会增加复杂性。用户提供的引用中也提到了构建镜像时使用代理,可能需要处理网络问题,但用户当前的问题是关于库缺失,所以可能暂时不需要考虑代理。 还需要检查用户是否在Dockerfile中正确安装了所有依赖。例如,在基于Ubuntu的镜像中,使用apt-get install libfreetype6。同时,用户可能需要运行apt-get update来更新软件源,否则可能找不到包。此外,安装后可能需要执行ldconfig来更新库缓存,但在Docker构建过程中可能需要显式执行。 另外,用户可能遇到的另一个问题是,即使安装了libfreetype6,也可能存在符号链接的问题,比如库文件存在但链接名称不正确。这时候可能需要手动创建符号链接,如ln -s libfreetype.so.6.18.3 libfreetype.so.6,并在Dockerfile中执行ldconfig。 用户还可能希望优化镜像大小,所以需要确保安装步骤是高效的,比如合并RUN指令,清理不必要的缓存。例如,在Dockerfile中将多个apt-get命令合并成一个RUN指令,减少镜像层数。 最后,需要验证解决方案是否有效,比如在构建镜像后运行容器,并检查libfreetype.so.6是否存在,或者运行应用程序测试是否仍然报错。如果用户使用的基础镜像不同(如Alpine),可能需要使用不同的包管理器(apk),但用户提供的引用中用的是Ubuntu,所以可能继续使用apt。 总结可能的步骤: 1. 修改Dockerfile,添加安装libfreetype6的步骤。 2. 确保在安装后更新库缓存。 3. 检查是否需要其他依赖库,如用户引用中的ffmpeg等。 4. 构建镜像并测试运行。 同时,用户可能想知道如何检查容器内的库是否安装正确,可以通过进入容器执行ldconfig -p | grep libfreetype来验证。 还需要提醒用户注意基础镜像的选择,如果使用的最小化镜像(如alpine),可能需要安装额外的依赖,或者使用更完整的镜像版本。此外,如果用户无法修改Dockerfile,可以尝试在运行容器时使用-v挂载宿主机的库,但这种方法可能存在兼容性问题,不推荐。 最后,生成相关问题,帮助用户进一步理解Docker中的依赖管理,比如多阶段构建、镜像优化、依赖冲突解决等。</think>### Docker中解决$libfreetype.so.6$缺失的完整方案 --- #### 1. **修改Dockerfile添加依赖安装** 在构建镜像时,通过`RUN`指令安装所需库(以Ubuntu/CentOS基础镜像为例): **Ubuntu/Debian基础镜像**: ```dockerfile FROM ubuntu:20.04 # 安装libfreetype6及必要工具 RUN apt-get update && \ apt-get install -y libfreetype6 && \ rm -rf /var/lib/apt/lists/* ``` **CentOS/RHEL基础镜像**: ```dockerfile FROM centos:7 RUN yum install -y freetype && \ yum clean all ``` --- #### 2. **多阶段构建优化** 若需减少镜像体积,可使用多阶段构建: ```dockerfile # 阶段1:构建环境 FROM ubuntu:20.04 as builder RUN apt-get update && apt-get install -y libfreetype6 # 阶段2:运行时镜像 FROM ubuntu:20.04 COPY --from=builder /usr/lib/x86_64-linux-gnu/libfreetype.so.6* /usr/lib/ ``` --- #### 3. **运行时验证库路径** 构建完成后,进入容器检查库是否生效: ```bash docker build -t trafficmon . docker run -it --rm trafficmon bash -c "ldconfig -p | grep libfreetype.so.6" ``` 输应包含类似: ``` libfreetype.so.6 (libc6,x86-64) => /usr/lib/x86_64-linux-gnu/libfreetype.so.6 ``` --- #### 4. **特殊场景处理** - **符号链接问题**: 若库版本不匹配(如基础镜像已安装更高版本$libfreetype.so.7$),需手动创建兼容链接: ```dockerfile RUN ln -s /usr/lib/libfreetype.so.7 /usr/lib/libfreetype.so.6 && \ ldconfig ``` - **自定义安装路径**: 若从源码编译安装,需显式配置库路径: ```dockerfile RUN ./configure --prefix=/opt/freetype && \ make && make install ENV LD_LIBRARY_PATH=/opt/freetype/lib:$LD_LIBRARY_PATH ``` --- #### 5. **依赖冲突排查** 若同时存在其他库依赖(如用户引用[1]中提到的$libGL.so.1$),需组合安装命令: ```dockerfile RUN apt-get update && \ apt-get install -y libfreetype6 ffmpeg libsm6 libxext6 && \ rm -rf /var/lib/apt/lists/* ``` --- ### 关键注意事项 1. **缓存清理**:在`apt-get install`后使用`rm -rf /var/lib/apt/lists/*`减少镜像体积。 2. **基础镜像选择**:优先使用包含完整工具链的镜像(如`ubuntu:20.04`而非`alpine`),避免依赖缺失。 3. **版本兼容性**:若应用依赖特定版本,需指定包版本: ```dockerfile RUN apt-get install -y libfreetype6=2.10.1-2 ``` --- ### 相关问题 1. 如何通过`docker exec`检查容器内已安装的共享库? 2. Docker多阶段构建如何减少镜像体积? 3. 如何处理Docker容器中的动态库版本冲突问题? 4. 为什么Alpine镜像更容易现共享库缺失问题?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值