The previous recipe adds a GUI to the ported fusch
program with two issues left behind—unresponsiveness of the GUI and no progress update when processing is going on. This recipe discusses how to use a background thread to handle the
processing and report the progress to the main UI thread.
The sample program in this recipe is based on the program we developed in previous recipes of this chapter. You should go through them first. In addition, readers are recommended to reading the following recipes in Chapter 2, Java Native Interface:
The following steps describe how to use a background thread for heavy processing and report progress update to the Java UI thread:
PortingExecutableAUI
project that we developed in the previous recipe to a folder named PortingExecutableAUIAsync
. Open the project in the folder at the Eclipse IDE.MainActivity.java
:handler
: An instance of the handler
class handles the messages sent from background threads. It will update the GUI with the content of the message.
public static final int MSG_TYPE_PROG = 1; public static final int MSG_TYPE_SUCCESS = 2; public static final int MSG_TYPE_FAILURE = 3; Handler handler = new Handler() { @Override public void handleMessage(Message msg) { switch(msg.what) { case MSG_TYPE_PROG: String updateMsg = (String)msg.obj; if (1 == msg.arg1) { String curText = text1.getText().toString(); String newText = curText.substring(0, curText.lastIndexOf(" ")) + " " + updateMsg; text1.setText(newText); } else if (2 == msg.arg1) { text1.append(updateMsg); } else { text1.append(" " + updateMsg); } break; case MSG_TYPE_SUCCESS: Uri uri = Uri.fromFile(new File(outputImageDir + outputImgFileName)); img2.setImageURI(uri); text1.append(" processing done!"); text2.setText(getImageDimension(inputImagePath) + ";" + getImageDimension(outputImageDir + outputImgFileName)); break; case MSG_TYPE_FAILURE: text1.append(" error processing the image"); break; } } };
ImageProcRunnable
: A private class of MainActivity
implements the Runnable
interface, which accepts the command string, calls the native method naMain,
and sends the result message to the handler at the Java UI thread.
An instance of this class will be invoked from a background thread:
private class ImageProcRunnable implements Runnable { String procCmd; public ImageProcRunnable(String cmd) { procCmd = cmd; } @Override public void run() { int res = naMain(procCmd, MainActivity.this); if (0 == res) { //success, send message to handler Message msg = new Message(); msg.what = MSG_TYPE_SUCCESS; handler.sendMessage(msg); } else { //failure, send message to handler Message msg = new Message(); msg.what = MSG_TYPE_FAILURE; handler.sendMessage(msg); } } }
updateProgress
: This is a method to be called from native code through JNI. It sends a message to the handler at the
Java UI thread:
public void updateProgress(String pContent, int pInPlaceUpdate) { Message msg = new Message(); msg.what = MSG_TYPE_PROG; msg.arg1 = pInPlaceUpdate; msg.obj = pContent; handler.sendMessage(msg); }
fusch.c
source code.JavaVM
reference in the naMain
method, and get a global reference for the MainAcitvity
object reference pMainActObj
. The fusch
program uses more than one background thread. We will need these references to call Java methods from those background threads:#ifdef ANDROID_BUILD int naMain(JNIEnv* env, jobject pObj, jstring pCmdStr, jobject pMainActObj); jint JNI_OnLoad(JavaVM* pVm, void* reserved) { JNIEnv* env; if ((*pVm)->GetEnv(pVm, (void **)&env, JNI_VERSION_1_6) != JNI_OK) { return -1; } cachedJvm = pVm; JNINativeMethod nm[1]; nm[0].name = "naMain"; nm[0].signature = "(Ljava/lang/String;Lcookbook/chapter9/portingexecutableaui/MainActivity;)I"; nm[0].fnPtr = (void*)naMain; jclass cls = (*env)->FindClass(env, "cookbook/chapter9/portingexecutableaui/MainActivity"); (*env)->RegisterNatives(env, cls, nm, 1); return JNI_VERSION_1_6; } int naMain(JNIEnv* env, jobject pObj, jstring pCmdStr, jobject pMainActObj) { char progBuf[500]; jmethodID updateProgMID, toStringMID; jstring progStr; jclass mainActivityClass = (*env)->GetObjectClass(env, pMainActObj); cachedMainActObj = (*env)->NewGlobalRef(env, pMainActObj); updateProgMID = (*env)->GetMethodID(env, mainActivityClass, "updateProgress", "(Ljava/lang/String;I)V"); if (NULL == updateProgMID) { LOGE(1, "error finding method updateProgress"); return EXIT_FAILURE; } int argc = 0; char** argv = (char**) malloc (sizeof(char*)*4); *argv = "fusch"; char** targv = argv + 1; argc++; jboolean isCopy = JNI_TRUE; char *cmdstr = (*env)->GetStringUTFChars(env, pCmdStr, &isCopy); if (NULL == cmdstr) { LOGI(2, "get string failed"); return EXIT_FAILURE; } char* pch; pch = strtok(cmdstr, " "); while (NULL != pch) { *targv = pch; argc++; targv++; pch = strtok(NULL, " "); } LOGI(1, "No. of arguments: %d", argc); LOGI(1, "%s %s %s %s", argv[0], argv[1], argv[2], argv[3]); #else int main(int argc, char *argv[]) { #endif
return
statement of the main
method to release the native string and the cached JavaVM reference to avoid memory leaks:#ifdef ANDROID_BUILD (*env)->ReleaseStringUTFChars(env, pCmdStr, cmdstr); (*env)->DeleteGlobalRef(env, cachedMainActObj); cachedMainActObj = NULL; #endif
#ifdef ANDROID_BUILD progStr = (*env)->NewStringUTF(env, MSG[I_NOTHINGTODO]); (*env)->CallVoidMethod(env, pMainActObj, updateProgMID, progStr, 0); #else puts(MSG[I_NOTHINGTODO]); #endif
seam_progress
and carve_progress
functions are executed by native threads started at naMain
. We used the cached JavaVM
reference cachedJvm
and MainActivity
object reference cachedMainActObj
to get jmethodID
of the updateProgress
method defined at MainActivity.java
:#ifdef ANDROID_BUILD char progBuf[500]; JNIEnv *env; jmethodID updateProgMID; (*cachedJvm)->AttachCurrentThread(cachedJvm, &env, NULL); jstring progStr; jclass mainActivityClass = (*env)->GetObjectClass(env, cachedMainActObj); updateProgMID = (*env)->GetMethodID(env, mainActivityClass, "updateProgress", "(Ljava/lang/String;I)V"); if (NULL == updateProgMID) { LOGE(1, "error finding method updateProgress at seam_progress"); (*cachedJvm)->DetachCurrentThread(cachedJvm); pthread_exit((void*)NULL); } #endif
updateProgress
method from seam_progress
and carve_progress
. This is shown in the code section extracted from the carve_progress
function, as follows:#ifdef ANDROID_BUILD sprintf(progBuf, "%6d %6d %3d%%", max, pro, lrintf((float)(pro * 100) / max)); progStr = (*env)->NewStringUTF(env, progBuf); (*env)->CallVoidMethod(env, cachedMainActObj, updateProgMID, progStr, 1); #else printf("%6d %3d%% ", pro, lrintf((float)(pro * 100) / max)); #endif
The preceding example shows how to use a background thread to handle heavy processing, so that the GUI can remain responsive to user inputs. While the background thread is processing the images, it also sends progress updates to the UI thread.
The details of the
fusch
program are actually a bit more complicated than the core idea described, because it uses heavy concurrent processing. This is illustrated in the following diagram:
Once we click on either the Width or Height button in MainActivity.java
, a new Java thread (Background Thread 1) will be created with an instance of the ImageProcRunnable
. This thread will invoke the naMain
native method.
Multiple native threads are created with the pthread_create
function in the naMain
method. Two of them, indicated as Background Thread 2 and Background Thread 3, will be running
seam_progress
and
carve_progress
respectively.
We send messages of the MSG_TYPE_PROG
type to the handler bound to the UI thread in all the three background threads. The handler will process the messages and update the GUI.
Sending messages to a handler in Java is straightforward; we simply call the handler.sendMessage()
method. But things can be a bit
troublesome in the native code.
We defined an updateProgress
method in MainActivity.java
, which accepts a string and an integer, constructs a message, and sends it to the handler. The native code invokes this Java method through JNI in order to send messages. There are two situations:
naMain
native method. At naMain
, we retrieve jmethodID
for updateProgress
, and call the updateProgress
method through the JNI function CallVoidMethod
. You can refer back to the Calling static and instance methods from native code recipe in Chapter 2, Java Native Interface for more information.naMain
by the pthread_create
function. We must call AttachCurrentThread
to attach the native threads to a Java VM before we can make any JNI calls. Note that we used the cached MainActivity
object reference cachedMainActObj
for calling the updateProgress
method. For more details about caching at JNI, we can refer to the Caching jfieldID, jmethodID, and reference data to improve performance recipe in Chapter 2, Java Native Interface.The GUI we have created doesn't look all that good, but it is simple and enough to illustrate how to use a background thread for heavy processing and to send out GUI update messages from the native code.