如果仅需要调用脚本实现 App 自动升级(无需无感、允许进程中断),核心是保证脚本可执行、权限足够,且适配 Android 系统特性。以下是简化后的可直接使用的脚本 + 程序调用方案,确保「一键自动升级」生效:
一、最终可执行的升级脚本(简化版)
保留核心自动升级逻辑,去掉无感适配,确保稳定性:
bash
运行
#!/system/bin/sh
# 系统级App自动升级脚本(仅实现自动升级,需root/system权限)
# 适用场景:允许进程中断,仅需自动完成「杀进程→安装→重启服务」
# ===================== 配置项(根据实际情况修改)=====================
TARGET_PACKAGE="com.your.system.app" # 你的App包名
APK_PATH="/sdcard/YourSystemApp_new.apk" # 新APK存放路径(建议先推送到sdcard)
PROP_UPGRADING="persist.sys.yourapp.upgrading" # 升级标记属性
SERVICE_NAME=".PersistentService" # 保活服务名
# ====================================================================
# 输出日志(方便调试)
LOG_TAG="AppAutoUpgrade"
log() {
echo "[$LOG_TAG] $1"
log -t $LOG_TAG "$1" 2>/dev/null # 输出到系统日志,可通过adb logcat查看
}
# 步骤1:检查权限(必须root/system用户)
if [ "$(id -u)" -ne 0 ]; then
log "Error: 脚本需要root权限执行!"
exit 1
fi
# 步骤2:检查APK文件是否存在
if [ ! -f "$APK_PATH" ]; then
log "Error: APK文件不存在!路径:$APK_PATH"
exit 1
fi
# 步骤3:标记升级状态(关闭保活,允许杀进程)
log "Step 1: 标记升级状态,关闭保活"
setprop $PROP_UPGRADING true
# 步骤4:强制杀死旧进程(保活逻辑已关闭,可正常杀死)
log "Step 2: 杀死旧进程"
am force-stop $TARGET_PACKAGE
sleep 1 # 等待进程完全退出
# 步骤5:覆盖安装新APK
log "Step 3: 开始安装新APK..."
pm install -r "$APK_PATH"
if [ $? -eq 0 ]; then
log "APK安装成功!"
else
log "Error: APK安装失败!"
setprop $PROP_UPGRADING false # 安装失败,恢复保活
exit 1
fi
# 步骤6:重置升级状态(恢复保活)
log "Step 4: 重置升级状态,恢复保活"
setprop $PROP_UPGRADING false
# 步骤7:启动新进程的保活服务
log "Step 5: 启动新版本服务"
am startservice -n "$TARGET_PACKAGE$SERVICE_NAME"
# 验证服务是否启动
sleep 2
if am startservice -n "$TARGET_PACKAGE$SERVICE_NAME" 2>&1 | grep -q "Service started"; then
log "升级完成!新服务已启动"
else
log "警告:服务启动失败,请手动重启App!"
fi
exit 0
二、脚本使用前置条件(必须满足)
-
权限要求:
- 脚本必须以
root或system用户执行(Android 系统限制pm install/am force-stop需系统权限); - 脚本文件需赋予可执行权限:
chmod +x /system/bin/app_upgrade.sh(建议将脚本放到/system/bin/目录)。
- 脚本必须以
-
APK 要求:
- 新 APK 必须和旧版本同签名(系统级 App 需用平台签名);
- 新 APK 需先推送到设备(如
/sdcard/YourSystemApp_new.apk),确保路径可访问。
-
系统适配:
- 已完成之前的系统源码修改(ProcessList/PowerManagerService 中的升级属性判断);
- 设备已解锁 /system 分区(若脚本 / APK 需写入 /system 目录)。
三、程序中调用脚本的代码(Android 端)
在 App 中通过代码调用脚本,实现「一键自动升级」,核心是确保以 root 权限执行:
1. 核心调用代码
java
运行
import android.util.Log;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
/**
* 调用升级脚本,实现App自动升级
*/
public class UpgradeManager {
private static final String TAG = "UpgradeManager";
// 脚本路径(建议放到/system/bin/,确保权限)
private static final String SCRIPT_PATH = "/system/bin/app_upgrade.sh";
// 新APK推送路径(需先把APK传到设备这个路径)
public static final String APK_UPLOAD_PATH = "/sdcard/YourSystemApp_new.apk";
/**
* 执行自动升级(异步调用,避免阻塞主线程)
*/
public void startAutoUpgrade() {
new Thread(() -> {
Process process = null;
try {
// 1. 检查脚本是否可执行
if (!isScriptExecutable()) {
Log.e(TAG, "脚本无执行权限,请先执行 chmod +x " + SCRIPT_PATH);
return;
}
// 2. 以root权限执行脚本
Log.d(TAG, "开始执行升级脚本...");
process = Runtime.getRuntime().exec(new String[]{"su", "-c", SCRIPT_PATH});
// 3. 读取脚本输出(调试用,可查看升级过程)
readProcessOutput(process.getInputStream(), "INFO");
readProcessOutput(process.getErrorStream(), "ERROR");
// 4. 等待脚本执行完成,检查结果
int exitCode = process.waitFor();
if (exitCode == 0) {
Log.d(TAG, "自动升级执行成功!");
} else {
Log.e(TAG, "自动升级失败,脚本退出码:" + exitCode);
}
} catch (Exception e) {
Log.e(TAG, "升级脚本执行异常", e);
} finally {
if (process != null) {
process.destroy();
}
}
}).start();
}
/**
* 读取进程输出流(日志)
*/
private void readProcessOutput(InputStream is, String type) {
new Thread(() -> {
try (BufferedReader br = new BufferedReader(new InputStreamReader(is))) {
String line;
while ((line = br.readLine()) != null) {
if ("INFO".equals(type)) {
Log.d(TAG, "脚本输出:" + line);
} else {
Log.e(TAG, "脚本错误:" + line);
}
}
} catch (Exception e) {
Log.e(TAG, "读取脚本输出失败", e);
}
}).start();
}
/**
* 检查脚本是否有可执行权限
*/
private boolean isScriptExecutable() {
try {
Process process = Runtime.getRuntime().exec(new String[]{"su", "-c", "ls -l " + SCRIPT_PATH});
BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line = br.readLine();
br.close();
process.waitFor();
// 检查权限位是否包含x(可执行)
return line != null && line.contains("-rwx") || line.contains("-rx");
} catch (Exception e) {
return false;
}
}
/**
* 推送新APK到设备(升级前需先调用,把本地APK传到设备)
* @param localApkPath 本地APK路径(如/sdcard/Download/new.apk)
*/
public void pushNewApkToDevice(String localApkPath) {
new Thread(() -> {
try {
// 推送APK到目标路径(覆盖原有文件)
Process process = Runtime.getRuntime().exec(
new String[]{"su", "-c", "cp " + localApkPath + " " + APK_UPLOAD_PATH}
);
int exitCode = process.waitFor();
if (exitCode == 0) {
Log.d(TAG, "APK推送成功:" + APK_UPLOAD_PATH);
} else {
Log.e(TAG, "APK推送失败");
}
} catch (Exception e) {
Log.e(TAG, "推送APK异常", e);
}
}).start();
}
}
2. 调用流程(关键)
java
运行
// 1. 先推送新APK到设备(需确保APK已在本地)
String localApkPath = "/sdcard/Download/YourSystemApp_v2.apk"; // 本地新APK路径
UpgradeManager upgradeManager = new UpgradeManager();
upgradeManager.pushNewApkToDevice(localApkPath);
// 2. 等待APK推送完成(建议加2-3秒延迟,或监听推送结果)
new Handler().postDelayed(() -> {
// 3. 执行自动升级
upgradeManager.startAutoUpgrade();
}, 3000);
四、关键注意事项(必看)
-
脚本部署:
- 将脚本文件
app_upgrade.sh放到设备/system/bin/目录(需解锁 /system 分区); - 执行
chmod +x /system/bin/app_upgrade.sh赋予可执行权限; - 执行
chown root:root /system/bin/app_upgrade.sh确保属主为 root。
- 将脚本文件
-
APK 推送:
- 升级前必须先将新 APK 推送到脚本配置的
APK_PATH路径(如/sdcard/YourSystemApp_new.apk); - 新 APK 必须和旧版本同签名 (系统级 App 需用平台签名),否则
pm install -r会失败。
- 升级前必须先将新 APK 推送到脚本配置的
-
权限保障:
- App 必须是系统级(
android:sharedUserId="android.uid.system"+ 平台签名); - 设备必须已 root,或 App 拥有
android.permission.INJECT_EVENTS/android.permission.MANAGE_PACKAGES系统权限。
- App 必须是系统级(
-
失败排查:
- 脚本执行失败:通过
adb logcat -s AppAutoUpgrade查看日志; - 安装失败:检查 APK 签名、路径权限、是否为系统 App(
/system/priv-app下的 App 需先删除原有文件再安装); - 进程杀不死:检查系统源码中
PROP_UPGRADING属性是否生效(执行getprop persist.sys.yourapp.upgrading确认值为 true)。
- 脚本执行失败:通过
五、简化版脚本执行流程(核心逻辑)
- 脚本以 root 权限执行,先标记升级状态(关闭保活);
- 强制杀死旧 App 进程(此时保活逻辑已豁免,可正常杀死);
- 覆盖安装新 APK(
pm install -r保留数据,覆盖安装); - 重置升级状态(恢复保活);
- 启动新进程的保活服务,完成自动升级。
六、验证升级是否成功
- 执行
adb logcat -s AppAutoUpgrade查看脚本执行日志,确认无错误; - 执行
adb shell pm list packages -f | grep com.your.system.app查看 APK 路径是否为新版本; - 执行
adb shell ps | grep com.your.system.app确认新进程已启动; - 执行
adb shell getprop persist.sys.yourapp.upgrading确认值为 false(保活已恢复)。
这套方案仅聚焦「自动升级」核心需求,去掉了无感升级的复杂适配,代码和脚本都更简洁,只需确保权限和路径正确,即可实现「调用脚本→自动杀旧进程→装新 APK→重启服务」的全自动化流程。
主要是这个过程需要用户交互吗
这套自动升级脚本的执行过程完全不需要用户手动交互(前提是满足权限 / 环境条件),全程由脚本自动完成,用户无需点击 "安装""确认" 等操作。以下是核心说明:
一、为什么不需要用户交互?
Android 系统中普通 App 安装需要用户确认(弹窗),但你的场景是系统级 App + root / 系统权限 + 命令行安装,绕开了用户交互环节:
| 操作环节 | 是否需要用户交互 | 核心原因 |
|---|---|---|
| 标记升级状态(setprop) | 否 | 系统属性设置是后台操作,无界面、无弹窗 |
| 杀死旧进程(am force-stop) | 否 | 系统命令后台杀进程,无任何用户感知 |
| 安装 APK(pm install -r) | 否 | pm install 是系统级命令(root/SystemUID 执行),跳过 PackageInstaller 的用户确认弹窗 |
| 启动新服务(am startservice) | 否 | 后台启动服务,无界面交互 |
二、"无需用户交互" 的前提条件(必须满足)
如果缺少以下条件,可能会触发用户交互(甚至安装失败),需重点保障:
-
权限足够 :
- 脚本必须以
root或system用户执行(普通用户执行pm install会触发权限弹窗); - App 必须是系统级(
android:sharedUserId="android.uid.system"+ 平台签名),否则pm install可能要求用户确认。
- 脚本必须以
-
关闭安装验证 :
-
部分设备开启了 "未知来源安装验证",需提前通过命令关闭(仅首次配置): bash
运行
# 关闭未知来源安装验证(系统级) settings put secure install_non_market_apps 1 # 允许特定包名免验证安装 pm grant com.your.system.app android.permission.INSTALL_PACKAGES
-
-
APK 路径可访问 :
- 新 APK 存放路径(如
/sdcard/)需有可读权限,避免脚本因 "文件不可读" 触发异常(无用户交互,但会安装失败)。
- 新 APK 存放路径(如
三、可能触发用户交互的例外场景(需规避)
-
设备开启 "安装监控 / 安全验证" :部分厂商(如华为、小米)的定制系统会对
pm install触发 "安全检测" 弹窗,需提前在系统设置中关闭:-
路径:设置 → 安全 → 外部来源应用安装 → 允许 "系统服务 / 你的 App";
-
或通过命令关闭(不同厂商命令不同,示例): bash
运行
# 小米/红米 setprop persist.security.install_non_market_apps 1 # 华为 am broadcast -a com.huawei.packageinstaller.action.ALLOW_INSTALL_UNKNOWN_APP -n com.huawei.packageinstaller/.permission.PackageInstallerReceiver --es package "com.your.system.app"
-
-
APK 签名不匹配 :若新 APK 签名与旧版本不一致,
pm install -r会失败(无用户交互,但升级失败),需确保新旧 APK 使用同一套平台签名。 -
/system 分区只读 :若你的 App 安装在
/system/priv-app/(系统分区),需先挂载分区为可写(脚本中可添加):bash
运行
# 脚本中新增:挂载/system为可写(仅需一次) mount -o rw,remount /system
四、脚本中规避用户交互的补充配置
在脚本开头添加以下命令,确保全程无交互:
bash
运行
#!/system/bin/sh
# 新增:关闭所有可能触发交互的配置
# 1. 关闭安装验证
settings put secure install_non_market_apps 1 >/dev/null 2>&1
# 2. 挂载/system为可写(若App在/system目录)
mount -o rw,remount /system >/dev/null 2>&1
# 3. 禁用安装弹窗(部分厂商)
pm grant $TARGET_PACKAGE android.permission.INSTALL_PACKAGES >/dev/null 2>&1
# 原有配置项&逻辑...
五、总结
只要满足 "root/SystemUID 权限 + 系统级 App + 同签名 APK + 关闭安装验证",脚本执行全程:✅ 无用户弹窗 / 确认✅ 无界面交互✅ 后台自动完成 "杀进程→装 APK→启服务"
用户唯一可能的感知是:App 进程短暂消失(几秒)后重新启动,无任何手动操作要求。
如果实际执行中出现用户交互弹窗,优先排查:
- 脚本是否以
root执行(执行id -u确认返回 0); - 是否关闭了 "未知来源安装验证";
- App 是否为系统级(是否在
/system/priv-app/目录 + 平台签名)。