In-Depth Analysis of Executing Shell Commands from Java in Android: A Case Study on Screen Recording

Dec 07, 2025 · Programming · 16 views · 7.8

Keywords: Android | Java | Shell Commands | Root Privileges | Process Communication

Abstract: This article delves into the technical details of executing Shell commands from Java code in Android applications, particularly in scenarios requiring root privileges. Using the screenrecord command in Android KitKat as an example, it analyzes why direct use of Runtime.exec() fails and provides a solution based on the best answer: passing commands through the output stream of the su process. The article explains process permissions, input/output stream handling, and error mechanisms in detail, while referencing other answers to supplement with generic function encapsulation and result capture methods, offering a comprehensive technical guide for developers.

In Android development, there are scenarios where executing Shell commands from within an application is necessary to achieve specific functionalities, such as using the screenrecord command introduced in Android KitKat for screen recording. However, directly invoking these commands from Java code can encounter permission issues, leading to operation failures. This article uses a concrete case to deeply analyze the root cause and provide effective solutions.

Problem Background and Initial Attempt

A developer attempted to execute the screenrecord command on a rooted Android KitKat device. Manually entering su followed by screenrecord --time-limit 10 /sdcard/MyVideo.mp4 in a terminal emulator successfully creates the video file. But when using the following approach in Java code, the file is not created:

Process su = Runtime.getRuntime().exec("su");
Process execute = Runtime.getRuntime().exec("screenrecord --time-limit 10 /sdcard/MyVideo.mp4");

The fundamental issue with this method is that the two Process instances are independent; the root privileges obtained by the first su process are not passed to the second process executing screenrecord. In reality, the second process runs with the current application's user identifier (UID), lacking the necessary permissions.

Core Solution: Passing Commands Through the su Process

The correct approach is to write the command into the standard input stream of the su process, allowing it to execute under root privileges. Here is an improved code example based on the best answer:

try {
    Process su = Runtime.getRuntime().exec("su");
    DataOutputStream outputStream = new DataOutputStream(su.getOutputStream());

    outputStream.writeBytes("screenrecord --time-limit 10 /sdcard/MyVideo.mp4\n");
    outputStream.flush();

    outputStream.writeBytes("exit\n");
    outputStream.flush();
    su.waitFor();
} catch (IOException e) {
    throw new Exception(e);
} catch (InterruptedException e) {
    throw new Exception(e);
}

The key to this code lies in using DataOutputStream to write the command string to the output stream of the su process, with a newline character \n simulating the enter key press in a terminal. flush() ensures the command is sent immediately, while waitFor() causes the current thread to wait for the su process to finish, avoiding resource contention. Finally, writing the exit command safely exits the su shell.

Extension and Optimization: Generic Function Encapsulation

Referencing other answers, we can encapsulate the above logic into reusable functions to enhance code modularity and maintainability. Here is a generic method supporting multiple commands:

public static void sudo(String... strings) {
    try {
        Process su = Runtime.getRuntime().exec("su");
        DataOutputStream outputStream = new DataOutputStream(su.getOutputStream());

        for (String s : strings) {
            outputStream.writeBytes(s + "\n");
            outputStream.flush();
        }

        outputStream.writeBytes("exit\n");
        outputStream.flush();
        try {
            su.waitFor();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        outputStream.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

Usage example: creating a directory if needed:

private static void suMkdirs(String path) {
    if (!new File(path).isDirectory()) {
        sudo("mkdir -p " + path);
    }
}

Advanced Handling: Capturing Command Output

In some scenarios, we need to retrieve the execution results of Shell commands. This can be achieved by reading the process's input stream:

public static String sudoForResult(String... strings) {
    String res = "";
    DataOutputStream outputStream = null;
    InputStream response = null;
    try {
        Process su = Runtime.getRuntime().exec("su");
        outputStream = new DataOutputStream(su.getOutputStream());
        response = su.getInputStream();

        for (String s : strings) {
            outputStream.writeBytes(s + "\n");
            outputStream.flush();
        }

        outputStream.writeBytes("exit\n");
        outputStream.flush();
        try {
            su.waitFor();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        res = readFully(response);
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        Closer.closeSilently(outputStream, response);
    }
    return res;
}

public static String readFully(InputStream is) throws IOException {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    byte[] buffer = new byte[1024];
    int length = 0;
    while ((length = is.read(buffer)) != -1) {
        baos.write(buffer, 0, length);
    }
    return baos.toString("UTF-8");
}

The utility class Closer is used to safely close resources, handling objects that may not implement the Closeable interface (e.g., Socket before Android 19).

Conclusion and Best Practices

When executing Shell commands from Java in Android, special attention must be paid to permission management and inter-process communication. Key points include: using the su process to obtain root privileges, passing commands through output streams, and properly handling input/output streams to avoid blocking or resource leaks. The code examples provided in this article are optimized and can be directly applied to real-world projects, but developers should adjust error handling and resource management strategies based on specific needs. Additionally, since root operations involve system security, it is recommended to use them only in necessary scenarios and ensure user consent.

Copyright Notice: All rights in this article are reserved by the operators of DevGex. Reasonable sharing and citation are welcome; any reproduction, excerpting, or re-publication without prior permission is prohibited.