问题背景
在开发 Android 应用时,我们经常需要执行 Linux 命令并处理其输出。但当命令产生无限输出流时,我们需要:
-
实时处理输出内容
-
在满足特定条件时主动终止命令
-
区分处理不同类型的事件(成功、错误、普通日志)
本文将介绍如何优雅解决这些问题,并提供完整的实现方案。
核心实现
1. 命令执行与流处理
private void execShellCommand(String path) {
String command = "cat " + path;
Process process = null;
try {
process = Runtime.getRuntime().exec(command);
// 处理错误流(防止阻塞)
new Thread(() -> {
try (BufferedReader errorReader = new BufferedReader(
new InputStreamReader(process.getErrorStream()))) {
String errorLine;
while ((errorLine = errorReader.readLine()) != null) {
handleErrorLog("test_result error: " + errorLine);
}
} catch (IOException e) {
Log.e(TAG, "Error reading error stream: ", e);
}
}).start();
// 处理输入流
new Thread(() -> {
try (BufferedReader inputReader = new BufferedReader(
new InputStreamReader(process.getInputStream()))) {
String line;
while ((line = inputReader.readLine()) != null) {
handleOutputLog(line);
// 检查终止条件
if (line.contains("success")) {
handleSuccessLog("Status: SUCCESS");
terminateProcess(process);
break;
}
}
} catch (IOException e) {
Log.e(TAG, "Error reading input stream: ", e);
}
}).start();
// 等待进程结束
int exitCode = process.waitFor();
Log.w(TAG, "test_result exit with code: " + exitCode);
} catch (Exception e) {
e.printStackTrace();
}
}
2. 优雅终止进程
@RequiresApi(api = Build.VERSION_CODES.O)
private void terminateProcess(Process process) {
if (process == null) return;
try {
// 尝试发送 Ctrl+C 信号
OutputStream os = process.getOutputStream();
os.write(3); // ASCII 3 = ETX (End of Text)
os.flush();
// 等待短暂时间
Thread.sleep(100);
if (process.isAlive()) {
// 强制终止
process.destroyForcibly();
Log.w(TAG, "进程被强制终止");
}
} catch (Exception e) {
Log.e(TAG, "终止进程失败", e);
process.destroyForcibly();
}
}
3. 事件处理与 UI 更新
// 使用 Handler 在主线程更新 UI
private final Handler mLogHandler = new LogHandler(Looper.getMainLooper());
private static class LogHandler extends Handler {
public LogHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(@NonNull Message msg) {
switch (msg.what) {
case COMMAND_UPDATE_LOG:
Log.d(TAG, "普通日志: " + msg.obj);
break;
case COMMAND_ERROR_LOG:
Log.e(TAG, "错误日志: " + msg.obj);
break;
case COMMAND_SUCCESS_LOG:
Log.i(TAG, "成功日志: " + msg.obj);
break;
}
}
}
// 处理不同类型的事件
private void handleOutputLog(String message) {
Message msg = new Message();
msg.what = COMMAND_UPDATE_LOG;
msg.obj = message;
mLogHandler.sendMessage(msg);
}
private void handleErrorLog(String message) {
Message msg = new Message();
msg.what = COMMAND_ERROR_LOG;
msg.obj = message;
mLogHandler.sendMessage(msg);
}
private void handleSuccessLog(String message) {
Message msg = new Message();
msg.what = COMMAND_SUCCESS_LOG;
msg.obj = message;
mLogHandler.sendMessage(msg);
}
关键设计要点
1. 多线程流处理
| 线程 | 功能 | 重要性 |
|---|---|---|
| 主线程 | UI 更新和用户交互 | 高 |
| 输入流线程 | 读取命令标准输出 | 中 |
| 错误流线程 | 读取命令错误输出 | 高 |
2. 事件分类处理
| 事件类型 | 标识 | 处理方式 |
|---|---|---|
| 普通日志 | COMMAND_UPDATE_LOG | 记录并显示 |
| 错误日志 | COMMAND_ERROR_LOG | 记录并告警 |
| 成功事件 | COMMAND_SUCCESS_LOG | 终止进程并通知 |
完整实现代码
package com.android.myapplication;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.widget.Button;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
public class MainActivity extends AppCompatActivity {
private Button button;
private static final String TAG = "CommandExecutor";
private static final int COMMAND_UPDATE_LOG = 1;
private static final int COMMAND_ERROR_LOG = 2;
private static final int COMMAND_SUCCESS_LOG = 3;
private final Handler mLogHandler = new LogHandler(Looper.getMainLooper());
private static class LogHandler extends Handler {
public LogHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(@NonNull Message msg) {
switch (msg.what) {
case COMMAND_UPDATE_LOG:
Log.d(TAG, "普通日志: " + msg.obj);
break;
case COMMAND_ERROR_LOG:
Log.e(TAG, "错误日志: " + msg.obj);
break;
case COMMAND_SUCCESS_LOG:
Log.i(TAG, "成功日志: " + msg.obj);
break;
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button = findViewById(R.id.button);
button.setOnClickListener(v -> {
execShellCommand("/data/local/tmp/test_path");
});
}
private void execShellCommand(String path) {
String command = "cat " + path;
try {
Process process = Runtime.getRuntime().exec(command);
// 处理错误流(防止阻塞)
new Thread(() -> {
try (BufferedReader errorReader = new BufferedReader(
new InputStreamReader(process.getErrorStream()))) {
String errorLine;
while ((errorLine = errorReader.readLine()) != null) {
handleErrorLog("错误: " + errorLine);
}
} catch (IOException e) {
Log.e(TAG, "读取错误流失败: ", e);
}
}).start();
// 处理输入流
new Thread(() -> {
try (BufferedReader inputReader = new BufferedReader(
new InputStreamReader(process.getInputStream()))) {
String line;
while ((line = inputReader.readLine()) != null) {
handleOutputLog(line);
// 检查终止条件
if (line.contains("success")) {
handleSuccessLog("状态: 成功");
terminateProcess(process);
break;
}
}
} catch (IOException e) {
Log.e(TAG, "读取输入流失败: ", e);
}
}).start();
// 等待进程结束
int exitCode = process.waitFor();
Log.w(TAG, "命令退出代码: " + exitCode);
} catch (Exception e) {
Log.e(TAG, "执行命令失败", e);
}
}
@RequiresApi(api = Build.VERSION_CODES.O)
private void terminateProcess(Process process) {
if (process == null) return;
try {
// 尝试优雅终止
OutputStream os = process.getOutputStream();
os.write(3); // 发送 Ctrl+C 信号
os.flush();
// 短暂等待
Thread.sleep(100);
if (process.isAlive()) {
// 强制终止
process.destroyForcibly();
Log.w(TAG, "进程被强制终止");
}
} catch (Exception e) {
Log.e(TAG, "终止进程失败", e);
process.destroyForcibly();
}
}
private void handleOutputLog(String message) {
Message msg = new Message();
msg.what = COMMAND_UPDATE_LOG;
msg.obj = message;
mLogHandler.sendMessage(msg);
}
private void handleErrorLog(String message) {
Message msg = new Message();
msg.what = COMMAND_ERROR_LOG;
msg.obj = message;
mLogHandler.sendMessage(msg);
}
private void handleSuccessLog(String message) {
Message msg = new Message();
msg.what = COMMAND_SUCCESS_LOG;
msg.obj = message;
mLogHandler.sendMessage(msg);
}
}