Pull to refresh

Использование unix pipes для отображения прогресса выполнения нативного кода на Android'е

Development for Android *
В одном из проектов мне понадобилось отслеживать прогресс выполнения нативного кода на Android'е (конкретно — портированного FFmpeg'а). Ситуация осложнялась также тем, что по ряду причин код выполнялся в нескольких процессах.


Среди рассмотренных вариантов были следующие:
  • IPC-вызовы из сишного кода
  • парсинг logcat'а
  • передача данных через pipes

Наиболее подходящим вариантом оказался последний — использование именованных каналов, стандартного средства для межпроцессных взаимодействий в unix-системах (для незнакомых с ним, почитать можно например здесь). Вкратце — это однонаправленный FIFO-канал с файловым интерфейсом. При записи он блокируется до тех пор, пока другой процесс не откроет его для чтения и наоборот — при чтении канал снова блокируется, пока другой процесс не запишет в него данные. Большим плюсом является то, что одновременно писать в пайп может сразу несколько процессов.

Тут лежит тестовый проект, демонстрирующий идею. В нем запускается сервис, пишущий в пайп из нативного кода, а также поток-читатель, выводящий результат на экран.

Поток-reader. Создает новый пайп функцией mkfifo (это просто обертка над одноименным системным вызовом) и читает байты из него, как из обычного файла. Вызов read блокирует выполнение до тех пор, пока кто-то с другой стороны пайпа не начнет писать данные. Здесь используется удобный класс AsyncTask, который позволяет выполнять функцию в бэкграунде и публиковать промежуточные результаты в UI-поток.

    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.


Поток-writer. Запускаем его в отдельном сервисе. Он выполняет долгоиграющую функцию, пишущую прогресс выполнения в этот самый пайп.

    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.


Ворнинг 1: Несмотря на то, что код может выполняться в отдельном процессе, запускайте долгие операции в отдельном потоке, иначе система убьет процесс без предупреждения.

Ворнинг 2: Не создавайте пайпы на sdcard — FAT32 не поддерживает их.

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.


Добавляем атрибут «process» к нашему сервису в манифесте.

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


* This source code was highlighted with Source Code Highlighter.

В DDMS'е видим оба процесса.

image

Запускаем и видим увеличивающуюся циферку на экране (не очень эффектно).

Рассмотренным способом можно организовать и дуплексную связь (заведя еще один пайп). Конечно, решение не подходит для всех случаев. Если вам нужно передавать объекты, то лучше воспользоваться стандартным IPC-механизмом Android'а, который позволяет передавать сериализованные объекты между процессами.

Литература:
developers.sun.com/solaris/articles/named_pipes.html
developer.android.com/guide/developing/tools/aidl.html
Tags: androidnamed pipesipc
Hubs: Development for Android
Total votes 49: ↑42 and ↓7 +35
Comments 12
Comments Comments 12

Popular right now