【问题标题】:How to replace FileObserver in Android 10?如何在 Android 10 中替换 FileObserver?
【发布时间】:2020-03-06 17:36:35
【问题描述】:

android.os.FileObserver 需要java.io.File 才能运行。 但是在 Android 10 中,由于著名的“存储访问框架”,Google 限制了对除您应用的私有目录之外的所有内容的访问。因此,通过java.io.File 访问任何内容都会中断并呈现FileObserver 无用,除非您打算在应用程序的私有目录中使用它。但是,我希望在外部存储的某个目录中发生更改时收到通知。我还想避免定期检查更改。

我尝试使用ContentResolver.registerContentObserver(uri,notifyForDescendants,observer),但该方法遇到了一些问题:

  • 到目前为止我插入的每个Uri 都被接受了
  • 如果Uri 不起作用,它既不会失败也不会通知
  • 我找不到任何文档告诉我哪个 Uris 真正有效

我在某种程度上唯一得到的工作是以下方法:

// works, but returns all changes to the external storage
contentResolver.registerContentObserver(MediaStore.Files.getContentUri("external"), true, contentObserver)

不幸的是,这包括所有外部存储,并且仅在发生更改时返回 Media Uris - 例如content://media/external/file/67226

有没有办法查明Uri 是否指向我的目录?

或者有没有办法让registerContentObserver()Uri 一起工作,这样每当文件夹中的某些内容发生变化时我都会收到通知?

我也没有成功尝试与DocumentsFile 和外部存储Uris 相关的各种 Uris。

【问题讨论】:

  • 也许可以尝试传递目录 uri 而不是 External Conten Uri。
  • 我应该提到的,但没有用。
  • @squaresandcircles 你能解决这个问题吗?我正在从事类似的任务并坚持下去。
  • @squaresandcircles 你解决了这个问题吗?我被卡住了
  • 问题仍然存在。

标签: android android-10.0 storage-access-framework fileobserver


【解决方案1】:

即使尝试使用以下基本构造函数时,我也会不断收到错误 -

No direct method <init>(Ljava/util/List;I)V in class Landroid/os/FileObserver; or its super classes (declaration of 'android.os.FileObserver' appears in /system/framework/framework.jar!classes2.dex)

来自Detect file change using FileObserver on Android的评论:

当我尝试使用构造函数 FileObserver(File) 时,我看到了该消息(或类似的内容)。 deprecated FileObserver(String) 的使用解决了我的问题....原来的 FileObserver 有错误。

完全披露,我使用的是 Xamarin.Android API;但是,我引用的要点和评论者都在使用 Java。无论如何,确实 - 再次尝试使用对应的 String 构造函数,我终于能够制作和使用观察者。磨我的齿轮以使用已弃用的 API,但显然他们至少坚持到并包括 Android 12.0.0_r3... 仍然希望支持的构造函数实际工作。也许这里有一些提交问题的保证。

【讨论】:

  • 对应的 String 构造函数也适用于我。
【解决方案2】:

我有这个问题的临时解决方案,所以让我们看看这是否有帮助。 我使用DocumentFile 启动了一个无限的while 循环来监视文件的创建和删除(如果要修改文件或重命名文件,则必须执行更多操作)。以下是我的示例:

private static int currentFileIndirectory = 0;
private static final int FILE_CREATED = 0;
private static final int FILE_DELETED = 1;

private static DocumentFile[] onDirectoryChanged(DocumentFile[] documentFiles, int event) {
    Log.d("FileUtil", "onDirectoryChanged: " + event);
    if (event == FILE_CREATED) {
    } else {

    }
    return documentFiles;
}

private static boolean didStartWatching = false;

private static void startWatchingDirectory(final DocumentFile directory) {
    if (!didStartWatching) {
        didStartWatching = true;
        DocumentFile[] documentFiles = directory.listFiles();
        if (null != documentFiles && documentFiles.length > 0) {
            currentFileIndirectory = documentFiles.length;
        }

        new Thread(new Runnable() {
            public void run() {
                while (true) {
                    DocumentFile[] documentFiles = directory.listFiles();
                    if (null != documentFiles && documentFiles.length > 0) {
                        if (documentFiles.length != currentFileIndirectory) {
                            if (documentFiles.length > currentFileIndirectory) {//file created
                                DocumentFile[] newFiles = new DocumentFile[documentFiles.length - currentFileIndirectory];
                                onDirectoryChanged(newFiles, FILE_CREATED);
                            } else {//file Deleted
                                onDirectoryChanged(null, FILE_DELETED);
                            }
                            currentFileIndirectory = documentFiles.length;
                        }
                    }
                }
            }
        }).start();
    }
} 

【讨论】:

  • 这是一个糟糕的实现——它引入了一个剧烈的轮询循环,实际上并没有提供任何新文件的onDirectoryChanged 回调,只是一个大小与新文件数量相符的空数组。不妨将documentFiles 参数更改为int
【解决方案3】:

我找到了一种在 Android 10 上使用 ContentObserver 实现 FileObserver 的方法,但它可能仅适用于媒体文件,因为它适用于媒体内容 uri。

ContentResolver.registerContentObserver()的uri应该是文件对应的媒体uri(如content://media/external/file/49),通过文件路径查询。

fun getMediaUri(context: Context, file: File): Uri? {
    val externalUri = MediaStore.Files.getContentUri("external")
    context.contentResolver.query(
        externalUri,
        null,
        "${MediaStore.Files.FileColumns.DATA} = ?",
        arrayOf(file.path),
        null
    )?.use { cursor ->
        if (cursor.moveToFirst()) {
            val idIndex = cursor.getColumnIndex("_id")
            val id = cursor.getLong(idIndex)
            return Uri.withAppendedPath(externalUri, "$id")
        }
    }
    return null
}

然后ContentObserver.onChange()将被uri: content://media/external/file/{id}的每个文件更改触发; uriContentObserver.onChange(boolean selfChange, Uri uri) 中将永远是content://media/external/file;只有注册的文件才会有 id(例如content://media/external/file/49?deletedata=false)。

当输入 uri 的路径与注册的 uri 匹配时,FileObserver 会做什么。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-11-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多