This example shows how to run a separate process to perform a command.
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.