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
相关产品推荐
相关产品推荐

