在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。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/81873.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

neo4j框架:java安装教程

安装使用neo4j需要事先安装好java&#xff0c;java版本的选择是一个犯难的问题。本文总结了在安装java和使用Java过程中遇到的问题以及相应的解决方法。 Java的安装包可以在java官方网站Java Downloads | Oracle 中国进行下载 以java 8为例&#xff0c;选择最后一行的x64 compr…

[服务器备份教程] Rclone实战:自动备份数据到阿里云OSS/腾讯云COS等对象存储

更多服务器知识&#xff0c;尽在hostol.com 各位服务器的守护者们&#xff0c;咱们都知道&#xff0c;数据是数字时代的“黄金”&#xff0c;而服务器上的数据更是我们业务的命脉。可天有不测风云&#xff0c;硬盘可能会突然“寿终正寝”&#xff0c;手滑执行了“毁灭性”命令…

Nextjs App Router 开发指南

Next.js是一个用于构建全栈web应用的React框架。App Router 是 nextjs 的基于文件系统的路由器&#xff0c;它使用了React的最新特性&#xff0c;比如 Server Components, Suspense, 和 Server Functions。 术语 树(Tree): 一种用于可视化的层次结构。例如&#xff0c;包含父…

山东大学计算机图形学期末复习15——CG15

CG15 OpenGL缓冲区、读写操作以及混合&#xff08;Blending&#xff09; 一、OpenGL缓冲区概述 OpenGL中的缓冲区是用于存储像素数据的内存区域&#xff0c;主要包括以下类型&#xff1a; 颜色缓冲区&#xff08;Color Buffer&#xff09;&#xff1a;存储每个像素的颜色值…

html+css+js趣味小游戏~记忆卡片配对(附源码)

下面是一个简单的记忆卡片配对游戏的完整代码&#xff0c;使用HTML、CSS和JavaScript实现&#xff1a; html <!DOCTYPE html> <html lang"zh"> <head><meta charset"UTF-8"><meta name"viewport" content"wid…

⼀个并发访问量⽐较⼤的key在某个时间过期,在redis中这个时间过期什么意思

在 Redis 中&#xff0c;当提到一个键&#xff08;key&#xff09;“在这个时间过期”&#xff0c;指的是为该键设置了生存时间&#xff08;TTL, Time To Live&#xff09;或过期时间&#xff08;expiration time&#xff09;。一旦到达设定的过期时间&#xff0c;Redis 会自动…

【设计模式】- 行为型模式1

模板方法模式 定义了一个操作中的算法骨架&#xff0c;将算法的一些步骤推迟到子类&#xff0c;使得子类可以不改变该算法结构的情况下重定义该算法的某些步骤 【主要角色】&#xff1a; 抽象类&#xff1a;给出一个算法的轮廓和骨架&#xff08;包括一个模板方法 和 若干基…

ubuntu22.04 卸载ESP-IDF

要在Ubuntu 22.04上完全卸载ESP-IDF&#xff0c;请按照以下步骤操作&#xff1a; 卸载ESP-IDF的步骤 删除ESP-IDF目录&#xff1a; # 假设ESP-IDF安装在~/esp/esp-idf目录 rm -rf ~/esp/esp-idf删除ESP-IDF工具链和下载的工具&#xff1a; rm -rf ~/.espressif从PATH中移除ESP…

SQLMesh 内置宏详解:@PIVOT等常用宏的核心用法与示例

本文系统解析 SQLMesh 的四个核心内置宏&#xff0c;涵盖行列转换的 PIVOT、精准去重的 DEDUPLICATE、灵活生成日期范围的 DATE_SPINE&#xff0c;以及动态表路径解析的 RESOLVE_TEMPLATE。通过真实案例演示参数配置与 SQL 渲染逻辑&#xff0c;并对比宏调用与传统 SQL 的差异&…

基于Springboot + vue3实现的工商局商家管理系统

项目描述 本系统包含管理员、商家两个角色。 管理员角色&#xff1a; 用户管理&#xff1a;管理系统中所有用户的信息&#xff0c;包括添加、删除和修改用户。 许可证申请管理&#xff1a;管理商家的许可证申请&#xff0c;包括搜索、修改或删除许可证申请。 许可证审批管理…

