Process Example

This example shows how to run a separate process to perform a command.

A screenshot of the application.

We import the classes that will be needed by the application. The most relevant to this example is the ProcessBuilder class.

from java.lang import ProcessBuilder, String
from android.os import AsyncTask
from android.view import View
from android.widget import Button, ScrollView, TextView
from serpentine.activities import Activity
from serpentine.widgets import VBox

We define a class based on a custom Activity class provided by the serpentine package. This represents the application, and will be used to present a graphical interface to the user.

The initialisation method simply calls the corresponding method in the base class. This must be done even if no other code is included in the method.

class ProcessActivity(Activity):

    __interfaces__ = [View.OnClickListener]
    
    def __init__(self):
    
        Activity.__init__(self)

The onCreate method is called when the activity is created by Android. As with the __init__ method, we must call the corresponding method in the base class. We use this method to set up the user interface, registering a listener for a button that the user can press to run a process.

    def onCreate(self, bundle):
    
        Activity.onCreate(self, bundle)
        
        label = TextView(self)
        label.setText("ls -R /system")
        
        self.button = Button(self)
        self.button.setText("Run process")
        self.button.setOnClickListener(self)
        
        self.view = TextView(self)
        scrollView = ScrollView(self)
        scrollView.addView(self.view)
        
        layout = VBox(self)
        layout.addView(label)
        layout.addView(self.button)
        layout.addView(scrollView)
        
        self.setContentView(layout)

In the following method we respond to the button click by disabling the button, so that the user can only start a single process, and adding a message to the TextView that will contain the output of the process.

    def onClick(self, view):
    
        self.button.setEnabled(False)
        self.view.setText("Processing...")
        
        l = array(["ls", "-R", "/system"])
        self.task = Task(self)
        self.task.execute(l)

We finish by creating a string array that we pass to the execute method of a custom AsyncTask object.

When the process finishes the Task object calls the following method to publish the result. Here, we enable the button again and show the string passed to the method in the TextView.

    @args(void, [String])
    def setResult(self, value):
    
        self.button.setEnabled(True)
        self.view.setText(value)

We define a Task class based on the standard AsyncTask class to monitor the background process. This defines three item types that describe the three parameters of the class: Params, Progress and Result.

class Task(AsyncTask):

    __item_types__ = [String, int, String]

For convenience the initialisation method accepts a reference to the activity itself, storing it for later reference, and calls the base class method as normal.

    @args(void, [ProcessActivity])
    def __init__(self, parent):
    
        AsyncTask.__init__(self)
        self.parent = parent

When the task's execute method is called, the following method is called in a background thread. Here, we create a background process using the array of strings supplied in the array of Params objects - Params is a String in this case - and collect the output of the process in a list of bytes.

    @args(Result, [[Params]])
    def doInBackground(self, params):
    
        builder = ProcessBuilder(params)
        builder.redirectErrorStream(True)
        
        process = builder.start()
        input_stream = process.getInputStream()
        input_bytes = []
        
        try:
            while True:
                v = input_stream.read()
                if v == -1:
                    break
                input_bytes.add(byte(v))
        except:
            pass
        
        process.waitFor()
        
        return String(array(input_bytes), "ASCII")

After the process has sent all its output, we wait for it to exit before converting it to a string which we return. This value of type Result is delivered back to the main thread and sent to the following method. Result is a String in this case.

    @args(void, [Result])
    def onPostExecute(self, result):
    
        self.parent.setResult(result)

The method simply calls the activity's setResult method which displays the string in a TextView. This is possible because the onPostExecute method is itself run in the main thread.

Files