Несколько 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)
}
}