Process实现静默升级

  当你没跨过一个问题的时候,这个问题就会一直跟着你。

  在安卓中我们可以使用Process来执行名命令行命令,我们来看看如何使用。

  首先是实例化,可以通过Runtime.getRuntime().exec(cmd)获取到Process,exec中写入我们需要执行的命令行即可。最简单的示例如下:

public static int execCmd(String cmd) { 
        int result = -1; 
        DataOutputStream dos = null; 
         
        try { 
            Process p = Runtime.getRuntime().exec("su"); 
            dos = new DataOutputStream(p.getOutputStream()); 
            dos.writeBytes(cmd + "\n"); 
            dos.flush(); 
            dos.writeBytes("exit\n"); 
            dos.flush(); 
            p.waitFor(); 
            result = p.exitValue(); 
        } catch (Exception e) { 
            e.printStackTrace(); 
        } finally { 
            if (dos != null) { 
                try { 
                    dos.close(); 
                } catch (IOException e) { 
                    e.printStackTrace(); 
                } 
            } 
        } 
        return result; 
    } 
} 

  这里的su表示切换用户身份,以达到root的权限,后面调用dos.writeBytes(cmd + "\n"); 执行命令行,这里我们需要注意的方法是dos.flush(); 和p.waitFor(); 和p.exitValue(); 。
  dos.flush();的作用是刷新缓冲区的信息
  p.waitFor();的作用是等待子进程完成
  p.exitValue();是获取命令行执行的结果,0 表示正确执行了。
  尤其的是p.waitFor();会导致线程挂起,所以我们执行命令行的代码一般要写在子线程中,这里看下线程挂起是什么一个过程。

   主进程中调用Runtime.exec会创建一个子进程,用于执行shell脚本。子进程创建后会和主进程分别独立运行。
  因为主进程需要等待脚本执行完成,然后对脚本返回值或输出进行处理,所以这里主进程调用Process.waitfor等待子进程完成。
   通过shell脚本可以看出:子进程执行过程就是不断的打印信息。主进程中可以通过Process.getInputStream和Process.getErrorStream获取并处理。
  这时候子进程不断向主进程发生数据,而主进程调用Process.waitfor后已挂起。当前子进程和主进程之间的缓冲区塞满后,子进程不能继续写数据,然后也会挂起。
  这样子进程等待主进程读取数据,主进程等待子进程结束,两个进程相互等待,最终导致死锁。

  还有一点就是result = p.exitValue(); 必须要写在p.waitFor(); 的后面,因为二者并不是线性执行的,如果放在之前调用,没有等命令行执行完成就调用,可能会报错:process has not exited。

  为了解决线程缓冲区阻塞导致线程挂起,我们可以不断处理缓冲区中的数据,避免阻塞,比如我们看一下在root权限下实现静默升级功能的代码,如下:

    public void installAppWithRoot(String path) throws Exception {
        Process process = Runtime.getRuntime().exec("su");
        DataOutputStream dos = new DataOutputStream(process.getOutputStream());
        dos.writeBytes("pm install -r " + path + "\n");
        dos.flush();
        dos.writeBytes("exit" + "\n");
        dos.flush();
        BufferedReader errorStream = null;
        errorStream = new BufferedReader(new InputStreamReader(process.getErrorStream()));
        StringBuilder errorMsg = new StringBuilder();
        String line;
        while ((line = errorStream.readLine()) != null) {
            errorMsg.append(line);
        }
        Log.d("apk安装错误信息", String.valueOf(errorMsg));
        BufferedReader successStream = null;
        StringBuilder successMsg = new StringBuilder();
        successStream = new BufferedReader(new InputStreamReader(process.getInputStream()));
        // 读取命令执行结果
        while ((line = successStream.readLine()) != null) {
            successMsg.append(line);
        }
        Log.d("apk安装正确信息", String.valueOf(successMsg));
        process.waitFor();
        int result = process.exitValue();
        Log.d("apk安装返回信息", result + "");
    }

  及时的处理缓冲区中的信息,打印出命令的输出流和错误信息,并保证了线程的正常执行。

推荐阅读更多精彩内容

  • 进程 操作系统背景知识 顾名思义,进程即正在执行的一个过程。进程是对正在运行程序的一个抽象。 进程的概念起源于操作...
    go以恒阅读 501评论 0 2
  • 官网 中文版本 好的网站 Content-type: text/htmlBASH Section: User ...
    不排版阅读 2,892评论 0 5
  • 1. 硬链接和软连接区别 硬连接-------指通过索引节点来进行连接。在Linux的文件系统中,保存在磁盘分区...
    杰伦哎呦哎呦阅读 918评论 0 2
  • 1. 简介 用户打开浏览器,其实就是打开了浏览器应用程序。那么什么是程序呢?我们常说浏览器是多线程的,JS 是单线...
    love丁酥酥阅读 2,200评论 0 5
  • 顾名思义,进程即正在执行的一个过程。进程是对正在运行程序的一个抽象。进程的概念起源于操作系统,是操作系统最核心的概...
    SlashBoyMr_wang阅读 339评论 0 2