Java 為了跨平台犧牲了對底層硬體的直接控制權。當需要調節系統音量、讀取感測器等硬體相關功能時,常見策略是:用 C/C++ 編寫原生小工具,Java 透過 Runtime.exec() 呼叫並解析輸出。本文以音量控制為例,完整演示此混合開發模式。
使用 Windows Multimedia API 實現三大核心功能:
mixerGetLineInfo:定位 Master Volume 控制線路mixerGetControlDetails:讀取當前音量值mixerSetControlDetails:設定新音量值編譯為 Console 應用程式 VolumeControl.exe,支援三種參數模式:
| 參數 | 功能 | 輸出範例 |
|---|---|---|
0 | 查詢當前音量 | 標準輸出數字 0-100 |
1 | 增加音量 | 無輸出 |
2 | 減少音量 | 無輸出 |
public class VolumeTracker extends JPanel implements Runnable {
private Thread pbThread; // 繪圖刷新執行緒
private double seconds = 82.0; // 當前音量 (0-100 映射)
private final double duration = 100.0;
// 初始化:呼叫 exe 取得初始音量
private void initVolume() {
try {
Runtime rt = Runtime.getRuntime();
String cmd = new MngPathTool().getCurPath() + "\\binex\\VolumeControl.exe 0";
Process child = rt.exec(cmd);
// 讀取標準輸出解析音量值
InputStreamReader reader = new InputStreamReader(child.getInputStream());
char[] buf = new char[5];
reader.read(buf);
String volStr = new String(buf).replaceAll("\\D", "");
seconds = Integer.parseInt(volStr);
child.waitFor(); // 等待結束
} catch (Exception e) { e.printStackTrace(); }
}
// 繪製垂直進度條
public void paint(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
Dimension d = getSize();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
int numSegments = 20;
int progress = (int) ((duration - seconds) / duration * numSegments);
double segHeight = (d.height - 4) / (double) numSegments;
for (int i = 0; i < numSegments; i++) {
g2.setColor(i < progress ? Color.BLUE : Color.BLUE.darker());
g2.fill3DRect(2, (int)(i * segHeight) + 2, d.width - 4, (int)segHeight, true);
}
}
// 控制介面
public void addVolume() { changeVolume(false); initVolume(); }
public void minusVolume() { changeVolume(true); initVolume(); }
private void changeVolume(boolean isMinus) {
try {
String cmd = new MngPathTool().getCurPath() +
"\\binex\\VolumeControl.exe " + (isMinus ? "2" : "1");
Runtime.getRuntime().exec(cmd);
} catch (Exception e) { e.printStackTrace(); }
}
// 執行緒循環刷新
public void run() {
while (pbThread != null) {
try { pbThread.sleep(99); } catch (Exception e) { break; }
repaint();
}
}
}
事件流向:JVolumeDlg → VolumeControl → VolumeTracker
Process.getInputStream() 讀取 stdout,需注意編碼與緩衝。MngPathTool.getCurPath() 解決部署路徑差異,確保 exe 可被定位。Runnable 實現非阻塞刷新。此模式可推廣至任何「Java 難以直接實現、但原生 API 成熟」的場景:
若追求更緊密整合,可考慮 JNI/JNA 直接映射 DLL,但 Runtime.exec 以「開發簡單、部署獨立、故障隔離」三大優勢,仍是中小型專案的首選方案。