调用脚本实现 App 自动升级(无需无感、允许进程中断)

如果仅需要调用脚本实现 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

二、脚本使用前置条件(必须满足)

  1. 权限要求

    • 脚本必须以 rootsystem 用户执行(Android 系统限制 pm install/am force-stop 需系统权限);
    • 脚本文件需赋予可执行权限:chmod +x /system/bin/app_upgrade.sh(建议将脚本放到 /system/bin/ 目录)。
  2. APK 要求

    • 新 APK 必须和旧版本同签名(系统级 App 需用平台签名);
    • 新 APK 需先推送到设备(如 /sdcard/YourSystemApp_new.apk),确保路径可访问。
  3. 系统适配

    • 已完成之前的系统源码修改(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);

四、关键注意事项(必看)

  1. 脚本部署

    • 将脚本文件 app_upgrade.sh 放到设备 /system/bin/ 目录(需解锁 /system 分区);
    • 执行 chmod +x /system/bin/app_upgrade.sh 赋予可执行权限;
    • 执行 chown root:root /system/bin/app_upgrade.sh 确保属主为 root。
  2. APK 推送

    • 升级前必须先将新 APK 推送到脚本配置的 APK_PATH 路径(如 /sdcard/YourSystemApp_new.apk);
    • 新 APK 必须和旧版本同签名 (系统级 App 需用平台签名),否则 pm install -r 会失败。
  3. 权限保障

    • App 必须是系统级(android:sharedUserId="android.uid.system" + 平台签名);
    • 设备必须已 root,或 App 拥有 android.permission.INJECT_EVENTS/android.permission.MANAGE_PACKAGES 系统权限。
  4. 失败排查

    • 脚本执行失败:通过 adb logcat -s AppAutoUpgrade 查看日志;
    • 安装失败:检查 APK 签名、路径权限、是否为系统 App(/system/priv-app 下的 App 需先删除原有文件再安装);
    • 进程杀不死:检查系统源码中 PROP_UPGRADING 属性是否生效(执行 getprop persist.sys.yourapp.upgrading 确认值为 true)。

五、简化版脚本执行流程(核心逻辑)

  1. 脚本以 root 权限执行,先标记升级状态(关闭保活);
  2. 强制杀死旧 App 进程(此时保活逻辑已豁免,可正常杀死);
  3. 覆盖安装新 APK(pm install -r 保留数据,覆盖安装);
  4. 重置升级状态(恢复保活);
  5. 启动新进程的保活服务,完成自动升级。

六、验证升级是否成功

  1. 执行 adb logcat -s AppAutoUpgrade 查看脚本执行日志,确认无错误;
  2. 执行 adb shell pm list packages -f | grep com.your.system.app 查看 APK 路径是否为新版本;
  3. 执行 adb shell ps | grep com.your.system.app 确认新进程已启动;
  4. 执行 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) 后台启动服务,无界面交互

二、"无需用户交互" 的前提条件(必须满足)

如果缺少以下条件,可能会触发用户交互(甚至安装失败),需重点保障:

  1. 权限足够

    • 脚本必须以 rootsystem 用户执行(普通用户执行 pm install 会触发权限弹窗);
    • App 必须是系统级(android:sharedUserId="android.uid.system" + 平台签名),否则 pm install 可能要求用户确认。
  2. 关闭安装验证

    • 部分设备开启了 "未知来源安装验证",需提前通过命令关闭(仅首次配置): bash

      运行

      复制代码
      # 关闭未知来源安装验证(系统级)
      settings put secure install_non_market_apps 1
      # 允许特定包名免验证安装
      pm grant com.your.system.app android.permission.INSTALL_PACKAGES
  3. APK 路径可访问

    • 新 APK 存放路径(如 /sdcard/)需有可读权限,避免脚本因 "文件不可读" 触发异常(无用户交互,但会安装失败)。

三、可能触发用户交互的例外场景(需规避)

  1. 设备开启 "安装监控 / 安全验证" :部分厂商(如华为、小米)的定制系统会对 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"
  2. APK 签名不匹配 :若新 APK 签名与旧版本不一致,pm install -r 会失败(无用户交互,但升级失败),需确保新旧 APK 使用同一套平台签名。

  3. /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 进程短暂消失(几秒)后重新启动,无任何手动操作要求。

如果实际执行中出现用户交互弹窗,优先排查:

  1. 脚本是否以 root 执行(执行 id -u 确认返回 0);
  2. 是否关闭了 "未知来源安装验证";
  3. App 是否为系统级(是否在 /system/priv-app/ 目录 + 平台签名)。
相关推荐
Kapaseker1 小时前
Compose 进阶—巧用 GraphicsLayer
android·kotlin
黄林晴2 小时前
Android17 为什么重写 MessageQueue
android
阿巴斯甜1 天前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker1 天前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq95271 天前
Andorid Google 登录接入文档
android
黄林晴1 天前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab2 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿2 天前
Android MediaPlayer 笔记
android
Jony_2 天前
Android 启动优化方案
android
阿巴斯甜2 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android