Android 开发必读:5 个优化 Companion Shell 代码的最佳实践
——
Android 开发必读:5 个优化 Companion Shell 代码的最佳实践
在 Android AI Agent 开发中,Companion Shell 作为连接系统底层与上层应用的关键桥梁,其代码质量直接影响项目的可维护性。本文基于 OpenClaw 最新提交的 distill companion shell cleanup 重构实践,为你拆解 5 个经过验证的优化策略,帮助团队减少技术债务、提升代码可读性。
—
为什么需要清理 Companion Shell?
Companion Shell 是 OpenClaw 框架中负责执行系统级命令、管理进程生命周期的核心组件。随着功能迭代,这类代码往往会出现以下问题:
- 职责混杂:单个脚本同时处理网络请求、文件操作、权限管理
- 重复逻辑:相似功能的代码片段分散在多个文件
- 测试困难:紧耦合的代码难以单元测试
- 文档缺失:复杂 shell 脚本缺乏清晰的注释和结构
本次重构的核心目标是通过 distill(提炼) 模式,将混杂的逻辑分离为单一职责的模块。
—
实践一:单一职责原则(SRP)重构
重构前的问题代码
#!/bin/bash
反模式:一个脚本处理过多任务
companion_shell.sh
function start_agent() {
# 检查网络状态
ping -c 1 google.com > /dev/null || exit 1
# 清理旧日志
rm -rf /data/openclaw/logs/*
# 启动服务
am startservice -n com.openclaw/.AgentService
# 发送启动通知
curl -X POST https://api.openclaw.com/notify \
-d '{"event":"agent_started"}'
}
重构后的模块化结构
#!/bin/bash
companion_shell.sh - 仅保留服务生命周期管理
source "$(dirname "$0")/lib/network_utils.sh"
source "$(dirname "$0")/lib/log_manager.sh"
source "$(dirname "$0")/lib/notifier.sh"
function start_agent() {
# 每个职责委托给专用模块
check_network_or_fail "google.com"
cleanup_logs "/data/openclaw/logs"
am startservice -n com.openclaw/.AgentService
notify_api "agent_started"
}
关键改进:通过 source 引入工具库,主脚本只负责流程编排。
—
实践二:引入 Shell 脚本测试框架
使用 Bats 进行单元测试
#!/usr/bin/env bats
test/network_utils.bats
@test "check_network_or_fail succeeds with reachable host" {
run check_network_or_fail "127.0.0.1"
[ "$status" -eq 0 ]
}
@test "check_network_or_fail fails with unreachable host" {
run check_network_or_fail "256.256.256.256"
[ "$status" -eq 1 ]
[[ "$output" == "Network unreachable" ]]
}
CI 集成配置
.github/workflows/shell-test.yml
name: Shell Script Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Bats
uses: bats-core/bats-action@1.5.4
- name: Run tests
run: bats test/
—
实践三:标准化错误处理与日志
统一的错误处理模式
#!/bin/bash
lib/error_handler.sh
set -euo pipefail # 严格模式:遇错即停、未定义变量报错、管道错误捕获
readonly LOG_LEVEL_ERROR=0
readonly LOG_LEVEL_WARN=1
readonly LOG_LEVEL_INFO=2
readonly LOG_LEVEL_DEBUG=3
LOG_LEVEL=${LOG_LEVEL:-$LOG_LEVEL_INFO}
log() {
local level=$1
shift
local message="$*"
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
# 只有满足级别要求的日志才输出
[[ $level -le $LOG_LEVEL ]] || return 0
echo "[$timestamp] [${level_names[$level]}] $message" >&2
}
便捷函数
error() { log $LOG_LEVEL_ERROR "$@"; }
warn() { log $LOG_LEVEL_WARN "$@"; }
info() { log $LOG_LEVEL_INFO "$@"; }
debug() { log $LOG_LEVEL_DEBUG "$@"; }
带上下文的错误退出
fail() {
error "$@"
exit 1
}
使用示例
#!/bin/bash
source "$(dirname "$0")/lib/error_handler.sh"
start_agent() {
info "Starting OpenClaw Agent..."
local pid_file="/var/run/openclaw.pid"
if [[ -f "$pid_file" ]]; then
local old_pid=$(cat "$pid_file")
if kill -0 "$old_pid" 2>/dev/null; then
warn "Agent already running (PID: $old_pid)"
return 0
fi
rm -f "$pid_file"
fi
# 启动服务,失败时自动记录错误
am startservice -n com.openclaw/.AgentService \
|| fail "Failed to start service, exit code: $?"
echo $! > "$pid_file"
info "Agent started successfully"
}
—
实践四:Android 特定优化
ADB 命令的安全封装
#!/bin/bash
lib/adb_wrapper.sh
检测 ADB 可用性并设置超时
adb_safe() {
local timeout=${ADB_TIMEOUT:-30}
local device=${ANDROID_SERIAL:-}
if [[ -n "$device" ]]; then
adb -s "$device" "$@" 2>/dev/null
else
timeout "$timeout" adb "$@" 2>/dev/null
fi
}
带重试机制的 shell 命令
adb_shell_retry() {
local max_attempts=${1:-3}
shift
local delay=${1:-2}
shift
for ((i=1; i<=max_attempts; i++)); do
if adb_safe shell "$@"; then
return 0
fi
warn "ADB attempt $i/$max_attempts failed, retrying in ${delay}s..."
sleep "$delay"
done
fail "ADB command failed after $max_attempts attempts: $*"
}
获取 OpenClaw 服务状态
get_agent_status() {
adb_shell_retry 3 1 \
"dumpsys activity services com.openclaw/.AgentService | grep -E 'pid|running'"
}
—
实践五:文档与代码自解释
文件头标准模板
#!/bin/bash
#===============================================================================
#
FILE: agent_controller.sh
#
USAGE: ./agent_controller.sh {start|stop|restart|status}
#
DESCRIPTION: OpenClaw Android Agent 生命周期管理控制器
负责服务的启动、停止、状态查询及异常恢复
#
OPTIONS: 参见 USAGE
REQUIREMENTS: Android SDK, ADB 工具, root 或 shell 权限
NOTES: 需要设置 ANDROID_SERIAL 环境变量指定目标设备
AUTHOR: OpenClaw Team
VERSION: 2.1.0
CREATED: 2024-01-15
REVISION: 2024-06-20 - 重构为 distill 模式
#===============================================================================
函数文档规范
#-------------------------------------------------------------------------------
函数: recover_agent
用途: 检测并恢复异常退出的 Agent 服务
参数:
$1 - 最大恢复尝试次数 (默认: 5)
$2 - 恢复间隔秒数 (默认: 10)
返回:
0 - 恢复成功
1 - 恢复失败,需人工介入
示例:
recover_agent 3 5 # 最多尝试3次,间隔5秒
#-------------------------------------------------------------------------------
recover_agent() {
local max_retries=${1:-5}
local interval=${2:-10}
# 实现...
}
—
重构效果验证
执行以下命令验证清理效果:
1. 统计代码行数变化
find . -name "*.sh" -exec wc -l {} + | tail -1
2. 检查循环依赖
grep -r "source.\.sh" --include=".sh" . | sort | uniq -c
3. 运行静态分析
shellcheck lib/*.sh companion_shell.sh
4. 执行测试套件
bats test/ --tap
—
FAQ
Q1: 什么是 “distill” 重构模式?
distill(提炼) 是一种代码重构策略,核心思想是将混杂的复杂代码蒸馏为纯净、单一职责的模块。与简单删除代码不同,它强调保留业务价值的同时消除噪声,使核心逻辑更加清晰可测。
Q2: Companion Shell 与普通 Shell 脚本有何区别?
Companion Shell 特指与 Android AI Agent 主进程协同工作的辅助脚本,通常运行在设备或调试主机上,负责:
- 监控 Agent 进程状态
- 执行需要 shell 权限的系统操作
- 桥接 Java/Kotlin 层与原生系统调用
Q3: 如何在现有项目中应用这些实践?
建议按以下优先级逐步实施:
1. 立即:添加 set -euo pipefail 和基础日志
2. 本周:提取重复代码到 lib/ 目录
3. 本月:引入 Bats 测试覆盖核心流程
4. 本季度:完善文档和 CI 集成
Q4: OpenClaw 的这次重构是否影响 API 兼容性?
本次 distill companion shell cleanup 为内部实现优化,对外部调用接口保持兼容。若你直接依赖了具体的 shell 函数名或文件路径,建议关注 OpenClaw 变更日志 获取迁移指南。
Q5: 是否有推荐的 Shell 代码规范工具?
推荐组合使用:
- ShellCheck:静态分析与最佳实践检查
- shfmt:自动格式化(类似 Go 的 gofmt)
- Bats:Bash 测试框架
—
总结与下一步
本文介绍的 5 个实践——单一职责重构、测试框架引入、标准化错误处理、Android 特定优化、文档规范化——构成了 OpenClaw distill companion shell cleanup 的核心方法论。这些策略不仅适用于 shell 脚本,也可迁移到 Python、Node.js 等脚本环境。
立即行动:
1. 审查你项目中的 shell 脚本,识别”万能脚本”反模式
2. 选择一个最复杂的脚本,应用本文的模块化重构
3. 在团队内建立 shell 代码审查清单
—
相关阅读
—