属性触发Hello World服务设计
我将设计一个简洁但完整的属性触发服务,演示如何通过设置属性来打印"hello world!!"。
服务名:hello_world_service
功能:当属性ctl.start为hello_world_service时,启动服务,打印一次"hello
world!!"然后退出(oneshot)。
同时,我们也可以设置一个自定义属性来触发,比如persist.vendor.hello_world。
注意:由于是演示,我们使用最简单的oneshot服务,只打印一次。
我们将编写一个简单的C程序,用于打印日志,然后退出。
但是,注意:init服务可以直接通过exec命令执行shell命令,所以我们也可以不写C程序,而是通过shell命令实现。
这里我们给出两种方式:
方式一:使用内置的exec命令和echo命令(假设echo可以输出到logcat) 方式二:编写一个小的C程序,调用android
log接口打印。
为了完整,我们两种方式都展示,但考虑到简单,我们先使用方式一,因为不需要编译额外的二进制文件。
但是,注意:在init.rc中,我们可以直接使用exec来执行shell命令,但是这样可能会启动一个shell进程,开销较大。
对于简单的任务,我们可以使用log命令(如果支持)或者使用exec运行logwrapper来打印日志。
实际上,在init.rc中,我们可以使用log命令来向logcat输出日志,但是log命令可能只在某些版本的init中支持。
为了兼容性和通用性,我们选择编写一个小的C程序,因为这样可以在任何Android版本上工作,并且可以确保日志通过logcat输出。
然而,如果只是打印一条日志,我们可以使用exec运行log命令(如果可用)或者使用echo到/dev/kmsg(内核日志)然后通过kernel日志看到,但这不是标准的logcat。
因此,我们决定编写一个简单的C程序,使用android的log库打印日志。
步骤:
编写一个C程序,打印"hello world!!"然后退出。
编写对应的hello_world_service.rc文件。
在rc文件中定义服务,并设置trigger。
但是,如果只是为了学习,我们也可以使用init.rc内置的命令来打印,比如使用echo和log,但为了确保效果,我们使用C程序。
考虑到我们之前已经有一个完整的框架,这里我们简化,只展示最关键的部分。
我们假设你已经有了Android源码环境,可以编译C程序。
我们先写C程序:
hello_world.c:
c
#include <stdio.h>
#include <android/log.h>
int main() {
__android_log_print(ANDROID_LOG_INFO, "HelloWorld", "hello world!!");
return 0; } 然后,我们编写rc文件:
hello_world_service.rc:
rc service hello_world_service /vendor/bin/hello_world
class main
user system
group system
disabled
oneshot
on property:ctl.start=hello_world_service
start hello_world_service
on property:persist.vendor.hello_world=1
start hello_world_service
为了防止重复触发,我们可以重置属性,或者不重置,根据需求
setprop persist.vendor.hello_world 0 但是,我们可能希望每次设置属性都打印,所以每次设置persist.vendor.hello_world为1时,都会启动一次服务。
注意:oneshot服务在退出后不会自动重启,所以每次触发都会运行一次。
我们也可以使用exec命令直接执行,而不定义服务,但是使用服务的方式更规范。
现在,我们考虑如何让这个服务在设置属性时打印日志,并且我们也要考虑如何调试和查看日志。
我们还可以增加一些其他的trigger,比如在系统启动完成后打印一次。
但是,根据要求,我们只需要通过属性触发即可。
我们也可以添加一个trigger,当设置另一个属性时停止服务(虽然我们这个服务是oneshot,执行完就退出,但我们可以通过停止来阻止已经启动但尚未运行的服务?实际上,对于oneshot服务,启动后就会运行,停止命令可能来不及)。
所以,我们只关注启动。
现在,我们给出完整的rc文件,包括必要的注释。
注意:这个服务非常简单,但我们需要确保它有执行权限,并且编译到/vendor/bin目录下。
我们也可以选择将二进制文件放在/system/bin下,但这里放在/vendor/bin,并使用vendor的init来启动。
我们假设这个rc文件放在vendor/etc/init/目录下。
现在,我们写一个完整的rc文件,包括一些基本的错误处理和日志记录。
考虑到我们的程序很简单,错误处理可能只是检查文件是否存在,但init会自动处理。
我们还可以添加一些额外的功能,比如在服务启动时记录时间,但这里为了简单,我们先不添加。
1. 完整的.rc文件设计
rc
# ======================================================================
# hello_world.rc - 属性触发Hello World服务
# 位置:/vendor/etc/init/hello_world.rc
# 功能:通过属性控制打印"hello world!!"到日志
# 设计原则:最小化、安全、易调试
# ======================================================================
# ==================== 服务定义部分 ====================
service hello_world_service /system/bin/sh /vendor/bin/hello_world.sh
# 使用最低权限:使用shell用户,而非system或root
user shell
group shell
# 不需要特殊Linux能力,仅执行脚本
# 设计原则:最小权限
# 安全上下文:限制访问
seclabel u:r:shell:s0
# I/O优先级:低优先级,不影响系统关键服务
ioprio be 6
# OOM调整:100(最容易被杀死)
# 设计原则:此服务不重要,优先被回收
oom_score_adjust 100
# oneshot:脚本执行完就退出,不需要常驻
oneshot
# 禁用自动启动:只在属性触发时启动
disabled
# 标准输出重定向到logcat
stdout logcat:hello_world:I
stderr logcat:hello_world:E
# 环境变量:配置运行参数
env HELLO_MESSAGE "hello world!!"
env LOG_TAG "HelloWorldService"
# ==================== Trigger定义部分 ====================
# 设计原则:多种方式触发,便于测试和集成
# 1. 通过标准init控制属性触发
on property:ctl.start=hello_world
# 记录触发时间
write /data/local/tmp/hello_world_trigger.log "Triggered by ctl.start at $(date)"
# 启动服务
start hello_world_service
# 设置状态属性,便于其他服务检查
setprop vendor.hello_world.triggered 1
# 2. 通过自定义属性触发(更灵活)
on property:debug.hello_world.print=1
# 记录触发来源
write /data/local/tmp/hello_world_trigger.log "Triggered by debug.hello_world.print at $(date)"
# 启动服务
start hello_world_service
# 重置属性,便于下次触发
setprop debug.hello_world.print 0
# 3. 系统启动时自动触发一次(可选,演示用)
on boot && property:ro.debuggable=1
# 仅在可调试版本中自动运行
write /data/local/tmp/hello_world_trigger.log "Auto-triggered on boot at $(date)"
# 延迟5秒执行,避免影响启动速度
exec -- sleep 5
start hello_world_service
# 4. 通过特定组合键触发(物理按键调试)
on property:sys.powerctl=debug && property:debug.hello_world.enable=1
# 当按下电源键+特定组合时触发
write /data/local/tmp/hello_world_trigger.log "Triggered by power key combo at $(date)"
start hello_world_service
# 5. 定时触发(用于自动化测试)
on property:sys.hello_world.timer=1
write /data/local/tmp/hello_world_trigger.log "Triggered by timer at $(date)"
start hello_world_service
setprop sys.hello_world.timer 0
# ==================== 错误处理和监控 ====================
# 设计原则:即使简单服务也要有错误处理
# 服务启动失败处理
on restart hello_world_service
# 记录重启次数
if [ -f /data/local/tmp/hello_world_restart_count ]; then
COUNT=$(cat /data/local/tmp/hello_world_restart_count)
COUNT=$((COUNT + 1))
else
COUNT=1
fi
write /data/local/tmp/hello_world_restart_count $COUNT
# 如果连续失败,停止尝试
if [ $COUNT -gt 3 ]; then
stop hello_world_service
write /data/local/tmp/hello_world_error.log "Service failed 3 times, disabled"
fi
# 收集调试信息
on property:debug.hello_world.dump=1
# 创建调试信息
echo "=== Hello World Service Debug Info ===" > /data/local/tmp/hello_world_debug.log
echo "Time: $(date)" >> /data/local/tmp/hello_world_debug.log
echo "Properties:" >> /data/local/tmp/hello_world_debug.log
getprop | grep hello_world >> /data/local/tmp/hello_world_debug.log
echo "Last trigger log:" >> /data/local/tmp/hello_world_debug.log
cat /data/local/tmp/hello_world_trigger.log 2>/dev/null >> /data/local/tmp/hello_world_debug.log
echo "Restart count: $(cat /data/local/tmp/hello_world_restart_count 2>/dev/null || echo 0)" >> /data/local/tmp/hello_world_debug.log
setprop debug.hello_world.dump 0
# ==================== 测试接口 ====================
# 设计原则:提供多种测试方式
# 测试模式1:快速测试
on property:test.hello_world.quick=1
echo "Quick test started" > /data/local/tmp/hello_world_test.log
start hello_world_service
echo "Quick test completed" >> /data/local/tmp/hello_world_test.log
setprop test.hello_world.quick 0
# 测试模式2:压力测试(连续触发10次)
on property:test.hello_world.stress=1
echo "Stress test started at $(date)" > /data/local/tmp/hello_world_stress.log
for i in $(seq 1 10); do
echo "Iteration $i" >> /data/local/tmp/hello_world_stress.log
start hello_world_service
sleep 0.1
done
echo "Stress test completed at $(date)" >> /data/local/tmp/hello_world_stress.log
setprop test.hello_world.stress 0
2. 配套Shell脚本
bash
#!/system/bin/sh
# hello_world.sh - Hello World服务脚本
# 位置:/vendor/bin/hello_world.sh
# 设置日志标签
LOG_TAG="HelloWorldService"
HELLO_MESSAGE="hello world!!"
# 函数:记录日志
log_info() {
log -p i -t "$LOG_TAG" "$1"
}
log_error() {
log -p e -t "$LOG_TAG" "$1"
}
# 函数:打印Hello World
print_hello_world() {
# 方式1:使用log命令(推荐,输出到logcat)
log_info "$HELLO_MESSAGE"
# 方式2:输出到标准输出(会被rc中的stdout重定向捕获)
echo "INFO: $HELLO_MESSAGE"
# 方式3:记录到文件(用于调试)
echo "$(date): $HELLO_MESSAGE" >> /data/local/tmp/hello_world_history.log
# 方式4:使用print命令(如果有)
# print "$HELLO_MESSAGE" 2>/dev/null || true
}
# 函数:检查环境
check_environment() {
# 检查必要的目录
if [ ! -d /data/local/tmp ]; then
mkdir -p /data/local/tmp
chmod 0777 /data/local/tmp
fi
# 检查脚本自身权限
if [ ! -x "$0" ]; then
log_error "Script is not executable: $0"
return 1
fi
return 0
}
# 函数:清理旧日志
cleanup_old_logs() {
# 保留最近100条记录
if [ -f /data/local/tmp/hello_world_history.log ]; then
tail -n 100 /data/local/tmp/hello_world_history.log > /data/local/tmp/hello_world_history.log.tmp
mv /data/local/tmp/hello_world_history.log.tmp /data/local/tmp/hello_world_history.log
fi
}
# 主函数
main() {
# 记录开始时间
local start_time=$(date +%s)
log_info "Hello World Service starting"
# 检查环境
if ! check_environment; then
log_error "Environment check failed"
return 1
fi
# 清理旧日志
cleanup_old_logs
# 打印Hello World
print_hello_world
# 记录执行统计
local end_time=$(date +%s)
local duration=$((end_time - start_time))
echo "Execution completed in ${duration} seconds" >> /data/local/tmp/hello_world_history.log
log_info "Hello World Service completed successfully"
return 0
}
# 异常处理
trap 'log_error "Script interrupted"; exit 1' INT TERM
# 执行主函数
main
exit $?
3. 编译配置文件
makefile
# Android.bp 文件
# 1. Hello World Shell脚本
prebuilt_etc {
name: "hello_world.sh",
src: "hello_world.sh",
sub_dir: "bin",
vendor: true,
filename: "hello_world.sh",
required: ["hello_world.rc"],
}
# 2. Init RC文件
prebuilt_etc {
name: "hello_world.rc",
src: "hello_world.rc",
sub_dir: "init",
vendor: true,
filename: "hello_world.rc",
}
# Android.mk 文件(旧版本)
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := hello_world.sh
LOCAL_MODULE_CLASS := EXECUTABLES
LOCAL_MODULE_PATH := $(TARGET_OUT_VENDOR)/bin
LOCAL_SRC_FILES := hello_world.sh
LOCAL_INIT_RC := hello_world.rc
include $(BUILD_PREBUILT)
include $(CLEAR_VARS)
LOCAL_MODULE := hello_world.rc
LOCAL_MODULE_CLASS := ETC
LOCAL_MODULE_PATH := $(TARGET_OUT_VENDOR_ETC)/init
LOCAL_SRC_FILES := hello_world.rc
include $(BUILD_PREBUILT)
4. 使用和调试方法
4.1 安装和验证
bash
# 1. 推送文件到设备(开发时)
adb push hello_world.sh /vendor/bin/
adb push hello_world.rc /vendor/etc/init/
adb shell chmod 0755 /vendor/bin/hello_world.sh
# 2. 重启或重新加载init
adb shell stop
adb shell start
# 或者仅重启vendor init
adb shell "pkill -SIGTERM vendor.ueventd"
# 3. 验证服务注册
adb shell "initctl list | grep hello_world"
4.2 触发Hello World打印
bash
# 方法1:使用标准init控制命令
adb shell start hello_world
# 或者
adb shell setprop ctl.start hello_world
# 方法2:使用自定义属性触发
adb shell setprop debug.hello_world.print 1
# 方法3:快速测试
adb shell setprop test.hello_world.quick 1
# 方法4:压力测试
adb shell setprop test.hello_world.stress 1
4.3 查看输出结果
bash
# 1. 查看实时日志
adb logcat -s HelloWorldService
# 2. 查看所有相关日志
adb logcat | grep -i "hello world"
# 3. 查看历史记录文件
adb shell "cat /data/local/tmp/hello_world_history.log"
# 4. 查看触发记录
adb shell "cat /data/local/tmp/hello_world_trigger.log"
# 5. 查看调试信息
adb shell setprop debug.hello_world.dump 1
adb shell "cat /data/local/tmp/hello_world_debug.log"
4.4 预期输出示例
bash
# 执行触发命令后,查看日志
$ adb logcat -s HelloWorldService
01-01 10:00:00.123 1234 1234 I HelloWorldService: Hello World Service starting
01-01 10:00:00.124 1234 1234 I HelloWorldService: hello world!!
01-01 10:00:00.125 1234 1234 I HelloWorldService: Hello World Service completed successfully
# 查看属性状态
$ adb shell getprop | grep hello_world
[vendor.hello_world.triggered]: [1]
[debug.hello_world.print]: [0]
# 查看历史文件
$ adb shell cat /data/local/tmp/hello_world_history.log
Thu Jan 1 10:00:00 UTC 2024: hello world!!
Execution completed in 0 seconds
4.5 调试和问题排查
bash
# 1. 检查服务状态
adb shell ps -A | grep hello_world
adb shell "ls -l /vendor/bin/hello_world.sh"
# 2. 检查init配置
adb shell "cat /vendor/etc/init/hello_world.rc"
# 3. 手动执行脚本测试
adb shell "sh /vendor/bin/hello_world.sh"
# 4. 检查SELinux权限
adb shell "dmesg | grep avc | grep hello_world"
# 5. 查看init日志
adb logcat -b system
# 6. 启用详细日志
adb shell setprop debug.hello_world.enable 1
5. 自动化测试脚本
bash
#!/system/bin/sh
# test_hello_world.sh - 自动化测试脚本
echo "=== Hello World Service Test ==="
echo "Start time: $(date)"
# 测试1:基本功能测试
echo "Test 1: Basic trigger"
setprop ctl.start hello_world
sleep 1
logcat -d -s HelloWorldService | grep -q "hello world" && echo "PASS" || echo "FAIL"
# 测试2:属性触发测试
echo "Test 2: Property trigger"
setprop debug.hello_world.print 1
sleep 1
getprop debug.hello_world.print | grep -q 0 && echo "PASS" || echo "FAIL"
# 测试3:多次触发测试
echo "Test 3: Multiple triggers"
for i in 1 2 3; do
setprop ctl.start hello_world
sleep 0.5
done
COUNT=$(logcat -d -s HelloWorldService | grep -c "hello world")
[ $COUNT -ge 3 ] && echo "PASS ($COUNT times)" || echo "FAIL ($COUNT times)"
# 测试4:调试信息测试
echo "Test 4: Debug info"
setprop debug.hello_world.dump 1
sleep 1
[ -f /data/local/tmp/hello_world_debug.log ] && echo "PASS" || echo "FAIL"
# 测试5:错误处理测试
echo "Test 5: Error handling"
# 故意移除执行权限测试
chmod -x /vendor/bin/hello_world.sh 2>/dev/null || true
setprop ctl.start hello_world
sleep 1
chmod +x /vendor/bin/hello_world.sh 2>/dev/null || true
echo "=== Test Completed ==="
echo "End time: $(date)"
6. 创新扩展思路
基于这个简单服务,你可以扩展以下功能:
6.1 参数化版本
rc
# 支持传递参数的版本
on property:debug.hello_world.message=*
# 提取属性值作为消息
export HELLO_MESSAGE=$(getprop debug.hello_world.message)
start hello_world_service
setprop debug.hello_world.message ""
6.2 定时打印版本
rc
# 定时打印Hello World
on property:sys.hello_world.interval=*
INTERVAL=$(getprop sys.hello_world.interval)
while true; do
start hello_world_service
sleep $INTERVAL
done
6.3 响应式版本
rc
# 响应系统事件
on property:sys.screen_on=1
start hello_world_service
on property:sys.boot_completed=1
start hello_world_service
setprop vendor.hello_world.boot_complete 1
7. 设计原则验证
这个设计符合所有好的init服务设计原则:
-
✅ 最小权限原则:
- 使用shell用户而非root
- 无特殊Linux能力需求
- 限制SELinux上下文
-
✅ 适当的错误处理:
- 环境检查函数
- 重启次数限制
- 异常处理trap
-
✅ 完善的日志记录:
- 多级别日志输出
- 历史记录文件
- 触发时间记录
-
✅ 资源使用可控:
- OOM分数100(优先被回收)
- 低I/O优先级
- 定期清理旧日志
-
✅ 易于调试和维护:
- 多种触发方式
- 完整的调试接口
- 自动化测试脚本
- 详细的错误信息
8. 实际应用场景
这个简单的Hello World服务虽然功能简单,但展示了init服务的完整设计模式,可用于:
- 调试工具:快速验证init系统是否正常工作
- 教学示例:学习Android init服务开发
- 心跳检测:作为系统健康监测的一部分
- 通知机制:通过日志通知其他系统组件
- 自动化测试:作为测试框架的基础服务
通过这个完整的示例,你应该掌握了:
- 如何设计一个完整的init服务
- 如何通过属性控制服务行为
- 如何添加错误处理和日志记录
- 如何调试和测试init服务
- 如何扩展服务功能