背景:公司的手持设备,做了两个mic。写工厂测试apk的同事写测试代码的时候就没有用上层的AudioMedia或MediaRecorder的相关api去做。而是用的Runtime.getRuntime().exec(...)去执行tinymix,tinycap指令来进行测试。
录音过程:先用tinymix打开相关流,然后tinycap开始录音。然后tinymix关闭相关流,tinycap会自动停掉。
Bug:tinymix关闭相关流后,tinycap从响应到自动停止要很久,10s以上。这显然难以接受
解决思路:因为adb shell后,执行tinymix打开相关流,然后tinycap开始录音,然后ctrl+c,结束录音,录音文件是ok的,可以播放。那么我们的解决思路是去提前停止tinycap所对应的进程(Runtime.getRuntime().exec执行这行命令时产生的进程)
尝试:直接拿到这个进程,Process=Runtime.getRuntime().exec,p.destroy().失败了,虽然tinycap停止了,但是生成的wav音频文件播放不了。
这就说明,ctrl+c这个操作实际并不是强杀进程。上网查询了解到ctrl+c实际发送了SIGINT这个信号。那么我们尝试着去模拟这个信号,用kill命令可以。kill-2 pid。注意是-2,正好对应SIGINT,kill命令的相关参数可以上网查询。
那么第一步,我们要先拿到tinycap的pid。
尝试了几种方法:
1.反射,stackoverflow上找到的 fail
public static int getPid(Process p) {
int pid = -1;
try {
Field f = p.getClass().getDeclaredField("pid");
f.setAccessible(true);
pid = f.getInt(p);
f.setAccessible(false);
} catch (Throwable e) {
pid = -1;
}
return pid;
}
虽然用的字段是pid,但是实测实际获取的却是ppid,绝望。java.lang.Process这个类往上追,追到ProcessImpl再到UNIXProcess.除了pid这个看起来有用的字段,没有别的可以获取pid的字段了。所以fail
2.用RunningTaskInfo fail
实测机器是8.0上只能拿到systemui,launcher和当前应用。所以fail
3.用RunningAppProcessInfo fail
只要AndroidManifest申明权限android.permission.REAL_GET_TASKS,并且是系统应用,就可以拿到所有应用的pid。
但是注意了,是应用。都是类似com.android.settings的进程,但是tinycap这种Runtime.getRuntime().exec生成的进程不在此列。所以fail
4.模拟ps |grep tinycap去获取pid success
public String getTinyCapPID() {
java.lang.Process psProcess = null;
try {
psProcess = Runtime.getRuntime().exec("sh");
} catch (IOException e) {
e.printStackTrace();
}
DataOutputStream out = new DataOutputStream(psProcess.getOutputStream());
InputStream is = psProcess.getInputStream();
try {
out.writeBytes("ps | grep 'tinycap' | cut -c 10-15\n");
out.writeBytes("ps\n");
out.flush();
} catch (IOException e) {
e.printStackTrace();
}
try {
out.writeBytes("exit\n");
out.flush();
psProcess.waitFor();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
String re="";
try {
if (is.read() != 0) {
byte firstByte = (byte) is.read();
int available = is.available();
byte[] characters = new byte[available + 1];
characters[0] = firstByte;
is.read(characters, 1, available);
re = new String(characters);
}
} catch (IOException e) {
e.printStackTrace();
}
return re;
}
第二步:kill -2 pid
Process p=Runtime.getRuntime().exec("kill -2 " + getTinyCapPID());
这里如果后面要执行别的东西,比如tinyplay。为了确保tinycap被杀死后才执行tinyplay。先p.waitFor()一下。我自己这边测试即使p.waiFor()保证kill -2 pid这条命令执行完,但是tinycap这个进程在小概率情况下不会马上被kill掉(kill -2后面还做了收尾工作)。
那么
Process p2=Runtime.getRuntime().exec("tinycap /data/Main.wav");
p2.waitFor()后再执行tinyplay。
大功告成!
PS:用Process p=Runtime.getRuntime().exec(xxx)来执行命令,从保险的角度,一定要用上p.waitFor()。个人实践的教训是一定要用p.waitFor()等命令执行完(命令执行完后,执行这个命令的进程没了后,这行代码才走完,否则一直阻塞)。再去做下一步相关的事情。
如果不用p.waiFor(),默认系统只会给很小一段时间去执行你的命令。很多情况下小概率会命令执行不到位。出现非必现的bug。比如笔者上面的Runtime.getRuntime().exec("ps | grep 'tinycap' | cut -c 10-15")如果没有后面的p.waitFor().实际运行过程中小概率会拿不到正确的pid,导致没杀对tinycap所在的进程,进一步后面tinyplay播放不了。