第五部分:第五节 - Express 路由与中间件进阶:厨房的分工与异常处理

随着你的 Express 应用变得越来越大&#xff0c;所有的路由和中间件都写在一个文件里会变得难以管理。这时候就需要将代码进行拆分和组织。此外&#xff0c;一个健壮的后端应用必须能够优雅地处理错误和一些常见的 Web 开发问题&#xff0c;比如跨域。 路由模块化 (express.Ro…

萌新联赛第(三)场

C题 这道题用暴力去写想都不要想&#xff0c;一定超时&#xff0c;于是我们需要优化&#xff0c;下面是思路过程&#xff1a; 如图&#xff0c;本题只需找到x的因数个数和(n-x)的因数个数&#xff0c;这两个相乘&#xff0c;得到的就是对于这个x来说组合的个数&#xff0c;且x…

【Android构建系统】如何在Camera Hal的Android.bp中选择性引用某个模块

背景描述 本篇文章是一个Android.bp中选择性引用某个模块的实例。 如果是Android.mk编译时期&#xff0c;在编译阶段通过某个条件判断是不是引用某个模块A, 是比较好实现的。Android15使用Android.bp构建后&#xff0c;要想在Android.bp中通过自定义的一个变量或者条件实现选…

【OneNET】_01_使用微信小程序通过新版OneNET平台获取STM32设备信息并进行控制

【OneNET】_01_使用微信小程序通过新版OneNET平台获取STM32设备信息并进行控制 一、 前言1.1 OntNET硬件方面: STM32F103C8T6 ESP01S教程 1.2 微信小程序方面 二、STM32代码部分修改三、微信小程序修改的部分四、小笔记&#xff08;个人杂记&#xff09;4.1 OneNETOneNET物联网…

用 python 编写的一个图片自动分类小程序(三)

图片自动分类识别小程序记录 2025/5/18 0:38修改程序界面&#xff0c;增加一些功能 用 python 编写的一个图片自动识别分类小程序。 操作系统平台&#xff1a;Microsoft Windows 11 编程语言和 IDE&#xff1a;python 3.10 Visual studio code 一&#xff1a;图片自动分…

嵌入式硬件篇---SGP30 气体传感器

文章目录 前言一、SGP30 气体传感器详解(一)基本概述(二)工作原理传感器结构检测机制自校准功能(三)主要特性(四)应用场景智能家居空气质量检测仪汽车行业商业建筑二、TVOC 与 eCO2 的含义(一)TVOC(总挥发性有机化合物)定义危害健康标准(二)eCO2(等效二氧化碳)…

【原创】ubuntu22.04下载编译AOSP 15

安装依赖的库&#xff0c;顺便把vim 也安装一下 sudo apt-get install vim sudo apt-get install git gnupg flex bison build-essential zip curl zlib1g-dev libc6-dev-i386 x11proto-core-dev libx11-dev lib32z1-dev libgl1-mesa-dev libxml2-utils xsltproc unzip font…

防止勒索病毒的兜底方案——备份

勒索病毒入侵会对您的业务数据进行加密勒索&#xff0c;导致业务中断、数据泄露、数据丢失等&#xff0c;从而带来严重的业务风险。 防止勒索病毒有三个方向&#xff1a; 1&#xff09;实时防御已知勒索病毒 各个云厂商的云安全中心实现了对大量已知勒索病毒的实时防御。在服务…

es在已有历史数据的文档新增加字段操作

新增字段设置默认值 场景 在已经有大量数据的索引文档上&#xff0c;增加新字段 技术实现 一.更新索引映射 通过PUT请求显式定义新字段类型&#xff0c;确保后续写入的文档能被正确解析 PUT /文档名/_mapping {"properties": {"字段名1": {"type…

留给王小川的时间不多了

王小川&#xff0c;这位头顶“天才少年”光环的清华学霸、搜狗输入法创始人、中国互联网初代技术偶像&#xff0c;正迎来人生中最难啃的硬骨头。 他在2023年创立的百川智能&#xff0c;被称为“大模型六小虎”之一。今年4月&#xff0c;王小川在全员信中罕见地反思过去两年工作…