如何在 Android 10 中替换 FileObserver?

Posted

技术标签:

【中文标题】如何在 Android 10 中替换 FileObserver?【英文标题】:How to replace FileObserver in Android 10? 【发布时间】: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 你解决了这个问题吗?我被卡住了 问题仍然存在。 【参考方案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 会做什么。

【讨论】:

以上是关于如何在 Android 10 中替换 FileObserver?的主要内容,如果未能解决你的问题,请参考以下文章

如何替换android应用图标

如何在 Android 清单应用程序中添加多个“工具:替换”?

如何在android studio中用另一个片段替换一个片段

Android如何在一段时间后替换视图?

如何使用 Gradle 替换 Android Volley 库?

Android编译时动态替换Jar包中的类