Несколько FileObserver в одном файле не удалось

В моем приложении у меня есть различные компоненты, которые отслеживают определенный файл в SDCard с помощью FileObservers. Таким образом, есть два экземпляра File Observer, которые наблюдают за одним файлом, скажем, abc.xml для всех событий.

FileObserver fo1 = new FileObserver(new File("/sdcard/abc.xml"));
fo1.startWatching();
FileObserver fo2 = new FileObserver(new File("/sdcard/abc.xml"));
fo2.startWatching();

Они оба зарегистрированы для разных событий. Моя проблема в том, что когда оба наблюдателя файла смотрят параллельно, я пропускаю вызовы onEvent() для "fo1".

Это ограничение системы Android? Каковы пути преодоления этой проблемы?

2 ответа

Поздно, но может быть полезно для других: это ошибка в Android - проблема сообщается здесь.

Так как это заставляло меня рвать на себе волосы, я написал замену для FileObserver, которая обходит эту проблему, поддерживая основной список FileObserver. Замена всех FileObservers в приложении этим FixedFileObserver должна привести к ожидаемому поведению. (предупреждение о вреде для здоровья: я не очень тщательно тестировал его во всех угловых случаях, но он работает для меня)

FixedFileObserver.java

package com.fimagena.filepicker.backend;

import android.os.FileObserver;

import java.io.File;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;


public abstract class FixedFileObserver {

    private final static HashMap<File, Set<FixedFileObserver>> sObserverLists = new HashMap<>();

    private FileObserver mObserver;
    private final File mRootPath;
    private final int mMask;

    public FixedFileObserver(String path) {this(path, FileObserver.ALL_EVENTS);}
    public FixedFileObserver(String path, int mask) {
        mRootPath = new File(path);
        mMask = mask;
    }

    public abstract void onEvent(int event, String path);

    public void startWatching() {
        synchronized (sObserverLists) {
            if (!sObserverLists.containsKey(mRootPath)) sObserverLists.put(mRootPath, new HashSet<FixedFileObserver>());

            final Set<FixedFileObserver> fixedObservers = sObserverLists.get(mRootPath);

            mObserver = fixedObservers.size() > 0 ? fixedObservers.iterator().next().mObserver : new FileObserver(mRootPath.getPath()) {
                @Override public void onEvent(int event, String path) {
                    for (FixedFileObserver fixedObserver : fixedObservers)
                        if ((event & fixedObserver.mMask) != 0) fixedObserver.onEvent(event, path);
                }};
            mObserver.startWatching();
            fixedObservers.add(this);
        }
    }

    public void stopWatching() {
        synchronized (sObserverLists) {
            Set<FixedFileObserver> fixedObservers = sObserverLists.get(mRootPath);
            if ((fixedObservers == null) || (mObserver == null)) return;

            fixedObservers.remove(this);
            if (fixedObservers.size() == 0) mObserver.stopWatching();

            mObserver = null;
        }
    }

    protected void finalize() {stopWatching();}
}

Ответ @Fimagena отлично работает для меня. Для тех, кто перешел на Kotlin и обнаружил, что версия, сгенерированная преобразователем кода Java->Kotlin, не работает, вот рабочая версия Kotlin:

package <your package>

import android.os.FileObserver
import java.io.File

var sObserverLists = mutableMapOf<File, MutableSet<FixedFileObserver>>()

abstract class FixedFileObserver(
        path: String,
        private val eventMask: Int = FileObserver.ALL_EVENTS
) {
    private var fileObserver: FileObserver? = null
    private val rootPath: File = File(path)

    abstract fun onEvent(event: Int, relativePath: String?)

    fun startWatching() {
        synchronized(sObserverLists) {
            if (!sObserverLists.containsKey(rootPath)) {
                sObserverLists[rootPath] = mutableSetOf()
            }

            val fixedObservers = sObserverLists[rootPath]

            fileObserver = if (fixedObservers!!.isNotEmpty()) {
                fixedObservers.iterator().next().fileObserver
            } else {
                object : FileObserver(rootPath.path) {
                    override fun onEvent(event: Int, path: String?) {
                        for (fixedObserver in fixedObservers) {
                            if (event and fixedObserver.eventMask != 0) {
                                fixedObserver.onEvent(event, path)
                            }
                        }
                    }
                }
            }
            fixedObservers.add(this)
            fileObserver!!.startWatching()
        }
    }

    fun stopWatching() {
        synchronized(sObserverLists) {
            val fixedObservers = sObserverLists.get(rootPath)
            if (fixedObservers == null || fileObserver == null) {
                return
            }

            fixedObservers.remove(this)
            if (fixedObservers.isEmpty()) {
                fileObserver!!.stopWatching()
            }

            fileObserver = null
        }
    }
}

И бонусный класс-обертка для энтузиастов rxJava/rxKotlin:

class RxFileObserver(
        private val path: String, eventMask: 
        Int = FileObserver.ALL_EVENTS
) : FixedFileObserver(path, eventMask) {

    private val subject = PublishSubject.create<String>().toSerialized()

    val observable: Observable<String> =
            subject.doOnSubscribe { startWatching() }
                    .doOnDispose { stopWatching() }
                    .share()

    override fun onEvent(event: Int, relativePath: String?) {
        subject.onNext(path)
    }
}
Другие вопросы по тегам