Using unix pipes to display native code progress on Android

    In one of the projects, I needed to track the progress of the native code on Android (specifically, ported FFmpeg). The situation was also complicated by the fact that for several reasons the code was executed in several processes.


    Among the options considered were the following:
    • IPC calls from the code
    • logcat parsing
    • data transfer through pipes

    The most suitable option was the latter - the use of named pipes, a standard tool for interprocess interactions in unix systems (for those unfamiliar with it, you can read for example here ). In short, it is a one-way FIFO channel with a file interface. When writing, it is blocked until another process opens it for reading and vice versa - when reading, the channel is blocked again until another process writes data to it. A big plus is that several processes can write to a pipe at the same time.

    Here lies a test project demonstrating the idea. It launches a service that writes to pipe from native code, as well as a reader thread that displays the result on the screen.

    Stream reader. Creates a new pipe with the mkfifo function (it's just a wrapper over the system call of the same name) and reads bytes from it, like from a regular file. The read call blocks execution until someone on the other side of the pipe starts writing data. It uses the convenient AsyncTask class, which allows you to execute a function in the background and publish intermediate results to a UI stream.

        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() {

          @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.


    Stream-writer . We launch it in a separate service. It performs a long-playing function, writing progress in this very pipe.

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

    * This source code was highlighted with Source Code Highlighter.


    Vorning 1 : Although the code can be executed in a separate process, run long operations in a separate thread, otherwise the system will kill the process without warning.

    Warning 2 : Do not create pipes on sdcard - FAT32 does not support them.

    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;
    }

    * This source code was highlighted with Source Code Highlighter.


    Add the “process” attribute to our service in the manifest.

          android:process=":remote">
        


    * This source code was highlighted with Source Code Highlighter.

    In DDMS we see both processes.

    image

    We start and see an increasing digit on the screen (not very impressive).

    In the considered way, it is possible to organize duplex communication (having one more pipe). Of course, the solution is not suitable for all cases. If you need to transfer objects, then it is better to use the standard Android IPC mechanism, which allows you to transfer serialized objects between processes.

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

    Also popular now: