一、背景说明
在 apk 开发过程中,难免遇到需要使用 apk 来执行相应的 shell 命令,本文档就是记录在 apk 内部如何执行 shell 命令。
二、准备知识
在使用 shell 命令之前,我们先了解一下 Android 进程基本信息。

如上图,可以看到当前进程空间内,存在好多进程分别以不同的用户权限在执行。除了常见的系统用户,还有 wifi 用户,nfc 用户等,其中我们比较关注的在于 u0_axxx 。关于此部分解释参考:关于android UID u0_axx是怎么来的
由于在 Android 系统中,单个 APK 都是一个独立的进程,因此,在 apk 内部执行 shell 命令,实际是以当前 apk 所在的进程用户权限去执行对应的命令。到这里就解释了执行命令的权限分配问题。
二、技术说明
1、基础用法
通过调用当前apk的运行时对象来执行 shell 命令:
- Runtime.getRuntime(). exec( "ls");
运行之后,在进程中会随机创建一个新用户,然后以新用户的方式来执行 shell 命令。
上述代码有个缺点就是执行之后,无法获取返回值。下面进行改进。
2、执行命令获取返回值
上述命令有缺陷,现在进行改进,使其执行后可以获取返回值。
- public static String runCmd( String shell) {
- String data = null;
- try {
- Process p = Runtime.getRuntime().exec( new String[]{ "su", shell});
- BufferedReader ie = new BufferedReader( new InputStreamReader(p.getErrorStream()));
- BufferedReader in = new BufferedReader( new InputStreamReader(p.getInputStream()));
- String error = null;
- while ((error = ie.readLine()) != null
- && !error.equals( "null")) {
- data += error + "\n";
- }
- String line = null;
- while ((line = in.readLine()) != null
- && !line.equals( "null")) {
- data += line + "\n";
- }
- Log.v( "cmd-test = " + shell, data);
- } catch (Exception e) {
- e.printStackTrace();
- }
- return data;
- }
3、apk 申请 root 权限
普通用法介绍完毕,还存在特殊情况,当我们需要以 root 命令来执行 shell 命令,和上面的代码有点区别的。
首先需要 apk 申请 root 权限:
- public static boolean RootCommand(String command){
- Process process = null;
- DataOutputStream os = null;
- try {
- process = Runtime.getRuntime().exec( "su");
- os = new DataOutputStream(process.getOutputStream());
- os.writeBytes(command + "\n");
- os.writeBytes( "exit\n");
- os.flush();
- process.waitFor();
- } catch (Exception e) {
- return false;
- } finally {
- try {
- if (os != null) {
- os.close();
- }
- process.destroy();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- return true;
- }
调用方式:
- String apkRoot = "chmod 777 " + getPackageCodePath();
- RootCommand(apkRoot);
执行以上代码之后,会弹出窗口提示申请 root 权限呢。允许之后当前 apk 就获取了 root 权限了。
至于检测是否获取到了 root 权限,就不细说了,提供一个小思路,执行 :ll /data/data,看看返回值是什么。
4、apk 以 root 权限执行 shell 命令
和普通命令有点区别的是,root 执行的时候需要在真正命令之前使用 su 去换到 root 用户,然后才开始执行 shell 命令的。
代码如下:
- public static void runRootCommand(String command){
- Process process = null;
- DataOutputStream os = null;
- try {
- process = Runtime.getRuntime().exec( "su");
- os = new DataOutputStream(process.getOutputStream());
- os.writeBytes(command + "\n");
- os.writeBytes( "exit\n");
- os.flush();
- process.waitFor();
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- try {
- if (os != null) {
- os.close();
- }
- process.destroy();
- } catch (Exception e) {
- }
- }
- }
以上代码依然没有返回值,只适合执行简单的shell命令。
5、apk 以 root 权限执行 shell 有返回值
修改以上代码,添加返回值:
- public static String execRootCmd( String cmd) {
- String result = "";
- DataOutputStream dos = null;
- DataInputStream dis = null;
- try {
- Process p = Runtime.getRuntime().exec( "su"); // 经过Root处理的android系统即有su命令
- dos = new DataOutputStream(p.getOutputStream());
- dis = new DataInputStream(p.getInputStream());
- dos.writeBytes(cmd + "\n");
- dos.flush();
- dos.writeBytes( "exit\n");
- dos.flush();
- String line = null;
- while ((line = dis.readLine()) != null) {
- Log.d( "result", line);
- result += line;
- }
- p.waitFor();
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- if (dos != null) {
- try {
- dos.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- if (dis != null) {
- try {
- dis.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- return result;
- }
三、小结
apk 执行 shell 命令的场景还是比较多的,需要根据不同的需求选用合适的函数进行调用,通常来说选择有返回值的函数进行使用即可。
在这里可能还包含了超时命令,暂时没做处理,有需求,再继续分析实现吧。
以上。