AndroidPatch Module (APM)
约 4156 字大约 14 分钟
2025-12-23
FolkPatch 提供了一个模块机制( AndroidPatch Module ),它可以在保持系统分区完整性的同时达到修改系统分区的效果;这种机制通常被称之为 systemless。
FolkPatch 的模块实现是从 APatch 模块复制并修改而来,感谢 APatch。
以下的文档内容基本来自于 KernelSU 的文档,其中大部分内容与 KernelSU 方面保持一致。需要注意的主要有以下几个地方:
- 文件位置
- 环境变量
- SELinux 支持,FolkPatch 直接使用了
magiskpolicy
FolkPatch 的模块运作机制与 Magisk 几乎是一样的,如果你熟悉 Magisk 模块的开发,那么开发 FolkPatch 的模块大同小异,你可以跳过下面有关模块的介绍,只需要了解 FolkPatch 模块与 Magisk 模块的异同。
BusyBox
FolkPatch 提供了一个功能完备的 BusyBox 二进制文件(包括完整的 SELinux 支持)。可执行文件位于 /data/adb/ap/bin/busybox。 FolkPatch 的 BusyBox 支持运行时可切换的 "ASH Standalone Shell Mode"。 这种独立模式意味着在运行 BusyBox 的 ash shell 时,每个命令都会直接使用 BusyBox 中内置的应用程序,而不管 PATH 设置为什么。 例如,ls、rm、chmod 等命令将不会使用 PATH 中设置的命令(在 Android 的情况下,默认情况下分别为 /system/bin/ls、/system/bin/rm 和 /system/bin/chmod),而是直接调用 BusyBox 内置的应用程序。 这确保了脚本始终在可预测的环境中运行,并始终具有完整的命令套件,无论它运行在哪个 Android 版本上。 要强制一个命令不使用 BusyBox,你必须使用完整路径调用可执行文件。
在 FolkPatch 上下文中运行的每个 shell 脚本都将在 BusyBox 的 ash shell 中以独立模式运行。对于第三方开发者相关的内容,包括所有启动脚本和模块安装脚本。
对于想要在 FolkPatch 之外使用这个"独立模式"功能的用户,有两种启用方法:
- 设置环境变量
ASH_STANDALONE为1。
例如:ASH_STANDALONE=1 /data/adb/ap/bin/busybox sh <script>。 - 使用命令行选项切换:
/data/adb/ap/bin/busybox sh -o standalone <script>。
为了确保所有后续的 sh shell 都在独立模式下执行,第一种是首选方法(这也是 FolkPatch 内部使用的方法),因为环境变量会被继承到子进程中。
与 Magisk 的差异
FolkPatch 的 BusyBox 现在是直接使用 Magisk 项目编译的二进制文件,感谢 Magisk! 因此,你完全不用担心 BusyBox 脚本在 Magisk 和 FolkPatch 之间的兼容问题,因为它们是完全一样的!
FolkPatch 模块
FolkPatch 模块就是一个放置在 /data/adb/modules 内且满足如下结构的文件夹:
MODID# 模块的文件夹名称与模块 ID 相同
module.prop# 模块配置(ID、版本等)
system# 此目录通常会被挂载到系统
...
skip_mount# 若存在,模块的 /system 将不会被挂载
disable# 若存在,模块会被禁用
remove# 若存在,下次重启时模块会被移除
post-fs-data.sh# post-fs-data 模式下运行
post-mount.sh# post-mount 模式下运行
service.sh# late_start 服务模式下运行
boot-completed.sh# Android 启动完毕后以服务模式运行
uninstall.sh# 模块被卸载时运行
action.sh# 管理器中点击 Action 时运行
system.prop# 通过 resetprop 更改的系统属性
sepolicy.rule# SELinux 策略规则
vendor# → MODID/system/vendor(自动生成)
product# → MODID/system/product(自动生成)
system_ext# → MODID/system/system_ext(自动生成)
another_module
...
...
与 Magisk 的差异
FolkPatch 没有内置的针对 Zygisk 的支持,因此模块中没有 Zygisk 相关的内容。
module.prop
module.prop 是一个模块的配置文件,在 FolkPatch 中如果模块中不包含此文件,那么它将不被认为是一个模块;此文件的格式如下:
id=<string>
name=<string>
version=<string>
versionCode=<int>
author=<string>
description=<string>idRequiredstring
模块的唯一标识符,必须匹配正则 ^[a-zA-Z][a-zA-Z0-9._-]+$。例如:a_module、a.module、module-101。不合法如:a module、1_module、-a-module。发布后不应更改。
nameRequiredstring
模块的显示名称,可以是任意单行字符串。
versionRequiredstring
模块的版本名,可以是任意单行字符串。
versionCodeRequiredint
模块的版本号,必须是一个整数,用于版本比较。
authorRequiredstring
模块的作者名称,可以是任意单行字符串。
descriptionRequiredstring
模块的描述信息,可以是任意单行字符串。
请确保使用 UNIX(LF)换行类型,而不是Windows(CR + LF)或 Macintosh(CR)。
Shell 脚本
请阅读 启动脚本 一节,以了解 post-fs-data.sh, post-mount.sh, service.sh 和 boot-completed.sh 之间的区别。对于大多数模块开发者来说,如果您只需要运行一个启动脚本,service.sh 应该已经足够了。
在您的模块的所有脚本中,请使用 MODDIR=${0%/*}来获取您的模块的基本目录路径;请勿在脚本中硬编码您的模块路径。
与 Magisk、KernelSU 的差异
你可以通过环境变量 APATCH 来判断脚本是否运行在 FolkPatch 中,如果运行在 FolkPatch,这个值会被设置为 true。
FolkPatch 的模块系统与 APatch 在底层实现上保持一致,对于模块开发者而言,FolkPatch 模块与 APatch 模块完全兼容。
system 目录
模块 system 目录的内容会在系统启动后以挂载的方式叠加在系统的 /system 分区之上,这意味着:
- 系统中对应目录的同名文件会被此目录的文件覆盖。
- 系统中对应目录的同名文件夹会与此目录的文件夹合并。
FolkPatch 支持两种挂载实现:
Magic Mount(内置挂载)
FolkPatch 在 APatch 基础上新增了 Magic Mount(类 Magisk 的 bind mount 挂载机制)。当在设置中启用挂载系统后,FolkPatch 使用内置的 Magic Mount 引擎处理模块的 system 目录挂载。
此方式的工作原理与 Magisk 的 Magic Mount 类似,通过 bind mount 将模块文件映射到系统分区。
Magic Mount 由 /data/adb/.magic_mount_enable 标记文件控制启用,挂载源目录为 /data/adb/ap/magic_mount。
OverlayFS(通过元模块)
如果安装了 Meta Module(元模块),元模块可以通过 metamount.sh 脚本接管挂载逻辑,使用 OverlayFS 或其他自定义挂载方案。
在 OverlayFS 模式下:
- 如果你想删掉系统原来目录某个文件或者文件夹,你需要在模块目录通过
mknod filename c 0 0来创建一个filename的同名文件;这样 OverlayFS 系统会自动 whiteout 等效删除此文件。 - 如果你想替换掉系统的某个目录,你需要在模块目录创建一个相同路径的目录,然后为此目录设置此属性:
setfattr -n trusted.overlay.opaque -v y <TARGET>。
如果你对 OverlayFS 感兴趣,建议阅读 Linux Kernel 关于 OverlayFS 的文档。
挂载方式选择
FolkPatch 默认不挂载模块文件。如需挂载,选择以下方式之一:
- 内置 Magic Mount:通过设置 → 常规 → 启用挂载系统开启,无需额外模块
- 元模块挂载:安装元模块后,由元模块控制挂载行为
关于挂载方式的详细对比,请参阅 挂载实现。
system.prop
这个文件的格式与 build.prop 完全相同:每一行都是 [key]=[value] 的形式。
sepolicy.rule
如果您的模块需要一些额外的 SELinux 策略补丁,请将这些规则添加到此文件中。这个文件中的每一行都将被视为一个策略语句。
模块安装包
FolkPatch 的模块安装包就是一个可以通过 FolkPatch 管理器 APP 刷入的 zip 文件,此 zip 文件的格式如下:
module.zip
customize.sh# 可选,安装定制脚本
...# 其他模块文件
从外部安装
在文件管理器中选中模块的 .zip 文件,通过「分享」或「打开方式」选择 FolkPatch,即可直接跳转到模块安装页面。
选中多个 .zip 文件分享到 FolkPatch,则会跳转到批量安装页面。
注意
FolkPatch 模块不支持在 Recovery 中安装!
批量安装
FolkPatch 支持一次性安装多个 APM 模块,适合批量刷入场景。
进入批量安装页面
有两种方式进入批量安装页面:
- 管理器内:在系统模块(APM)页面,点击浮动菜单中的「批量刷入」按钮,进入批量安装页面后可手动添加模块
- 外部多选:在文件管理器中多选
.zip模块文件,通过「分享」选择 FolkPatch,自动跳转到批量安装页面
安装模式
批量安装默认使用快速安装模式(静默安装),所有模块按顺序自动安装,无需用户交互,安装完成后显示日志。
快速安装的限制
快速安装模式下不支持需要音量键交互的模块(如通过 customize.sh 调用 volume_key 的模块)。如果你的模块需要音量键操作,请在设置中开启「完整批量安装流程」。
如果你需要使用完整安装流程(逐个处理,支持音量键交互),请在 设置 → 模块 → APM 批量安装完整流程 中开启。开启后,批量安装会逐个通过完整安装界面处理每个模块。
定制安装过程
如果你想控制模块的安装过程,可以在模块的目录下创建一个名为 customize.sh 的文件,这个脚本将会在模块被解压后导入到当前 shell 中,如果你的模块需要根据设备的 API 版本或者设备构架做一些额外的操作,那这个脚本将非常有用。
如果你想完全控制脚本的安装过程,你可以在 customize.sh 中声明 SKIPUNZIP=1 来跳过所有的默认安装步骤;此时,你需要自行处理所有安装过程(如解压模块,设置权限等)。
customize.sh 脚本以"独立模式"运行在 FolkPatch 的 BusyBox ash shell 中。你可以使用如下变量和函数:
变量
KernelPatch 相关:
KERNELPATCHOptionalbool
标记此脚本运行在 KernelPatch 环境下,此变量的值将永远为 true
KERNEL_VERSIONOptionalhex
从 KernelPatch 继承,内核版本号 (如:50a01 是指 5.10.1)
KERNELPATCH_VERSIONOptionalhex
从 KernelPatch 继承,KernelPatch 版本号 (如:a05 是指 0.10.5)
SUPERKEYOptionalstring
从 KernelPatch 继承,用于调用 kpatch 或者 supercall
APatch/FolkPatch 相关:
APATCHOptionalbool
标记此脚本运行在 APatch/FolkPatch 环境下,此变量的值将永远为 true
APATCH_VER_CODEOptionalint
APatch 当前的版本号 (如:10672)
APATCH_VEROptionalstring
APatch 当前的版本名 (如:10672)
安装环境:
BOOTMODEOptionalbool
此变量在 FolkPatch 中永远为 true
MODPATHOptionalpath
当前模块的安装目录
TMPDIROptionalpath
可以存放临时文件的目录
ZIPFILEOptionalpath
当前模块的安装包文件
ARCHOptionalstring
设备的 CPU 构架,只有 arm64
IS64BITOptionalbool
是否是 64 位设备
APIOptionalint
当前设备的 Android API 版本 (如:Android 6.0 上为 23)
Magisk 兼容性
MAGISK_VER_CODE 在 FolkPatch 中为 27000,MAGISK_VER 则为 27.0。这是为了兼容 Magisk 模块而设置的兼容性变量,FolkPatch 本身并不基于 Magisk。
函数
ui_print <msg>
print <msg> to console
Avoid using 'echo' as it will not display in custom recovery's console
abort <msg>
print error message <msg> to console and terminate the installation
Avoid using 'exit' as it will skip the termination cleanup steps
set_perm <target> <owner> <group> <permission> [context]
if [context] is not set, the default is "u:object_r:system_file:s0"
this function is a shorthand for the following commands:
chown owner.group target
chmod permission target
chcon context target
set_perm_recursive <directory> <owner> <group> <dirpermission> <filepermission> [context]
if [context] is not set, the default is "u:object_r:system_file:s0"
for all files in <directory>, it will call:
set_perm file owner group filepermission context
for all directories in <directory> (including itself), it will call:
set_perm dir owner group dirpermission context启动脚本
在 FolkPatch 中,根据脚本运行模式的不同分为以下几种:
post-fs-data 模式
- 这个阶段是阻塞的。在执行完成之前或者 10 秒钟之后,启动过程会暂停。
- 脚本在任何模块被挂载之前运行。这使得模块开发者可以在模块被挂载之前动态地调整它们的模块。
- 这个阶段发生在 Zygote 启动之前。
- 使用
setprop会导致启动过程死锁!请使用resetprop -n <prop_name> <prop_value>代替。 - 只有在必要时才在此模式下运行脚本。
post-mount 模式
- 这个阶段在模块挂载完成后运行。
- 适合需要在模块挂载后执行的操作。
late_start 服务模式
- 这个阶段是非阻塞的。你的脚本会与其余的启动过程并行运行。
- 大多数脚本都建议在这种模式下运行。
boot-completed 模式
- 这个阶段在 Android 系统启动完毕后以服务模式运行。
- 适合需要在系统完全启动后执行的操作。
在 FolkPatch 中,启动脚本根据存放位置的不同还分为两种:通用脚本和模块脚本。
通用脚本
放置在
/data/adb/post-fs-data.d、/data/adb/post-mount.d、/data/adb/service.d或/data/adb/boot-completed.d中。/data/adb
post-fs-data.d# post-fs-data 模式
post-mount.d# post-mount 模式
service.d# late_start 服务模式
boot-completed.d# Android 启动完毕后
只有在脚本被设置为可执行(
chmod +x script.sh)时才会被执行。在
post-fs-data.d中的脚本以 post-fs-data 模式运行,在service.d中的脚本以 late_start 服务模式运行,以此类推。模块不应在安装过程中添加通用脚本。
模块脚本
- 放置在模块自己的文件夹中。
- 只有当模块被启用时才会执行。
post-fs-data.sh以 post-fs-data 模式运行,post-mount.sh以 post-mount 模式运行,service.sh以 late_start 服务模式运行,boot-completed.sh在 Android 系统启动完毕后以服务模式运行。
所有启动脚本都将在 FolkPatch 的 BusyBox ash shell 中运行,并启用"独立模式"。
模块开发示例
以下是一个完整的模块开发示例,展示各核心文件的编写规范。代码树左侧为文件列表,点击可查看对应文件内容。
module.prop
system
etc
hosts
bin
demo-tool
vendor
etc
demo-vendor.conf
product
etc
sysconfig
demo-feature.xml
system_ext
etc
sysconfig
demo-ext.xml
customize.sh
service.sh
post-fs-data.sh
system.prop
sepolicy.rule
id=demo_module
name=Demo Module
version=1.0.0
versionCode=100
author=demo_developer
description=A demo module demonstrating FolkPatch module development# Replaces /system/etc/hosts via mount overlay
127.0.0.1 localhost
::1 ip6-localhost
# Ad-blocking entries
127.0.0.1 ad.example.com
127.0.0.1 tracker.example.com
127.0.0.1 ads.example.net#!/system/bin/sh
# Added to /system/bin — available system-wide after mount
case "$1" in
status)
echo "Demo Module: $(getprop persist.demo.module.active)"
;;
version)
echo "Demo Module v1.0.0"
;;
*)
echo "Usage: demo-tool {status|version}"
;;
esac# Mounted to /vendor/etc/demo-vendor.conf
# vendor → MODID/system/vendor (auto-generated symlink)
demo.vendor.feature=enabled
demo.vendor.mode=performance<?xml version="1.0" encoding="utf-8"?>
<!-- Added to /product/etc/sysconfig/ -->
<!-- product → MODID/system/product (auto-generated symlink) -->
<permissions>
<feature name="com.demo.feature" />
</permissions><?xml version="1.0" encoding="utf-8"?>
<!-- Added to /system_ext/etc/sysconfig/ -->
<!-- system_ext → MODID/system/system_ext (auto-generated symlink) -->
<permissions>
<feature name="com.demo.ext.feature" />
</permissions># ============================================
# Demo Module - Installation Script
# Runs in BusyBox ash standalone mode
# ============================================
# Verify FolkPatch/APatch environment
if [ "$APATCH" != true ]; then
abort "This module requires FolkPatch or APatch"
fi
# Device compatibility checks
if [ "$API" -lt 26 ]; then
abort "Android 8.0 (API 26) or above is required (current: $API)"
fi
ui_print "========================"
ui_print " Demo Module v1.0.0"
ui_print "========================"
ui_print "Arch: $ARCH"
ui_print "API: $API"
ui_print "ModPath: $MODPATH"
# Set permissions for system overlay files
if [ -d "$MODPATH/system" ]; then
set_perm_recursive "$MODPATH/system" 0 0 0755 0644
# Executables in /system/bin need execute permission
if [ -d "$MODPATH/system/bin" ]; then
set_perm_recursive "$MODPATH/system/bin" 0 2000 0755 0755 \
u:object_r:system_file:s0
fi
fi
# To fully control installation, uncomment the following:
# SKIPUNZIP=1
# ...then handle extraction and permissions manually
ui_print "Installation complete!"# ============================================
# Demo Module - Late Start Service
# Runs in parallel with boot process (non-blocking)
# Recommended for most startup tasks
# ============================================
MODDIR=${0%/*}
# Wait for system services to stabilize
sleep 60
# Execute post-boot tasks in background
if [ -f "$MODDIR/scripts/post-boot.sh" ]; then
sh "$MODDIR/scripts/post-boot.sh" &
fi# ============================================
# Demo Module - Pre-mount Script
# WARNING: This stage is BLOCKING (max 10s)!
# NEVER use setprop here - it causes deadlock.
# Use resetprop -n instead.
# Only use this when absolutely necessary.
# ============================================
MODDIR=${0%/*}
# Safely set properties before modules are mounted
resetprop -n persist.demo.module.active 1# Demo Module Properties
persist.demo.module.active=1
persist.demo.module.version=1.0.0# SELinux policy rules (processed by magiskpolicy)
# Format: allow <source> <target> <class> <permission>
allow demo_app demo_data_file file { read open getattr }版权所有
版权归属:APatch Document
本文转载自:https://apatch.dev/zh_CN/apm-guide.html(在新窗口打开)
许可证:Attribution-ShareAlike 4.0 International
