其实这个功能极少有人会用吧,大部分用Delphi开发安卓程序都是做一些和UI打交道的前端程序,而我又偏偏拿Delphi在做一些偏硬件应用类的程序。
今天碰到的问题是这样的,我的rk3588的开发板上有2块100M的以太网卡,一块是原生百兆以太网 eth0,而另外一块是挂载为USB设备的百兆以太网 eth1
安卓系统只支持1块网卡的持久化设置,一般都是原生网卡,在我这里就是eth0,而另外一块USB网卡是需要输入ADB命令来设置的,而且还不支持持久化,也就是你把网线拔了,这块网卡的设置就被安卓系统自动清掉了,下次你再接通网线后必须再设置一次,非常头大。
于是乎,我想在Delphi开发的程序中来设置,这样我的程序就可以接管,方便太多了,但问题来了,Delphi如何运行外部的ADB命令呢?
干了差不多1天,尝试了很多种方法都以失败告终,Delphi无法直接调用ADB的命令,那怎么办呢,我想到了笨但好用的方法,就是先用java写一个函数,传入需要运行的命令字符串,由java代码来调用,最后再由Delphi来调用这个java的函数。
于是我就朝这个方向尝试了,先经过了一些折腾最终写出了一个java代码,并通过编译生成了jar文件,java代码如下:
import android.util.Log;
import java.io.*;
public class ShellHelper {
public static int execRootCommand(String command) {
try {
Process process = Runtime.getRuntime().exec("su");
DataOutputStream os = new DataOutputStream(process.getOutputStream());
os.writeBytes(command + "\n");
os.writeBytes("exit\n");
os.flush();
// 读取输出(略)
process.waitFor();
return 0;
} catch (Exception e) {
Log.e("ShellHelper", "execRootCommand error: ", e);
return -1;
}
}
public static int execCommand(String command) {
try {
Process process = Runtime.getRuntime().exec(new String[]{command});
int exitCode = process.waitFor();
return exitCode;
} catch (Exception e) {
Log.e("ShellHelper", "execCommand error: ", e);
return -1;
}
}
public static String execRootCommandWithResult(String command) {
Process process = null;
BufferedReader reader = null;
DataOutputStream os = null;
try {
// 1. 获取 root shell
process = Runtime.getRuntime().exec("su");
// 2. 准备输入输出流
os = new DataOutputStream(process.getOutputStream());
reader = new BufferedReader(
new InputStreamReader(process.getInputStream())
);
// 3. 执行命令
os.writeBytes(command + "\n");
os.writeBytes("exit\n");
os.flush();
// 4. 读取输出
StringBuilder output = new StringBuilder();
char[] buffer = new char[4096];
int read;
while ((read = reader.read(buffer)) > 0) {
output.append(buffer, 0, read);
}
// 5. 等待执行完成
process.waitFor();
return output.toString();
} catch (Exception e) {
Log.e("ShellHelper", "execRootCommandWithResult error: ", e);
return "ERROR: " + e.getMessage();
} finally {
// 6. 清理资源
try {
if (os != null) os.close();
if (reader != null) reader.close();
if (process != null) process.destroy();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
这个java类中一共有3个函数:
execRootCommand(); //运行需要root权限的命令调用这个函数
execCommand(); //运行普通用户权限的命令调用这个函数
execRootCommandWithResult(); //运行root权限的命令且会返回运行打印信息的
函数写完后编译生成 shellutils.jar
把 shellutils.jar 导入到Delphi工程的Lib库中:
可以直接打开 shellutils.jar 看到里面的函数声明及实现
然后使用老猫 Jar转Pas工具生成Delphi的接口pas文件:
//====================================================
//
// 转换来自JarOrClass2Pas(原JavaClassToDelphiUnit)
// 原始作者:ying32
// QQ: 1444386932、396506155
// Email:yuanfen3287@vip.qq.com
//
// 修改 By:Flying Wang & 爱吃猪头肉
// 请不要移除以上的任何信息。
// 请不要将本版本发到城通网盘,否则死全家。
//
// Email:1765535979@qq.com
// QQ Group:165232328
//
// 生成时间:2025/4/29 20:06:43
// 工具版本:1.0.2018.2.26
//
//====================================================
unit Androidapi.JNI.ShellHelper;
interface
uses
Androidapi.JNIBridge,
Androidapi.JNI.JavaTypes;
type
// ===== Forward declarations =====
JShellHelper = interface; //ShellHelper
// ===== Forward SuperClasses declarations =====
// ===== Interface declarations =====
JShellHelperClass = interface(JObjectClass)
['{A1A50C39-E343-4390-8F51-3EF2038C766E}']
{ static Property Methods }
{ static Methods }
{class} function init: JShellHelper; cdecl;
{class} function execRootCommand(command: JString): Integer; cdecl;
{class} function execCommand(command: JString): Integer; cdecl;
{class} function execRootCommandWithResult(command: JString): JString; cdecl;
{ static Property }
end;
[JavaSignature('ShellHelper')]
JShellHelper = interface(JObject)
['{CF8884FA-055C-4FA2-BB04-57289F79352C}']
{ Property Methods }
{ methods }
{ Property }
end;
TJShellHelper = class(TJavaGenericImport<JShellHelperClass, JShellHelper>) end;
implementation
procedure RegisterTypes;
begin
TRegTypes.RegisterType('Androidapi.JNI.ShellHelper.JShellHelper',
TypeInfo(Androidapi.JNI.ShellHelper.JShellHelper));
end;
initialization
RegisterTypes;
end.
最后再写一下调用代码:
procedure TvgsTrackerMainFrm.Button25Click(Sender: TObject);
var
LCommand: JString;
LExitCode: Integer;
s: string;
begin
try
LCommand := StringToJString('/data/local/seteth1');
LExitCode := TJShellHelper.JavaClass.execRootCommand(LCommand);
// s:=JStringToString(TJShellHelper.JavaClass.execRootCommandWithResult(LCommand));
// ShowMessage(s);
if LExitCode = 0 then
ShowMessage('脚本执行成功')
else
ShowMessage('执行失败,错误码: ' + IntToStr(LExitCode));
except
on E: Exception do
ShowMessage('调用异常: ' + E.Message);
end;
end;
不过这里还漏了ADB的网卡配置脚本,一并贴出来
#!/system/bin/sh
ip addr flush dev eth1
ip addr add 192.168.1.129/24 broadcast 192.168.1.255 dev eth1
ip route add 192.168.1.0/24 dev eth1
ip route add default via 192.168.1.1 dev eth1
ip rule add from 192.168.1.0/24 lookup main
ip rule add to 192.168.1.0/24 lookup main
ip link set eth1 up
OK,就这么简单的搞定了,现在使用起来就非常的爽了,可以同时跨IP网段的拉取2个视频流,也可以从eth0网段推流到eth1网段,真是又趟过一个坑了呀。
欢迎Delphi的老人们一起来交流,加入 Delphi玩转AI (974857430) 群一起来变年轻吧!