Using background threads at porting

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.

Getting ready

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:

  • Calling static and instance methods from the native code
  • Caching jfieldID, jmethodID, and reference data to improve performance

How to do it...

The following steps describe how to use a background thread for heavy processing and report progress update to the Java UI thread:

  1. Copy the PortingExecutableAUI project that we developed in the previous recipe to a folder named PortingExecutableAUIAsync. Open the project in the folder at the Eclipse IDE.
  2. Add the following code to 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);
    }
  3. Update the fusch.c source code.
  4. We cache the 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
  5. Add the following lines before the 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
  6. To update the GUI, we send out a message to the Java code. We need to update the code used to produce output messages at various parts of the source file. The following is an example of this:
    #ifdef ANDROID_BUILD
      progStr = (*env)->NewStringUTF(env, MSG[I_NOTHINGTODO]);
      (*env)->CallVoidMethod(env, pMainActObj, updateProgMID, progStr, 0);
    #else
      puts(MSG[I_NOTHINGTODO]);
    #endif
  7. The 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
  8. We can then call the 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
  9. Build and run the Android app. You should be able to see a GUI similar to the following screenshot:
    How to do it...
  10. We can hit the Width or Height button to start the processing. The left and middle screenshots show the processing in progress, while the right screenshot shows the results:

    How to do it...

How it works...

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:

How it works...

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 from the native code

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:

  • Native code at Java thread: This is the case for Background Thread 1 in the previous diagram. The thread is created at Java code, and it calls the 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.
  • Native code at native thread: This is what happens at Background Thread 2 and Background Thread 3. These threads are created at 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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset