You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Delphi开发Android应用更新后重启失败问题排查

问题分析与解决方案

错误码127的核心含义是Shell无法找到要执行的命令,结合你的场景,主要问题集中在命令格式、路径解析、流读取三个方面,以下是具体排查和修复方案:

1. 优先解决:su路径未指定绝对路径

多数Root设备的su不在系统默认的PATH环境变量中,直接调用su会导致Shell找不到命令,这是错误码127的最常见原因。

需要将su替换为绝对路径,优先尝试/system/bin/su,失败后再尝试/system/xbin/su(不同设备的su路径可能不同)。

2. 修复命令格式与转义问题

原代码中用"转义双引号,在Delphi的Format函数中容易出现解析冲突,换成单引号包裹命令内容更可靠;同时如果APK路径包含空格,必须用引号包裹避免被Shell拆分。

3. 关键修复:读取错误输出流

原代码只读取了标准输出流(InputStream),但命令执行的错误信息会输出到错误流(ErrorStream),导致你看不到具体错误原因,CommandOut为空。必须同时读取两个流才能定位问题。

4. 优化命令执行方式

直接将完整字符串传给Runtime.exec()容易出现Shell解析错误,建议将命令拆分为参数数组(su、-c、具体执行命令),避免字符串解析冲突。


修改后的完整代码

改进的命令执行函数(支持读取错误流)

function ExecShellCmd(const CmdArgs: TArray<string>; out Output: string; out ErrorOutput: string): Integer;
var
  Process: JProcess;
  StreamReader: JInputStreamReader;
  BufferedReader: JBufferedReader;
  Line: JString;
begin
  Result := -1;
  Output := '';
  ErrorOutput := '';
  try
    // 构造参数数组执行命令
    Process := TJRuntime.JavaClass.getRuntime.Exec(
      TJavaArray<JString>.Create(CmdArgs.Length)
    );
    for var I := 0 to CmdArgs.Length - 1 do
      TJavaArray<JString>(Process.parameters)[I] := StringToJString(CmdArgs[I]);

    // 读取标准输出
    StreamReader := TJInputStreamReader.JavaClass.init(Process.getInputStream);
    BufferedReader := TJBufferedReader.JavaClass.init(StreamReader);
    try
      Line := BufferedReader.readLine;
      while Line <> nil do
      begin
        Output := Output + JStringToString(Line) + sLineBreak;
        Line := BufferedReader.readLine;
      end;
    finally
      BufferedReader.close;
      StreamReader.close;
    end;

    // 读取错误输出(关键:用于调试错误原因)
    StreamReader := TJInputStreamReader.JavaClass.init(Process.getErrorStream);
    BufferedReader := TJBufferedReader.JavaClass.init(StreamReader);
    try
      Line := BufferedReader.readLine;
      while Line <> nil do
      begin
        ErrorOutput := ErrorOutput + JStringToString(Line) + sLineBreak;
        Line := BufferedReader.readLine;
      end;
    finally
      BufferedReader.close;
      StreamReader.close;
    end;

    Result := Process.waitFor;
  except
    on E: Exception do
    begin
      if Result = 0 then Result := -1;
      ErrorOutput := ErrorOutput + E.ToString;
    end;
  end;
end;

调用示例

var
  CmdArgs: TArray<string>;
  CmdResult: Integer;
  CommandOut, ErrorOut: string;
  ApkPath: string;
begin
  ApkPath := TPath.Combine(TPath.GetDownloadsPath, FileName);
  // 处理路径中的空格,用单引号包裹
  if Pos(' ', ApkPath) > 0 then
    ApkPath := '''' + ApkPath + '''';

  // 构造要执行的完整命令(替换MYACTIVITY为你的组件名,格式:包名/类名)
  const FullCmd = Format('pm install -r -d %s && am start -S -W -a android.intent.action.MAIN -c android.intent.category.LAUNCHER -n MYACTIVITY', [ApkPath]);

  // 优先尝试/system/bin/su
  CmdArgs := ['/system/bin/su', '-c', FullCmd];
  CmdResult := ExecShellCmd(CmdArgs, CommandOut, ErrorOut);
  
  // 失败则尝试/system/xbin/su
  if CmdResult <> 0 then
  begin
    CmdArgs := ['/system/xbin/su', '-c', FullCmd];
    CmdResult := ExecShellCmd(CmdArgs, CommandOut, ErrorOut);
  end;

  // 调试用:查看输出和错误信息
  // ShowMessage('结果:' + IntToStr(CmdResult) + #13#10 + '输出:' + CommandOut + #13#10 + '错误:' + ErrorOut);
end;

额外注意事项

  • 确保应用拥有读取外部存储的权限,否则无法访问下载目录的APK文件;
  • 部分Root管理工具(如Magisk)会限制应用的Su权限,需在Root管理器中手动允许该应用的Su请求;
  • MYACTIVITY必须替换为正确的组件名,格式为包名/类名(例如com.example.myapp/.MainActivity),否则启动命令会失效。

内容的提问来源于stack exchange,提问作者Nils Eilers

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 16:45:42