В одном из проектов мне понадобилось отслеживать прогресс выполнения нативного кода на Android'е (конкретно — портированного FFmpeg'а). Ситуация осложнялась также тем, что по ряду причин код выполнялся в нескольких процессах.
Среди рассмотренных вариантов были следующие:
Наиболее подходящим вариантом оказался последний — использование именованных каналов, стандартного средства для межпроцессных взаимодействий в unix-системах (для незнакомых с ним, почитать можно например здесь). Вкратце — это однонаправленный FIFO-канал с файловым интерфейсом. При записи он блокируется до тех пор, пока другой процесс не откроет его для чтения и наоборот — при чтении канал снова блокируется, пока другой процесс не запишет в него данные. Большим плюсом является то, что одновременно писать в пайп может сразу несколько процессов.
Тут лежит тестовый проект, демонстрирующий идею. В нем запускается сервис, пишущий в пайп из нативного кода, а также поток-читатель, выводящий результат на экран.
Поток-reader. Создает новый пайп функцией mkfifo (это просто обертка над одноименным системным вызовом) и читает байты из него, как из обычного файла. Вызов read блокирует выполнение до тех пор, пока кто-то с другой стороны пайпа не начнет писать данные. Здесь используется удобный класс AsyncTask, который позволяет выполнять функцию в бэкграунде и публиковать промежуточные результаты в UI-поток.
Поток-writer. Запускаем его в отдельном сервисе. Он выполняет долгоиграющую функцию, пишущую прогресс выполнения в этот самый пайп.
Ворнинг 1: Несмотря на то, что код может выполняться в отдельном процессе, запускайте долгие операции в отдельном потоке, иначе система убьет процесс без предупреждения.
Ворнинг 2: Не создавайте пайпы на sdcard — FAT32 не поддерживает их.
Добавляем атрибут «process» к нашему сервису в манифесте.
В DDMS'е видим оба процесса.
Запускаем и видим увеличивающуюся циферку на экране (не очень эффектно).
Рассмотренным способом можно организовать и дуплексную связь (заведя еще один пайп). Конечно, решение не подходит для всех случаев. Если вам нужно передавать объекты, то лучше воспользоваться стандартным IPC-механизмом Android'а, который позволяет передавать сериализованные объекты между процессами.
Литература:
developers.sun.com/solaris/articles/named_pipes.html
developer.android.com/guide/developing/tools/aidl.html
Среди рассмотренных вариантов были следующие:
- 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'е видим оба процесса.
Запускаем и видим увеличивающуюся циферку на экране (не очень эффектно).
Рассмотренным способом можно организовать и дуплексную связь (заведя еще один пайп). Конечно, решение не подходит для всех случаев. Если вам нужно передавать объекты, то лучше воспользоваться стандартным IPC-механизмом Android'а, который позволяет передавать сериализованные объекты между процессами.
Литература:
developers.sun.com/solaris/articles/named_pipes.html
developer.android.com/guide/developing/tools/aidl.html