
Flipper Zero 外部应用 Apps Assets 资源文件夹机制详解fap_file_assets 打包与 /assets 别名解析【免费下载链接】flipperzero-firmwareFlipper Zero firmware source code项目地址: https://gitcode.com/GitHub_Trending/fl/flipperzero-firmware导读本文基于 Flipper Zero 固件仓库中 example_apps_assets 示例应用系统讲解外部应用.fap如何将随应用分发的静态资源游戏关卡、数据文件、插件脚本等打包进 Flipper 固件生态并在运行时通过 Apps Assets 文件夹自动解包、按需读取。读完本文你将掌握fap_file_assets清单字段的声明方式、/assets别名与APP_ASSETS_PATH()宏的正确用法、Apps Assets 与 Apps Data 的本质区别以及基于.assets.signature哈希签名的资源增量更新原理——这些都是开发可分发 FAP 应用的必备技能。Apps Assets 文件夹是什么Apps Assets是 Flipper Zero 上用于存放外部应用所携带静态资源assets的专用文件夹。与内置应用不同外部应用External App以.fap文件形式分发其随附的配置文件、游戏关卡、音效、插件二进制等数据并不能保证随固件一起刷入设备因此固件提供了一套标准化的“应用资源”机制资源在应用构建时被捆绑进.fap文件内部应用首次启动或资源更新时由 Flipper 固件自动解包到外部存储SD 卡应用运行时通过别名路径或宏访问解包后的文件。关键路径约束如下概念说明存储位置仅位于外部存储SD 卡即EXT_PATH(apps_assets)对应的/ext/apps_assets子目录命名与应用的appid一一对应例如appid为snake_game时路径为/ext/apps_assets/snake_game官方推荐访问方式/assets别名或APP_ASSETS_PATH()宏不推荐硬编码原始路径该文件夹在存储服务中的路径前缀定义位于 applications/services/storage/storage.h#L19#define STORAGE_APP_ASSETS_PATH_PREFIX /assets #define APP_ASSETS_PATH(path) STORAGE_APP_ASSETS_PATH_PREFIX / pathappid是应用的唯一标识定义在application.fam清单文件中同时用于应用商店中的应用识别与 Apps Assets 目录命名。如何获取 Apps Assets 文件夹路径文档明确给出了三种获取路径的方式推荐程度依次递增。方式一硬编码原始路径不推荐直接写死完整路径例如/ext/apps_assets/example_apps_assets// 不推荐路径在未来版本可能变化 file_stream_open(stream, /ext/apps_assets/example_apps_assets/test_asset.txt, FSAM_READ, FSOM_OPEN_EXISTING);不推荐的原因是 SD 卡挂载方式、存储布局在未来可能调整硬编码路径会导致应用在固件升级后失效。方式二/assets别名次推荐/assets是存储服务提供的虚拟别名会被自动解析为“当前应用对应的 Apps Assets 目录”。例如读取database.txt可写作/data/database.txt——注意原文档此处为笔误正确别名前缀应为/assets即file_stream_open(stream, /assets/test_asset.txt, FSAM_READ, FSOM_OPEN_EXISTING);/assets别名在存储服务内部applications/services/storage/storage_processing.c#L545-L556会被替换为真实路径} else if(furi_string_start_with(path, STORAGE_APP_ASSETS_PATH_PREFIX)) { FuriString* apps_assets_path_with_appsid furi_string_alloc_set(APPS_ASSETS_PATH /); furi_string_cat(apps_assets_path_with_appsid, furi_thread_get_appid(thread_id)); // /assets - /ext/apps_assets/appsid furi_string_replace_at( path, 0, strlen(STORAGE_APP_ASSETS_PATH_PREFIX), furi_string_get_cstr(apps_assets_path_with_appsid)); ... }可以看到别名的解析依赖于furi_thread_get_appid(thread_id)获取的当前线程所属应用 id因此别名只能在应用自身线程中使用。方式三APP_ASSETS_PATH()宏推荐最稳妥的做法是使用宏它在编译期展开为/assets/相对路径避免手工拼接字符串出错APP_ASSETS_PATH(database.txt) // 展开为 /assets/database.txt三种方式的路径对照总结如下以appid example_apps_assets、读取test_asset.txt为例方式示例写法推荐度硬编码/ext/apps_assets/example_apps_assets/test_asset.txt不推荐别名/assets/test_asset.txt次推荐宏APP_ASSETS_PATH(test_asset.txt)推荐Apps Assets 与 Apps Data 的区别两者都是外部应用常用的存储目录但用途截然相反维度Apps Assets 文件夹Apps Data 文件夹数据性质应用随附提供provided的静态内容应用运行时生成generated的数据典型示例游戏关卡内容level1.txt 等、插件脚本、只读配置文件游戏存档进度、用户设置、日志、缓存生命周期随应用更新自动覆盖重建由应用自行读写维护应用更新时保留路径别名/assetsSTORAGE_APP_ASSETS_PATH_PREFIX/dataSTORAGE_APP_DATA_PATH_PREFIX定义于 storage.h#L18物理位置/ext/apps_assets/appid/ext/apps_data/appid一个直观的取舍标准凡是会随版本升级被替换的内容放 Assets凡是用户产生的、需要持久保留的内容放 Data。若把存档放进 Assets应用更新时整目录会被删除重写用户进度将丢失反之把只读资源放进 Data则会白白占用 SD 卡空间且失去版本同步能力。如何随应用打包数据fap_file_assets目录准备在应用源码目录内创建一个普通文件夹例如files把要分发的资源放进去。以本示例应用的实际结构为例见 applications/examples/example_apps_assets/example_apps_assets ├── application.fam ├── example_apps_assets.c └── files ├── test_asset.txt └── poems ├── a jelly-fish.txt ├── my shadow.txt └── theme in yellow.txt注意文件夹名可以任意取但必须与清单中的fap_file_assets字段保持一致。清单声明在application.fam中为App(...)增加fap_file_assetsfiles字段。本示例的完整清单见 applications/examples/example_apps_assets/application.famApp( appidexample_apps_assets, nameExample: Apps Assets, apptypeFlipperAppType.EXTERNAL, entry_pointexample_apps_assets_main, requires[gui], stack_size4 * 1024, fap_categoryExamples, fap_file_assetsfiles, )字段说明字段值作用appidexample_apps_assets应用唯一标识决定/ext/apps_assets/appid目录名apptypeFlipperAppType.EXTERNAL外部应用构建产物为.fapentry_pointexample_apps_assets_main应用入口函数fap_file_assetsfiles声明相对应用目录的打包资源文件夹从构建系统源码看fap_file_assets仅对可分发的外部应用.fap合法在 scripts/fbt/appmanifest.py#L161-L167 中分发类应用AppBuildset.DIST_APP_TYPES被禁止使用内部应用的resources字段而必须通过fap_file_assets提供资源反之内部应用也不能使用fap_file_assets。清单校验器会在解析时强制这一约束。构建期捆绑构建系统在打包.fap时会将files目录整体封装成一段二进制资源段。相关逻辑位于 scripts/fbt_tools/fbt_extapps.py#L357-L366def prepare_app_file_assets(target, source, env): files_section_node next( filter(lambda t: t.name.endswith(_FAP_FILEASSETS_SECTION), target) ) bundler FileBundler( list(env.Dir(asset_dir).abspath for asset_dir in env[APP]._assets_dirs) ) bundler.export(files_section_node.abspath)随后在 fbt_extapps.py#L385-L394 中通过objcopy --add-section把该资源段写入.fap文件成为 ELF 中的一个只读段。也就是说资源随应用一起分发不需要额外拷贝文件到 SD 卡。运行时解包结果应用启动后files文件夹会被自动解包到 Apps Assets 目录最终结构为/assets ├── .assets.signature └── poems ├── a jelly-fish.txt ├── my shadow.txt └── theme in yellow.txt示例目录结构中的test_asset.txt同样会被解包解包后会在目录根部生成签名文件.assets.signature。目录结构与源文件夹保持一致文件名中的空格等字符原样保留。数据何时解包哈希签名增量机制解包不是每次启动都全量执行而是由签名比对驱动构建期files目录内容被哈希哈希值随资源段一起固化进.fap文件首次启动应用目录下不存在.assets.signature签名比对失败于是删除旧目录并全量解包同时写入新的签名文件后续启动固件将内嵌哈希与.assets.signature中的哈希比对若一致则跳过解包直接使用应用更新后内嵌哈希发生变化比对不一致触发“删除旧目录 → 重新解包”。该逻辑的实现位于 lib/flipper_application/application_assets.c签名文件名为常量FLIPPER_APPLICATION_ASSETS_SIGNATURE_FILENAME即.assets.signatureapplication_assets.c#L16资源段头包含 magic0x4F4C5A44、版本号、目录数与文件数application_assets.c#L22-L27签名比对函数flipper_application_assets_process_signature用memcmp比较内嵌签名与磁盘签名文件内容返回AssetsSignatureResultEqual / NotEqual / Error三种结果application_assets.c#L242-L245当签名不一致时先storage_simply_remove_recursive递归删除旧资源目录再重新mkdir并写入新文件application_assets.c#L297-L316。这一机制保证了应用升级后资源目录与.fap内资源严格同步同时避免每次启动都重复解包造成启动变慢与 SD 卡磨损。实战完整示例应用剖析读取资源文件的示例代码本示例的入口实现见 applications/examples/example_apps_assets/example_apps_assets.c。核心流程为打开存储服务 → 通过APP_ASSETS_PATH()构造资源路径 → 用file_stream逐行读取并打印。static void example_apps_data_print_file_content(Storage* storage, const char* path) { Stream* stream file_stream_alloc(storage); FuriString* line furi_string_alloc(); FURI_LOG_I(TAG, ----------------------------------------); FURI_LOG_I(TAG, File \%s\ content:, path); if(file_stream_open(stream, path, FSAM_READ, FSOM_OPEN_EXISTING)) { while(stream_read_line(stream, line)) { furi_string_replace_all(line, \r, ); furi_string_replace_all(line, \n, ); FURI_LOG_I(TAG, %s, furi_string_get_cstr(line)); } } else { FURI_LOG_E(TAG, Failed to open file); } FURI_LOG_I(TAG, ----------------------------------------); furi_string_free(line); file_stream_close(stream); stream_free(stream); } // Application entry point int32_t example_apps_assets_main(void* p) { UNUSED(p); Storage* storage furi_record_open(RECORD_STORAGE); example_apps_data_print_file_content(storage, APP_ASSETS_PATH(test_asset.txt)); example_apps_data_print_file_content(storage, APP_ASSETS_PATH(poems/a jelly-fish.txt)); example_apps_data_print_file_content(storage, APP_ASSETS_PATH(poems/theme in yellow.txt)); example_apps_data_print_file_content(storage, APP_ASSETS_PATH(poems/my shadow.txt)); furi_record_close(RECORD_STORAGE); return 0; }要点拆解furi_record_open(RECORD_STORAGE)获取存储服务句柄用完必须furi_record_closeAPP_ASSETS_PATH(test_asset.txt)在编译期展开为/assets/test_asset.txt运行时由存储服务解析到example_apps_assets专属目录路径中的空格如poems/a jelly-fish.txt是合法文件名字符直接传入即可读取失败时打印Failed to open file便于排查签名解包异常或路径错误。示例资源文件 applications/examples/example_apps_assets/files/test_asset.txt 内容为一行## This is test file content可作为验证资源是否正确解包的最小测试对象。其他真实用法仓库中还有两个直接依赖APP_ASSETS_PATH的范例applications/debug/expansion_test/expansion_test.c#L370扩展模块测试应用通过APP_ASSETS_PATH(TEST_FILE_NAME)打开随应用打包的测试文件并断言打开成功applications/examples/example_plugins_advanced/example_advanced_plugins.c#L26-L28插件高级示例用APP_ASSETS_PATH(plugins)加载内置.fal插件文件fal_embedded True时展示将插件二进制作为资产分发的典型场景。可见 Assets 机制不仅用于文本数据也常用来分发插件、脚本等可执行内容。常见问题与最佳实践常见问题解包后找不到文件先检查fap_file_assets声明的文件夹名与资源实际目录名是否一致再确认appid是否改变appid变化会导致/assets指向新目录旧目录残留。升级后旧数据残留签名机制在资源变化时会先删除整个应用资源目录再重建因此不必担心旧文件残留但如果误将用户数据放进 Assets升级时会被一并清除。别名在非应用线程不可用/assets的解析依赖线程的appid在系统线程、回调线程等非应用上下文中可能无法正确解析此时应显式构造/ext/apps_assets/appid/...路径。最佳实践始终用APP_ASSETS_PATH()宏避免硬编码/ext/apps_assets/appid与/assets只读资源放 Assets可写数据放 Apps Data/data别名对应APP_DATA_PATH()宏资源目录保持扁平、文件名避免依赖大小写敏感特性便于跨平台维护目录结构即资源结构先设计好files内部层级再写代码测试时优先使用files/test_asset.txt这类小文件验证解包链路再引入复杂资源。延伸阅读示例应用源码与文档applications/examples/example_apps_assets/存储路径别名与宏定义applications/services/storage/storage.h#L15-L25别名运行时解析实现applications/services/storage/storage_processing.c#L523-L556资源解包与签名比对实现lib/flipper_application/application_assets.c清单校验规则scripts/fbt/appmanifest.py#L161-L173资源段打包流程scripts/fbt_tools/fbt_extapps.py#L357-L394【免费下载链接】flipperzero-firmwareFlipper Zero firmware source code项目地址: https://gitcode.com/GitHub_Trending/fl/flipperzero-firmware创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考