一、背景說明
在 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 命令的場景還是比較多的,需要根據不同的需求選用合適的函數進行調用,通常來說選擇有返回值的函數進行使用即可。
在這裏可能還包含了超時命令,暫時沒做處理,有需求,再繼續分析實現吧。
以上。