调用脚本实现 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/ 目录 + 平台签名)。
相关推荐
圆号本昊3 小时前
【2025最新】Flutter 加载显示 Live2D 角色,实战与踩坑全链路分享
android·flutter
小曹要微笑3 小时前
MySQL的TRIM函数
android·数据库·mysql
mrsyf4 小时前
Android Studio Otter 2(2025.2.2版本)安装和Gradle配置
android·ide·android studio
DB虚空行者4 小时前
MySQL恢复之Binlog格式详解
android·数据库·mysql
liang_jy6 小时前
Android 事件分发机制(一)—— 全流程源码解析
android·面试·源码
峥嵘life7 小时前
2026 Android EDLA 认证相关资源网址汇总(持续更新)
android·java·学习
kkk_皮蛋7 小时前
在移动端使用 WebRTC (Android/iOS)
android·ios·webrtc
aqi008 小时前
FFmpeg开发笔记(九十六)采用Kotlin+Compose的视频编辑器OpenVideoEditor
android·ffmpeg·kotlin·音视频·流媒体
诸神黄昏EX9 小时前
Android Safety 系列专题【篇一:系统签名】
android