去年在调试Android设备时,我发现自己频繁地在命令行敲scrcpy命令。虽然这个开源工具已经足够强大,但每次都要记住各种参数组合实在不够友好。于是萌生了一个想法:为什么不为scrcpy做个图形化前端?正好Java 21刚发布不久,新特性中的虚拟线程和模式匹配让我跃跃欲试,加上Swing这个老牌GUI框架的现代化改造,一个可视化工具的开发计划就这么成型了。
这个工具的核心价值在于:
Java 21作为最新的LTS版本,带来了几项对GUI开发特别有用的特性:
java复制if (component instanceof JCheckBox checkbox) {
// 直接使用checkbox变量
}
很多人以为Swing已经过时,但实际上经过这些年的改进,它依然是个可靠的GUI选择:
采用经典的"三栏式"布局:
java复制JSplitPane mainSplit = new JSplitPane(
JSplitPane.HORIZONTAL_SPLIT,
new DevicePanel(), // 左侧设备列表
new JSplitPane(
JSplitPane.VERTICAL_SPLIT,
new OptionsPanel(), // 右上选项配置
new LogPanel() // 右下日志输出
)
);
通过adb devices命令获取设备列表,关键实现:
java复制List<AndroidDevice> discoverDevices() {
Process process = Runtime.getRuntime().exec("adb devices");
try (var reader = new BufferedReader(
new InputStreamReader(process.getInputStream()))) {
return reader.lines()
.skip(1) // 跳过"List of devices"标题
.filter(line -> !line.trim().isEmpty())
.map(line -> {
String[] parts = line.split("\\t");
return new AndroidDevice(parts[0], parts[1]);
})
.toList();
}
}
将GUI选项转换为scrcpy命令行参数的核心逻辑:
java复制String buildCommand(ScrcpyOptions options) {
StringBuilder cmd = new StringBuilder("scrcpy");
if (options.isShowTouches()) {
cmd.append(" --show-touches");
}
if (options.getBitRate() != DEFAULT_BITRATE) {
cmd.append(" -b ").append(options.getBitRate());
}
// 其他参数处理...
return cmd.toString();
}
传统Swing中,执行adb命令会导致界面冻结。现在可以这样处理:
java复制button.addActionListener(e -> {
Thread.startVirtualThread(() -> {
String result = executeAdbCommand();
SwingUtilities.invokeLater(() -> {
textArea.append(result); // 线程安全更新UI
});
});
});
处理不同组件的值变化时,新模式匹配语法更简洁:
java复制void stateChanged(ChangeEvent e) {
if (e.getSource() instanceof JSlider slider) {
updateBitRateLabel(slider.getValue());
} else if (e.getSource() instanceof JCheckBox checkbox) {
toggleOption(checkbox.getText(), checkbox.isSelected());
}
}
默认的JTable在设备多时会卡顿,解决方案:
实时显示scrcpy输出时要注意:
java复制// 错误做法:每次输出都立即刷新
textArea.append(line + "\n");
textArea.repaint();
// 正确做法:批量更新
private StringBuilder logBuffer = new StringBuilder();
void appendLog(String line) {
logBuffer.append(line).append("\n");
if (logBuffer.length() > 8192) {
flushLog();
}
}
private void flushLog() {
SwingUtilities.invokeLater(() -> {
textArea.append(logBuffer.toString());
logBuffer.setLength(0);
});
}
Java 21改进了jpackage工具,支持生成更小的镜像:
bash复制jpackage --name ScrcpyGUI \
--input target/libs \
--main-jar scrcpy-gui.jar \
--main-class com.example.Main \
--type app-image \
--java-options "--enable-preview"
通过jlink创建定制化JRE:
bash复制jlink --add-modules java.desktop,java.base \
--strip-debug \
--no-header-files \
--output ./custom-jre
解决方案:
处理流程:
在main方法开始处添加:
java复制System.setProperty("sun.java2d.uiScale", "1.0");
JFrame.setDefaultLookAndFeelDecorated(true);
FlatLaf.setGlobalExtraDefaults(
Collections.singletonMap("@scale", 1.0f));
这个项目最让我惊喜的是Java 21虚拟线程的表现,即使在老机器上同时操作多个设备,界面依然保持流畅。Swing配合FlatLaf的外观也完全能满足现代GUI的需求,完全没必要为了"看起来时髦"而选择Electron这类重型方案。