安卓rc-属性触发Hello World服务设计

属性触发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服务设计原则:

  1. ✅ 最小权限原则:

    • 使用shell用户而非root
    • 无特殊Linux能力需求
    • 限制SELinux上下文
  2. ✅ 适当的错误处理:

    • 环境检查函数
    • 重启次数限制
    • 异常处理trap
  3. ✅ 完善的日志记录:

    • 多级别日志输出
    • 历史记录文件
    • 触发时间记录
  4. ✅ 资源使用可控:

    • OOM分数100(优先被回收)
    • 低I/O优先级
    • 定期清理旧日志
  5. ✅ 易于调试和维护:

    • 多种触发方式
    • 完整的调试接口
    • 自动化测试脚本
    • 详细的错误信息

8. 实际应用场景

这个简单的Hello World服务虽然功能简单,但展示了init服务的完整设计模式,可用于:

  1. 调试工具:快速验证init系统是否正常工作
  2. 教学示例:学习Android init服务开发
  3. 心跳检测:作为系统健康监测的一部分
  4. 通知机制:通过日志通知其他系统组件
  5. 自动化测试:作为测试框架的基础服务

通过这个完整的示例,你应该掌握了:

  1. 如何设计一个完整的init服务
  2. 如何通过属性控制服务行为
  3. 如何添加错误处理和日志记录
  4. 如何调试和测试init服务
  5. 如何扩展服务功能
相关推荐
火柴就是我6 小时前
Flutter 项目本地 Maven 引入 AAR 找不到?一次 Gradle 路径问题排查
android
火柴就是我6 小时前
Android 打包报错 25.0.3
android·前端
码上有光7 小时前
Linux进程通信——共享内存、消息队列和信号量
android·linux·运维·共享内存·通信
Android打工仔11 小时前
Kotlin 协程源码解析:DispatchedContinuation 里的 Dispatcher 从哪里来?
android·kotlin
打工仔折腾 AI11 小时前
从BPE到SentencePiece:Transformer分词原理与Python实战对比
android·人工智能·python·深度学习·langchain·transformer·ai agent 实战
Dovis(誓平步青云)12 小时前
几个方案来回选不定?做一个随时切换的候选推荐页
android·java·服务器·开发语言·javascript·数据库·智能化
恋猫de小郭15 小时前
KMP 又改了编译流程, Separate Compilation 禁止了 `commonMain` 的依赖穿透
android·前端·flutter
执明wa16 小时前
Android RecyclerView 实战:点击频道栏切换对应内容
android·开发语言·windows·microsoft·android studio
新鲜势力呀16 小时前
PHP 定时数据同步实战:从第三方接口超时到异步同步 + 增量更新架构优化全过程
android
星间都市山脉18 小时前
RecoveryUI 圆角屏安全边距配置及代码调用链
android·java·linux·windows·ubuntu