Android O 获取Runtime.getRuntime().exec生成的进程号PID并模拟adb shell的Ctrl+C 操作

背景:公司的手持设备,做了两个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命令的相关参数可以上网查询。

那么第一步,我们要先拿到tinycappid

尝试了几种方法:

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;
}

链接:https://stackoverflow.com/questions/13055794/android-runtime-getruntime-exec-get-process-id

虽然用的字段是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;
}

实测可行。success

第二步: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播放不了。




你可能感兴趣的:(Android O 获取Runtime.getRuntime().exec生成的进程号PID并模拟adb shell的Ctrl+C 操作)