Tuesday, February 1, 2011

Android: using unix pipes for tracking progress of native code execution

One of my projects required tracking of how much code is executed at the moment. The situation was complicated by the fact that for a number of reasons some pieces of code were being executed in a few separate processes in turn.

There was three options to choose from:

  • IPC-calls from native code
  • logcat parsing
  • passing data through pipes

The last choice seemed best. Pipe is a common communication channel that may be used for one-way interprocess communication. Pipe has a file interface so we can handle it as it was an usual file. The main point of pipes is that we cannot write into it until someone start to read from this pipe and conversely, reading call blocks if no one writes into it. Another benefit is that several processes can write into a single pipe simultaneously (no synchronization mechanism needed).

Here you can find a demo project. It starts a service that writes into a pipe from native code. Also there is a reading thread that can read data from the pipe and display it.


Writing thread. It emulates a long running piece of code publishing its own progress. Below you can see the native implementation of "process" method.


    final String pipename = intent.getStringExtra("fn");
    new Thread(new Runnable() {
      
      @Override
      public void run() {
        process(pipename);
      }
    }).start();


JNIEXPORT jint JNICALL Java_ru_jecklandin_cats_ProcessingService_process
 (JNIEnv * env, jobject, jstring path) {
  
  const char* cpath = env->GetStringUTFChars(path, NULL);
  struct stat buf;
  
  if ( stat(cpath, &buf) < 0 || ! (buf.st_mode | S_IFIFO)) {
    LOGD("The file isn't a pipe");
    return -1;
  } 
  
  int fp = open(cpath, O_WRONLY);
  if(fp == -1) {
    LOGD("Could not open the pipe");
    return -1;
  }
   
  for (int i=0; i<10; ++i) {
    sleep(1);
    write(fp, &i, 1);
  }
  close(fp);
  env->ReleaseStringUTFChars(path, cpath);
  return 0;
}


Reading thread. Creates a new pipe and starts to read from it as if it was a simple file. "read" call blocks until someone on the other side of the pipe starts to write data into it.

 


    final TextView disp = new TextView(this);
    disp.setText("0");
    setContentView(disp);
    
    final String pipename = getDir("pipedemo", Context.MODE_WORLD_WRITEABLE)
     .getAbsolutePath() + "/pipe";
    final File pipe = new File(pipename);
    
    new AsyncTask<Void, Integer, Integer>() {

     @Override
     protected void onProgressUpdate(Integer... values) {
      disp.setText(""+values[0]);
     };
     
     protected Integer doInBackground(Void... params) {
      
      //create a pipe
      if (mkfifo(pipename) == -1) {
       Log.d(TAG, "Pipe error");
       return -1;
      }
      
      FileInputStream fis;
      try {
       fis = new FileInputStream(pipe);
       int res = 0;
       while (res != -1) { //blocks until someone writes to this pipe
        res = fis.read();
        publishProgress(res);
       }
      } catch (Exception e) {
       e.printStackTrace();
      }
      return 0;
     }
    }.execute();


* This source code was highlighted with Source Code Highlighter.

Warning 1: Despite our code might be run into a separate process, always launch long-running pieces of code in separate threads. Otherwise, the system can kill the hosting process without notice.

Warning 2. Don't create pipes on sdcard - FAT32 doesn't support them.





Add "process" attribute to our service in AndroidManifest.xml to make sure it works in a separate process.

<service android:name=".ProcessingService"
      android:process=":remote">
    </service>


DDMS displays both processes
image

When you compile and run the project, there will be an increasing number on the screen (not very eye-catching, I know).

If you need a duplex channel, just create one more pipe. Sure, this solution isn't a silver bullet. If there is a need to pass complicated objects between processes, you should consider using standard Android IPC tools (I mean aidl).

Useful references:
developers.sun.com/solaris/articles/named_pipes.html
developer.android.com/guide/developing/tools/aidl.html

 

No comments: