前两期回顾:
【超详细】手机搭建服务器 · 第 一 期
【超详细】手机搭建服务器 · 第 二 期
前言
第一期讲了必备条件;第二期讲了如何实现旁路供电,解决供电问题;
本期要实现的是:如何让手机的wifi永不断连:
- 不管是手机意外重启,还是重新开关机;
- 不管是开启了飞行模式;
- 不管是不是主动关闭wifi。
[!warning]建议
在开始之前,建议先把手机的这两个开关关闭,不是必须,但是建议,减少干扰:
- 系统设置 - 开发者选项 - WLAN扫描调节 - 关闭
- 系统设置 - WLAN - WLAN助理 - 智能选网和网速模式 - 关闭
补充说明:
· 机型不同,打开开发者选项的方式也不同,这个自己百度
· 不是所有手机都有【WLAN助理】这个选项,我是MIUI 11,所有有,其他手机可能是其他名字,或者没有这个功能,自己看自己系统的功能开关。主要是把能影响WIFI稳定的开关关闭,不管是信号调节还是省电方面的设置。
教程开始
1.新建开机自启脚本
1.1 打开MT管理器,进入/data/adb/service.d/文件夹,新建99-phone-server.sh文件
1.2 打开文件,在文件中输入以下代码并保存:
#!/system/bin/sh
# ============================================================
# Phone Server 启动器
# 放在 Magisk service.d 中
# 注意:
# 这个脚本不写其他的复杂业务逻辑,只负责启动真正的脚本。
# 以后 IPv6、DDNS、Debian、SSH 等脚本,也会这么做。
# ============================================================
WIFI_KEEPER="/data/local/phone-server/wifi-keeper/wifi-keeper.sh"
WIFI_LOG="/data/local/phone-server/wifi-keeper/wifi-keeper.log"
mkdir -p /data/local/phone-server/wifi-keeper
echo "$(date '+%Y-%m-%d %H:%M:%S') [INFO] [启动器] 99-phone-server.sh 开始执行" >> "$WIFI_LOG"
if [ -x "$WIFI_KEEPER" ]; then
nohup "$WIFI_KEEPER" >/dev/null 2>&1 &
echo "$(date '+%Y-%m-%d %H:%M:%S') [OK] [启动器] 已后台启动 Wi-Fi Keeper" >> "$WIFI_LOG"
else
echo "$(date '+%Y-%m-%d %H:%M:%S') [ERROR] [启动器] Wi-Fi Keeper 不存在或没有执行权限:$WIFI_KEEPER" >> "$WIFI_LOG"
fi
1.3给脚本设置权限


2.新建wifi核心脚本
2.1 打开MT管理器,进入/data/local/文件夹,新建phone-server文件夹,然后进入phone-server文件夹再新建wifi-keeper文件夹;
2.2 在/data/local/phone-server/wifi-keeper/文件夹下面新建wifi-keeper.sh文件,并且输入下面的代码进行保存:
#!/system/bin/sh
# ============================================================
# Wi-Fi Keeper
# ============================================================
# 功能:
# 1. 开机后自动检查 wifi 开关
# 2. 如果 wifi 关闭,自动打开 wifi
# 3. 等待系统自动连接“已保存”的 wifi
# 4. 如果系统没有自动连上,则按 WIFI_LIST 优先级选择已保存 wifi
# 5. wifi 断开后,自动重新连接
# 6. 输出详细日志,方便排查
#
# 适用环境:
# Android Root + Magisk service.d
#
# 重要说明:
# 1. 需要提前手动连接一次 wifi,让系统保存 wifi。
# 2. WIFI_LIST 里只写 Wi-Fi 名称,不用写密码、不用写验证方式。
# 3. 如果一个WI-FI没连接成功,系统会连接下一个wifi
# 4. 这是常驻守护脚本,不是执行一次就退出的脚本。
# ============================================================
# ============================================================
# 基础路径配置
# ============================================================
BASE_DIR="/data/local/phone-server/wifi-keeper"
LOG_FILE="$BASE_DIR/wifi-keeper.log"
# 临时锁目录,放在 /dev,重启后自动消失
# 作用:防止同一个 wifi-keeper 被重复启动
LOCK_DIR="/dev/phone-server-wifi-keeper.lock"
WIFI_IFACE="wlan0"
# ============================================================
# Wi-Fi 列表配置
# ============================================================
# 格式:
# 一行一个 Wi-Fi 名称。
#
# 顺序就是优先级:
# 第一行优先级最高,连接失败才尝试下一行。
# ============================================================
WIFI_LIST="
CMCC-Semmering
"
# ============================================================
# 行为参数
# ============================================================
# 开机后等待系统完全启动,最多等待多少秒,单位:秒
BOOT_WAIT_MAX=60
# 系统启动完成后,额外等待多少秒,单位:秒
# 有些 MIUI 设备 sys.boot_completed=1 后,Wi-Fi 服务还没完全准备好。
AFTER_BOOT_EXTRA_WAIT=10
# 打开 Wi-Fi 后,最多等待多久确认 Wi-Fi 进入可连接状态,单位:秒
ENABLE_WIFI_WAIT=10
# 打开 Wi-Fi 后,等待系统自动连接已保存 Wi-Fi 的时间,单位:秒
AUTO_CONNECT_WAIT=10
# 使用 wpa_cli 选择已保存 Wi-Fi 后,最多等待多久拿到 IP,单位:秒
SELECT_WIFI_WAIT=30
# 正常情况下,每隔多少秒检查一次 Wi-Fi,单位:秒
CHECK_INTERVAL=60
# 本轮修复失败后,等待多少秒再重试,单位:秒
FAIL_COOLDOWN=30
# 日志最大大小,单位:字节
# 1048576 字节 = 1MB
# 超过后只保留最后 300 行,避免日志无限增长
MAX_LOG_SIZE=1048576
# ============================================================
# 全局变量
# ============================================================
WPA_CLI_BIN=""
WPA_CTRL_DIR=""
WPA_LAST_OUT=""
# ============================================================
# 日志函数
# ============================================================
now() {
date '+%Y-%m-%d %H:%M:%S'
}
# 避免日志里输出 MAC 地址 / BSSID
mask_mac() {
echo "$1" | sed -E 's/([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}/[MAC已隐藏]/g'
}
# 取 WIFI_LIST 中第一个有效 wifi名称,用于“尚未解析到当前 SSID”时的日志提示。
# 这样日志不会把 MIUI 的 BSSID / state 状态误当成 Wi-Fi 名称。
get_first_wifi_name_from_list() {
printf '%s\n' "$WIFI_LIST" | while IFS= read -r line; do
[ -z "$line" ] && continue
case "$line" in
\#*) continue ;;
esac
echo "$line"
return 0
done
}
# 清洗从 dumpsys / wpa_cli 解析出来的 SSID。
clean_ssid_value() {
raw="$1"
ssid="$(echo "$raw" | sed 's/^ *//;s/ *$//')"
case "$ssid" in
""|"<unknown ssid>"|"unknown"|"null"|"0x"|"WifiSsid.NONE"|"none"|"<none>")
echo ""
return 0
;;
esac
echo "$ssid" | grep -Eiq '([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}|(^|[[:space:]])nid:|(^|[[:space:]])state:|bssid|scanning|associating|disconnected|completed' && {
echo ""
return 0
}
echo "$ssid"
}
rotate_log_if_needed() {
[ -f "$LOG_FILE" ] || return 0
size="$(wc -c < "$LOG_FILE" 2>/dev/null)"
[ -n "$size" ] || return 0
if [ "$size" -gt "$MAX_LOG_SIZE" ]; then
tail -n 300 "$LOG_FILE" > "$LOG_FILE.tmp" 2>/dev/null
mv "$LOG_FILE.tmp" "$LOG_FILE" 2>/dev/null
fi
}
log() {
level="$1"
action="$2"
msg="$(mask_mac "$3")"
mkdir -p "$BASE_DIR"
rotate_log_if_needed
echo "$(now) [$level] [$action] $msg" >> "$LOG_FILE"
}
log_info() {
log "INFO" "$1" "$2"
}
log_ok() {
log "OK" "$1" "$2"
}
log_error() {
log "ERROR" "$1" "$2"
}
# ============================================================
# 防止重复运行
# ============================================================
acquire_lock() {
if mkdir "$LOCK_DIR" 2>/dev/null; then
trap 'rm -rf "$LOCK_DIR"; log_info "退出脚本" "Wi-Fi Keeper 已退出,释放 lock"' EXIT
log_ok "启动脚本" "获取 lock 成功,当前实例开始运行"
return 0
fi
log_error "启动脚本" "检测到已有 Wi-Fi Keeper 正在运行,当前实例退出"
exit 0
}
# ============================================================
# 环境检查
# ============================================================
check_env() {
mkdir -p "$BASE_DIR"
if ! command -v svc >/dev/null 2>&1; then
log_error "检查环境" "系统不支持 svc 命令,无法打开 Wi-Fi"
exit 1
fi
if ! command -v ip >/dev/null 2>&1; then
log_error "检查环境" "系统不支持 ip 命令,无法检测 wlan0 地址"
exit 1
fi
log_ok "检查环境" "环境检查完成"
}
wait_boot_completed() {
log_info "等待系统启动" "开始等待 sys.boot_completed=1"
i=0
while [ "$i" -lt "$BOOT_WAIT_MAX" ]; do
boot_completed="$(getprop sys.boot_completed 2>/dev/null)"
if [ "$boot_completed" = "1" ]; then
log_ok "等待系统启动" "系统启动完成"
return 0
fi
sleep 2
i=$((i + 2))
done
log_error "等待系统启动" "等待系统启动完成超时,继续执行后续逻辑"
return 1
}
# ============================================================
# Wi-Fi 状态判断
# ============================================================
wifi_on_value() {
settings get global wifi_on 2>/dev/null
}
wifi_on_text() {
v="$(wifi_on_value)"
case "$v" in
0)
echo "关闭"
;;
1)
echo "已开启"
;;
2)
echo "正在开启"
;;
3)
echo "正在关闭"
;;
*)
echo "未知状态:$v"
;;
esac
}
has_wifi_ip() {
ip -4 addr show dev "$WIFI_IFACE" 2>/dev/null | grep -q "inet " && return 0
ip -6 addr show dev "$WIFI_IFACE" scope global 2>/dev/null | grep -q "inet6 " && return 0
return 1
}
wifi_iface_up() {
ip link show "$WIFI_IFACE" 2>/dev/null | grep -q "UP"
}
wifi_ready_for_connect() {
# 已经拿到 IP,说明 Wi-Fi 已连接
has_wifi_ip && return 0
# settings 显示已开启
[ "$(wifi_on_value)" = "1" ] && return 0
# wlan0 已经 UP,也说明可以等待系统自动连接或尝试选择已保存网络
wifi_iface_up && return 0
return 1
}
# ============================================================
# 当前 Wi-Fi 名称解析
# ============================================================
find_wpa_cli_bin_quiet() {
[ -n "$WPA_CLI_BIN" ] && [ -x "$WPA_CLI_BIN" ] && return 0
for b in \
"$(command -v wpa_cli 2>/dev/null)" \
"/system/bin/wpa_cli" \
"/vendor/bin/wpa_cli" \
"/system/xbin/wpa_cli" \
"/system_ext/bin/wpa_cli"
do
[ -z "$b" ] && continue
if [ -x "$b" ]; then
WPA_CLI_BIN="$b"
return 0
fi
done
return 1
}
wpa_cli_raw() {
if [ -z "$WPA_CTRL_DIR" ]; then
"$WPA_CLI_BIN" -i "$WIFI_IFACE" "$@" 2>&1
else
"$WPA_CLI_BIN" -p "$WPA_CTRL_DIR" -i "$WIFI_IFACE" "$@" 2>&1
fi
}
get_current_ssid_from_wpa() {
find_wpa_cli_bin_quiet || return 0
# 如果已经检测过控制目录,就直接用
if [ -n "$WPA_CTRL_DIR" ]; then
out="$(wpa_cli_raw status 2>/dev/null)"
ssid="$(echo "$out" | sed -n 's/^ssid=//p' | head -n 1)"
ssid="$(clean_ssid_value "$ssid")"
[ -n "$ssid" ] && echo "$ssid"
return 0
fi
# 尝试几个常见控制目录
for p in \
"" \
"/data/vendor/wifi/wpa/sockets" \
"/data/misc/wifi/sockets" \
"/dev/socket"
do
WPA_CTRL_DIR="$p"
out="$(wpa_cli_raw status 2>/dev/null)"
echo "$out" | grep -q "wpa_state" || continue
ssid="$(echo "$out" | sed -n 's/^ssid=//p' | head -n 1)"
ssid="$(clean_ssid_value "$ssid")"
if [ -n "$ssid" ]; then
echo "$ssid"
return 0
fi
done
WPA_CTRL_DIR=""
return 0
}
get_current_ssid_from_dumpsys() {
# 只提取 SSID,避免输出 BSSID/MAC。
line="$(dumpsys wifi 2>/dev/null | grep -m 1 "SSID:" 2>/dev/null)"
[ -z "$line" ] && return 0
ssid="$(echo "$line" | sed -n 's/.*SSID: *"\([^"]*\)".*/\1/p')"
if [ -z "$ssid" ]; then
ssid="$(echo "$line" | sed -n 's/.*SSID: *\([^,]*\).*/\1/p')"
fi
ssid="$(clean_ssid_value "$ssid")"
[ -n "$ssid" ] && echo "$ssid"
}
get_current_ssid() {
ssid="$(get_current_ssid_from_wpa)"
if [ -n "$ssid" ]; then
echo "$ssid"
return 0
fi
ssid="$(get_current_ssid_from_dumpsys)"
if [ -n "$ssid" ]; then
echo "$ssid"
return 0
fi
echo ""
}
# 获取日志里应该显示的 Wi-Fi 名称。
# 说明:
# MIUI 11 / Android 10 有时不允许从 wpa_cli / dumpsys 稳定解析当前 SSID。
# 如果已经拿到 Wi-Fi IP,但解析不到 SSID,并且 WIFI_LIST 里配置了目标 Wi-Fi,
# 就在日志中显示“目标 WiFi + 推断说明”,避免反复出现“未解析到当前WiFi名称”。
get_display_wifi_name() {
real_ssid="$(get_current_ssid)"
if [ -n "$real_ssid" ]; then
echo "$real_ssid"
return 0
fi
target_ssid="$(get_first_wifi_name_from_list)"
if has_wifi_ip && [ -n "$target_ssid" ]; then
echo "$target_ssid(根据WIFI_LIST推断)"
return 0
fi
if [ -n "$target_ssid" ]; then
echo "未连接;目标WiFi=$target_ssid"
else
echo "未连接"
fi
}
log_wifi_basic_status() {
wifi_value="$(wifi_on_value)"
wifi_text="$(wifi_on_text)"
ssid="$(get_display_wifi_name)"
ip_info="$(ip addr show dev "$WIFI_IFACE" 2>/dev/null | grep -E 'inet |inet6 ' | tr '\n' ' ')"
[ -z "$ip_info" ] && ip_info="未拿到IP"
log_info "WiFi状态" "WiFi开关=$wifi_value($wifi_text);当前WiFi=$ssid;IP=$ip_info"
}
enable_wifi() {
if wifi_ready_for_connect; then
log_ok "打开WiFi开关" "Wi-Fi 已经处于可用或可连接状态,无需重复打开"
log_wifi_basic_status
return 0
fi
log_info "打开WiFi开关" "检测到 Wi-Fi 关闭,开始打开 Wi-Fi"
out="$(svc wifi enable 2>&1)"
rc="$?"
if [ "$rc" -ne 0 ]; then
log_error "打开WiFi开关" "svc wifi enable 执行失败,rc=$rc,输出:$out"
return 1
fi
log_info "打开WiFi开关" "svc wifi enable 执行成功,开始等待 Wi-Fi 进入可连接状态"
i=0
while [ "$i" -lt "$ENABLE_WIFI_WAIT" ]; do
if wifi_ready_for_connect; then
log_ok "打开WiFi开关" "Wi-Fi 已进入可用或可连接状态"
log_wifi_basic_status
return 0
fi
if [ $((i % 10)) -eq 0 ]; then
log_info "打开WiFi开关" "等待 Wi-Fi 开启中,已等待 ${i} 秒"
log_wifi_basic_status
fi
sleep 2
i=$((i + 2))
done
log_error "打开WiFi开关" "已执行打开 Wi-Fi,但 ${ENABLE_WIFI_WAIT} 秒内没有确认进入可连接状态"
log_wifi_basic_status
return 1
}
wifi_connected_ok() {
if ! has_wifi_ip; then
return 1
fi
ssid="$(get_display_wifi_name)"
log_ok "检查WiFi状态" "Wi-Fi 已连接,当前WiFi=$ssid,已拿到 IP"
return 0
}
wait_wifi_connected() {
action="$1"
max_wait="$2"
i=0
while [ "$i" -lt "$max_wait" ]; do
if wifi_connected_ok; then
log_ok "$action" "连接判断成功"
return 0
fi
if [ $((i % 10)) -eq 0 ]; then
log_info "$action" "等待 Wi-Fi 获取 IP,已等待 ${i} 秒"
log_wifi_basic_status
fi
sleep 2
i=$((i + 2))
done
log_error "$action" "等待 ${max_wait} 秒后仍未拿到 Wi-Fi IP"
log_wifi_basic_status
return 1
}
wait_auto_connect() {
log_info "自动连接WIFI" "等待系统自动连接已保存 Wi-Fi,最长等待 ${AUTO_CONNECT_WAIT} 秒"
if wait_wifi_connected "自动连接WIFI" "$AUTO_CONNECT_WAIT"; then
return 0
fi
log_error "自动连接WIFI" "系统自动连接超时,准备按 Wi-Fi 名称选择已保存网络"
return 1
}
# ============================================================
# 使用 wpa_cli 选择已保存 wifi
# 注意:这里只选择已保存网络,不写入密码,不创建新网络
# ============================================================
find_wpa_cli_bin() {
if find_wpa_cli_bin_quiet; then
log_ok "查找wpa_cli" "找到 wpa_cli:$WPA_CLI_BIN"
return 0
fi
log_error "查找wpa_cli" "没有找到 wpa_cli,无法主动选择已保存 Wi-Fi,只能依赖系统自动连接"
return 1
}
wpa_exec() {
action="$1"
shift
WPA_LAST_OUT="$(wpa_cli_raw "$@")"
rc="$?"
safe_out="$(mask_mac "$WPA_LAST_OUT")"
log_info "$action" "执行:$WPA_CLI_BIN ${WPA_CTRL_DIR:+-p $WPA_CTRL_DIR} -i $WIFI_IFACE $*;rc=$rc;输出:$safe_out"
return "$rc"
}
find_wpa_cli_control() {
if ! find_wpa_cli_bin; then
return 1
fi
for p in \
"" \
"/data/vendor/wifi/wpa/sockets" \
"/data/misc/wifi/sockets" \
"/dev/socket"
do
WPA_CTRL_DIR="$p"
out="$(wpa_cli_raw status 2>&1)"
rc="$?"
safe_out="$(mask_mac "$out")"
log_info "检测wpa_cli" "尝试控制目录='${p:-默认}',rc=$rc,输出:$safe_out"
echo "$out" | grep -q "wpa_state" && {
log_ok "检测wpa_cli" "wpa_cli 可用,控制目录='${p:-默认}'"
return 0
}
done
WPA_CTRL_DIR=""
log_error "检测wpa_cli" "wpa_cli 存在,但无法连接到 Wi-Fi 控制接口,只能依赖系统自动连接"
return 1
}
find_saved_network_id_by_ssid() {
target_ssid="$1"
wpa_exec "查找已保存WIFI" list_networks
network_id="$(printf '%s\n' "$WPA_LAST_OUT" \
| awk -F '\t' -v target="$target_ssid" 'NR > 1 && $2 == target {print $1; exit}')"
if [ -z "$network_id" ]; then
log_error "查找已保存WIFI" "没有找到已保存的 Wi-Fi:$target_ssid。请先手动连接一次并保存"
return 1
fi
log_ok "查找已保存WIFI" "找到已保存 Wi-Fi:$target_ssid,network_id=$network_id"
echo "$network_id"
return 0
}
select_saved_wifi_by_ssid() {
ssid="$1"
log_info "选择WIFI" "准备选择已保存 Wi-Fi:$ssid"
if ! find_wpa_cli_control; then
return 1
fi
network_id="$(find_saved_network_id_by_ssid "$ssid")"
if [ -z "$network_id" ]; then
return 1
fi
wpa_exec "启用WIFI" enable_network "$network_id"
wpa_exec "选择WIFI" select_network "$network_id"
wpa_exec "重连WIFI" reconnect
if wait_wifi_connected "选择WIFI" "$SELECT_WIFI_WAIT"; then
current_ssid="$(get_display_wifi_name)"
log_ok "选择WIFI" "已连接 Wi-Fi:$current_ssid"
return 0
fi
log_error "选择WIFI" "已尝试选择 Wi-Fi:$ssid,但最终没有拿到 IP"
return 1
}
connect_by_priority() {
log_info "连接WIFI" "开始按 WIFI_LIST 顺序选择已保存 Wi-Fi"
tmp_list="/dev/phone-server-wifi-list.tmp"
printf '%s\n' "$WIFI_LIST" > "$tmp_list"
while IFS= read -r ssid; do
[ -z "$ssid" ] && continue
case "$ssid" in
\#*) continue ;;
esac
log_info "连接WIFI" "按优先级尝试选择 Wi-Fi:$ssid"
if select_saved_wifi_by_ssid "$ssid"; then
rm -f "$tmp_list" 2>/dev/null
return 0
fi
log_error "连接WIFI" "Wi-Fi=$ssid 连接失败,继续尝试下一个"
done < "$tmp_list"
rm -f "$tmp_list" 2>/dev/null
log_error "连接WIFI" "WIFI_LIST 中的已保存 Wi-Fi 都没有连接成功"
return 1
}
# ============================================================
# 主循环
# ============================================================
main_loop() {
log_info "启动脚本" "Wi-Fi Keeper 启动"
wait_boot_completed
if [ "$AFTER_BOOT_EXTRA_WAIT" -gt 0 ]; then
log_info "等待系统服务" "系统已启动,额外等待 ${AFTER_BOOT_EXTRA_WAIT} 秒,让 Wi-Fi 服务准备完成"
sleep "$AFTER_BOOT_EXTRA_WAIT"
fi
log_wifi_basic_status
enable_wifi
if wait_auto_connect; then
log_ok "启动连接" "系统自动连接 Wi-Fi 成功"
else
connect_by_priority
fi
while true; do
if wifi_connected_ok; then
sleep "$CHECK_INTERVAL"
continue
fi
log_error "守护检测" "检测到 Wi-Fi 不可用,开始自动修复"
enable_wifi
if wait_auto_connect; then
log_ok "守护检测" "系统自动恢复 Wi-Fi 成功"
else
if connect_by_priority; then
log_ok "守护检测" "按 Wi-Fi 名称选择已保存网络成功"
else
log_error "守护检测" "本轮 Wi-Fi 修复失败,${FAIL_COOLDOWN} 秒后重试"
sleep "$FAIL_COOLDOWN"
fi
fi
done
}
# ============================================================
# 程序入口
# ============================================================
acquire_lock
check_env
main_loop
2.3 按照1.3的过程给脚本设置权限。
[!question] 如何验证脚本是否成功
1. 手机先正常连接上你选择Wi-Fi,建议选择一个即可,避免手机因为某些原因在多个wifi之间来回切换,这个对后面的ipv6的解析影响非常大,然后关闭wifi的自动连接开关。
2. 正常连接上一个wifi以后,关闭wifi开关,把手机重启。
3.验证手机重启wifi是否能正常连接: 重启以后,等几秒钟(具体看配置你写的是几秒),然后看看wifi开关是否会自动打开,并且wifi是否会自动连接上。
4.验证手动断开wifi是否能正常连接: 如果wifi能自动连接上,可以手动关闭wifi开关,看看wifi是否能过几秒(具体还是要看配置你写的是几秒)后自动打开并且连接上wifi。
我用的脚本在米8SE上多轮测试没问题,如果你们有问题,记得联系我,我到时候拍查一下
[!question] 如何关闭脚本
方法一: 取消/data/adb/service.d/99-phone-server.sh脚本的执行权限
方法二: 删除/data/adb/service.d/99-phone-server.sh脚本
[!success]结束语
不得不说,AI是真的好用。不仅可以优化脚本,还能把注释写的清清楚楚。按照它的逻辑和日志,就算出问题了也能很好的定位和解决,这一次确实是省了不少时间。可惜两天撸的business 2个月翻车了。
另外,给佬友们送上一份可直接食用的prompt:
我现在正在把一台 Android 手机改造成 7×24 小时运行的家庭小服务器。
目前我已经完成了 ACC 充电控制,解决了长期插电和旁路供电相关的问题。下一步我要解决的是:手机与 Wi-Fi 之间的连接稳定性。
我的目标是:
手机开机后,或者 Wi-Fi 因为路由器重启、信号波动、系统休眠、网络异常等原因断开后,脚本能够自动恢复 Wi-Fi 连接,让手机尽可能长期稳定在线。
我的手机环境大概如下:
- Android 手机,已 Root;
- 使用 Magisk;
- 开机自启动脚本可以通过 Magisk 的 `/data/adb/service.d/` 实现;
- 手机会长期作为服务器运行;
- 后续还会继续做 IPv6 公网地址检测、DDNS 同步、Debian/SSH 服务守护等脚本;
- 所以我不希望所有业务脚本都直接堆在 `/data/adb/service.d/` 里。
---
# 我希望你帮我实现的事情
请你作为一名专业的 Android / Linux / Shell 脚本开发人员,帮我设计并编写一套 Wi-Fi 连接守护方案。
我希望它能实现这些能力:
## 1. 开机自动执行
手机开机后,Magisk 自动执行启动脚本。
但是我不希望把真正复杂的 Wi-Fi 守护逻辑直接写在 `/data/adb/service.d/` 里面。
我希望 `/data/adb/service.d/` 里只放一个“启动器脚本”,比如:
```text
/data/adb/service.d/99-phone-server.sh
```
这个启动器只负责启动真正的 Wi-Fi 守护脚本。
真正的 Wi-Fi 守护脚本和日志文件,请你帮我设计一个合理的目录,比如放在:
```text
/data/local/phone-server/wifi-keeper/
```
或者你认为更合理的目录也可以,但请你说明为什么这样设计。
---
## 2. 自动打开 Wi-Fi
脚本启动后,需要检查 Wi-Fi 开关状态。
如果 Wi-Fi 是关闭状态,就自动打开 Wi-Fi。
如果 Wi-Fi 已经打开,就不要重复操作。
注意:
不同 Android 版本、不同 ROM 对 Wi-Fi 命令支持可能不一样,所以请你不要想当然地只使用某一种命令。
请你先分析有哪些可能的实现方式,比如:
* `svc wifi enable`
* `settings get global wifi_on`
* `ip link show wlan0`
* `ip addr show wlan0`
* `wpa_cli`
* `cmd wifi`
* `dumpsys wifi`
然后根据兼容性和稳定性,选择更适合 Magisk Root 环境的实现方式。
如果某些命令在部分系统上可能不可用,请在脚本里做好兼容、兜底和日志记录,而不是让脚本直接崩溃。
---
## 3. Wi-Fi 列表和优先级
我希望脚本支持提前配置一个 Wi-Fi 列表。
比如:
```text
主 Wi-Fi
备用 Wi-Fi
手机热点
```
列表顺序就是优先级。
脚本应该优先连接列表中靠前的 Wi-Fi。
如果当前 Wi-Fi 可用,就不要频繁切换,避免影响 SSH、网页服务、IPv6、DDNS 等后续服务。
只有当当前 Wi-Fi 不可用、断开、没有拿到 IP,或者长时间无法恢复时,才尝试切换到下一个可用 Wi-Fi。
---
## 4. 已保存 Wi-Fi 和新 Wi-Fi 的处理
请你根据 Android 实际情况,设计合理逻辑:
* 如果 Wi-Fi 已经在系统中保存过,优先让系统自动连接;
* 如果系统自动连接失败,再尝试根据 Wi-Fi 名称选择已保存网络;
* 如果脚本能够可靠地支持“通过密码添加新 Wi-Fi”,可以设计为可选能力;
* 如果某些 Android 版本或 ROM 不适合脚本直接写入 Wi-Fi 密码,也请你明确说明原因,并给出更稳定的替代方案。
我不希望脚本为了追求“看起来功能完整”,强行使用不可靠的命令。
请你优先保证稳定性。
---
## 5. 不要频繁扫描和切换
手机是作为服务器使用的,所以稳定性比“永远连接最高优先级 Wi-Fi”更重要。
请你设计逻辑时注意:
* 如果当前 Wi-Fi 已经连接成功,并且已经拿到 IP,就不要乱动;
* 不要频繁扫描 Wi-Fi;
* 不要频繁断开当前 Wi-Fi 再切换;
* 不要在信号短暂波动时马上切换;
* 需要有合理的重试间隔和冷却时间;
* 避免脚本疯狂循环导致耗电、发热、刷屏日志。
---
## 6. Wi-Fi 是否连接成功的判断
我希望脚本不要只看 Wi-Fi 开关是否打开。
真正有意义的是:
```text
手机是否已经通过 wlan0 拿到了 IP 地址
```
请你设计连接成功判断逻辑。
比如:
* wlan0 拿到 IPv4;
* 或者 wlan0 拿到全局 IPv6;
* 或者两者都可以。
暂时不需要做外网连通性检测,比如 ping 某个公网 IP。
因为公网 ping 可能受 DNS、运营商、防火墙、网络环境影响,不适合作为 Wi-Fi 是否连接成功的唯一依据。
---
## 7. 日志要求
脚本必须输出清晰日志,方便我排查问题。
日志中需要包含:
* 时间,格式为 `YYYY-MM-DD HH:mm:ss`;
* 日志级别,比如 `INFO`、`OK`、`ERROR`;
* 当前操作名称,比如:
* 启动脚本
* 等待系统启动
* 打开 Wi-Fi 开关
* 检查 Wi-Fi 状态
* 等待系统自动连接
* 选择已保存 Wi-Fi
* 重连 Wi-Fi
* 进入守护循环
* 错误原因;
* 关键命令的返回值和输出,方便排查。
日志示例可以类似:
```text
2026-05-15 22:01:23 [OK] [启动脚本] 获取 lock 成功,当前实例开始运行
2026-05-15 22:01:40 [OK] [等待系统启动] 系统启动完成
2026-05-15 22:01:52 [INFO] [打开WiFi开关] 检测到 Wi-Fi 关闭,开始打开 Wi-Fi
2026-05-15 22:01:55 [OK] [打开WiFi开关] Wi-Fi 已进入可连接状态
2026-05-15 22:02:36 [OK] [检查WiFi状态] Wi-Fi 已连接,当前WiFi=xxx,已拿到 IP
```
日志文件也要限制大小,避免长期运行后日志无限变大。
请你在脚本里设计日志轮转,例如超过 1MB 后只保留最后若干行,并且在注释中写清楚单位。
---
## 8. 日志中的 Wi-Fi 名称和隐私
如果能解析当前 Wi-Fi 名称,日志中应该显示 Wi-Fi 名称,而不是 MAC 地址。
如果某些系统状态中出现 BSSID / MAC 地址,请隐藏或过滤掉。
例如不要输出:
```text
00:00:00:00:00:00 nid: 0 state: ASSOCIATING
```
这不是 Wi-Fi 名称,而是系统连接状态。
如果暂时解析不到当前 Wi-Fi 名称,但已经拿到 wlan0 的 IP,请不要误判为失败。
这种情况下可以写:
```text
Wi-Fi 已连接,已拿到 IP,但当前 Wi-Fi 名称暂时无法解析
```
或者如果配置列表里只有一个目标 Wi-Fi,也可以说明是根据配置推断的名称。
---
## 9. 防止重复运行
这个脚本是常驻守护脚本,不是一次性脚本。
所以请你加入 lock 机制,防止多个相同脚本同时运行。
我希望你使用适合 Android / Magisk 环境的方式,比如:
* 使用目录锁;
* lock 放在 `/dev` 这种重启后会自动清理的临时目录;
* 不要用容易残留的持久 PID 文件;
* 如果脚本正常退出,要自动释放 lock;
* 如果检测到已有实例运行,当前脚本应该退出,并写入日志。
请你解释一下 lock 的作用和为什么这样设计。
---
## 10. 守护循环
脚本不应该只在开机时执行一次。
它应该是常驻守护脚本。
大概逻辑应该是:
```text
等待系统启动完成
↓
打开 Wi-Fi
↓
等待系统自动连接已保存 Wi-Fi
↓
如果连接成功,进入守护循环
↓
每隔一段时间检查 Wi-Fi 是否仍然连接
↓
如果 Wi-Fi 正常,什么都不做
↓
如果 Wi-Fi 不正常,开始自动修复
↓
重新打开 Wi-Fi / 等待自动连接 / 必要时选择已保存 Wi-Fi
↓
失败则等待冷却时间后重试
```
---
# 请你特别注意
我上面的想法可能并不成熟,所以请你不要只是机械照着我的描述写。
请你先站在专业 Android 开发人员和 Linux 运维人员的角度,帮我完善整体逻辑。
在正式给代码之前,请你先做一次简短但准确的设计分析,包括:
1. 推荐目录结构;
2. 为什么 service.d 里只放启动器;
3. Wi-Fi 守护脚本应该承担哪些职责;
4. 哪些事情不应该放在 Wi-Fi 守护脚本里,比如 IPv6 检测、DDNS 同步;
5. 如何判断 Wi-Fi 是否真的可用;
6. 如何避免重复运行;
7. 哪些命令在 Android 上可能存在兼容性问题,脚本应该如何处理。
然后再给我完整代码。
---
# 输出要求
请输出:
## 第一部分:设计说明
用通俗易懂的话解释整体方案。
## 第二部分:目录结构
给出推荐目录结构,并说明每个文件的作用。
## 第三部分:Magisk 启动器脚本
给出完整代码,可以直接复制使用。
## 第四部分:Wi-Fi 守护脚本
给出完整代码,可以直接复制使用。
## 第五部分:安装和测试命令
包括:
* 创建目录;
* 写入脚本;
* 添加执行权限;
* 手动启动测试;
* 查看日志;
* 停止脚本;
* 清理 lock;
* 如何确认脚本是否正在运行。
---
# 我的最终目标
我希望得到的是一套适合 Android Root + Magisk + 手机服务器场景的 Wi-Fi 稳定连接方案。
重点是:
* 稳定;
* 易排查;
* 日志清晰;
* 不重复运行;
* 不乱切 Wi-Fi;
* 不把所有功能写成一个超级大脚本;
* 后续方便扩展 IPv6 检测、DDNS 更新、Debian / SSH 守护等功能。
请你根据这些目标,帮我设计并实现一个更专业、更稳定、更容易维护的版本。
1 个帖子 - 1 位参与者