扫描Android SD卡以查找新文件

13
我的应用程序允许用户将图像保存到他们的SD卡中,但我不确定如何在挂载和卸载SD卡之前让它出现在图库中。我已经搜索了几天这个问题,但不确定如何自动显示它。我找到了这个链接,但我不知道如何使用这个类。这是我用来保存文件的代码,在try catch块的底部是我想要扫描新媒体的SD卡的地方。
    FileOutputStream outStream = null;
    File file = new File(dirPath, fileName);
    try {
        outStream = new FileOutputStream(file);
        bm.compress(Bitmap.CompressFormat.JPEG, 100, outStream);
        outStream.flush();
        outStream.close();
    } catch {
         ...
    }

如果有人能指点我正确的方向,我会非常感激。


"sdrescan" 应用程序可以实现您想要的功能。它是免费的;也许您可以向作者请求代码片段。或者只需从您的应用程序中启动它即可。 - Edward Falk
有没有一种方法可以在不停止音乐播放器的情况下重新扫描?它会显示“抱歉,媒体播放器不支持此文件”,直到我按下后退一首歌曲、前进一首歌曲,然后它才能再次播放。 - NoBugs
6个回答

10

我尝试过很多不同的方法来触发MediaScanner,以下是我的结果。

发送广播

最简单和天真的解决方案。它包括从您的代码执行以下指令:

sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED,
              Uri.parse("file://"+ Environment.getExternalStorageDirectory())));

然而,在KitKat设备中由于缺少必要的权限,这种方法已经不再适用


MediaScannerWrapper

此处(根据@Brian的答案)所述,它包装了一个MediaScannerConnection实例,以便在特定目录上触发scan()方法。这种方法已被证明对于4.3及以下版本很好用,但在KitKat(4.4+)中仍然没有成功的运行。


FileWalker

有许多Play Store应用程序试图克服MediaStore不更新其数据库的问题之一是ReScan SD。它发送许多不同的广播:

  sendBroadcast(new Intent("android.intent.action.MEDIA_MOUNTED", Uri.parse("file://" + Environment.getExternalStorageDirectory())));
  sendBroadcast(new Intent("android.intent.action.MEDIA_MOUNTED", Uri.parse("file:///Removable")));
  sendBroadcast(new Intent("android.intent.action.MEDIA_MOUNTED", Uri.parse("file:///Removable/SD")));
  sendBroadcast(new Intent("android.intent.action.MEDIA_MOUNTED", Uri.parse("file:///Removable/MicroSD")));
  sendBroadcast(new Intent("android.intent.action.MEDIA_MOUNTED", Uri.parse("file:///mnt/Removable/MicroSD")));
  sendBroadcast(new Intent("android.intent.action.MEDIA_MOUNTED", Uri.parse("file:///mnt")));
  sendBroadcast(new Intent("android.intent.action.MEDIA_MOUNTED", Uri.parse("file:///storage")));
  sendBroadcast(new Intent("android.intent.action.MEDIA_MOUNTED", Uri.parse("file:///Removable")));

为了支持KitKat,该程序通过手动触发基目录下每个文件的scan()方法来尝试支持KitKat。不幸的是,这既需要大量的CPU资源又需要很长时间,因此并不建议使用。


"Shell方式"

在某些情况下,似乎唯一可以在KitKat中正常工作的方法是通过adb shell发送广播。因此,此代码片段允许您以编程方式执行此操作:

Runtime.getRuntime().exec("am broadcast -a android.intent.action.MEDIA_MOUNTED -d file://" + Environment.getExternalStorageDirectory());

这种方法更像是一种hack方式,但目前来说是我能想到的最好的方法。


底线

上述方案实际上适用于所有不是KitKat的内容。这是因为,感谢Justin,已经发现并发布了一个错误到官方追踪器。这意味着,在错误被解决之前,我们没有真正的KitKat支持。

应该使用哪个?在这些中,我会使用MediaScannerWrapper解决方案,以及shell-ish方法(最后一个)。


KITKAT上的shell方法,您提到它在某些情况下可以工作。我很想知道在哪些用例下此方法会失败?或者在哪些用例下它可以工作? - Ahmed
我找不到任何固定的使用情况。唯一有效的方法似乎是在添加文件后刷新MediaScanner。另一方面,当删除文件时它却不起作用。 - Sebastiano
2
当我使用adb shell从命令行发送广播时,它可以用于新文件和已删除的文件。但是,在KitKat上,无论是对于新文件还是已删除的文件,在程序上都不能正常工作。我正在使用模拟器API 19测试音频音乐文件。 - Ahmed
我认为这是可以做到的。因为当我重命名或添加一些媒体文件时,使用ES文件浏览器并更改包含该媒体的文件夹的路径,它们会出现在画廊中并添加到媒体存储中。如果您在该文件夹上有太多媒体,则需要一些时间来重新生成缩略图。顺便说一下,当我添加或重命名图像文件时,我会观察logcat:Media Provider object removed,然后是一些带有标签art的logcat,关于创建位图,我确信唯一的方法是使用新数据和位图更新MediaStore,如果您重命名或移动媒体,则删除最后一个并添加新的。 - M. Reza Nasirloo

5

自从我上次发布的答案显然不是一个合适的方法后,我在这里找到了另一种方法(链接)。你基本上需要创建一个包装类,初始化它,然后调用scan()方法。非常有帮助的帖子。如果这也不合适,请告诉我。


注意:这里使用的是从API Level 1开始就可用的scanFile(),而不是在API Level 8之后可用的新版scanFile()。 - Brian

3

您也可以通过发送广播显式调用媒体扫描器。

sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri
                    .parse("file://"
                            + Environment.getExternalStorageDirectory())));

编辑

这是一篇旧文章。更新到新版本

Android正在采取措施,防止应用程序伪造更多类似的系统广播。

如果您想要让Android索引放在外部存储器上的文件,则可以使用MediaScannerConnectionACTION_MEDIA_SCANNER_SCAN_FILE

参考:此帖子

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
    final Intent scanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
    final Uri contentUri = Uri.fromFile(outputFile); 
    scanIntent.setData(contentUri);
    sendBroadcast(scanIntent);
} else {
    final Intent intent = new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse("file://" + Environment.getExternalStorageDirectory()));
    sendBroadcast(intent);
}

如果上面的代码不能正常工作,您可以尝试以下方法:

MediaScannerConnection.scanFile(this, new String[] {
    file.getAbsolutePath()
}, null, new MediaScannerConnection.OnScanCompletedListener() {

    @Override
    public void onScanCompleted(String path, Uri uri) {


    }
});

1
不允许发送广播 android.intent.action.MEDIA_MOUNTED,这与安卓系统有关。 - Evgenii Vorobei

3

我已经仔细阅读了参考资料好几遍,但是我还是无法弄清楚如何让mediaScannerConnection工作。至于辅助函数,我认为现在还不是最好的方法。API 8并不是很旧,但它不支持很多设备(包括我的测试手机)。我尝试了几天我的初始帖子中的链接,但是我无法让那个示例工作。如果您可以发布一些片段或指向另一个示例,我将不胜感激。在那之前,我只能保留“sendBroadcast”方法,因为这是目前唯一可行的方法。 - Brian
这里有一个新功能的API演示:http://developer.android.com/resources/samples/ApiDemos/src/com/example/android/apis/content/ExternalStorage.html。你可以在这里找到该功能的代码实现,以便为自己的实现建模:http://android.git.kernel.org/?p=platform/frameworks/base.git;a=blob;f=media/java/android/media/MediaScannerConnection.java。 - hackbod
1
重申一下:不要发送ACTION_MEDIA_MOUNTED。这将会做各种各样的事情,包括对SD卡进行完整扫描。事实上,我需要确保在未来版本的平台中,应用程序不能发送此广播,因为如果它们在错误的时间接收到该广播,可能会导致平台的某些部分出现问题。所以请预计,如果您的应用程序这样做,将来可能会出现故障。 - hackbod
2
就像我说的那样,我想改变它并且用正确的方式来做。但是我无法让“正确的方式”起作用。如果您可以通过告诉我该怎么做而不是告诉我不要做什么来帮助我,我将不胜感激。 - Brian
@hackbod - 我发现让媒体存储库扫描已删除的照片的唯一方法是使用ACTION_MEDIA_MOUNTED。我尝试了几种解决方案,但都失败了:使用文件和父级目录的MediaScannerConnection.scanFile(...)。ACTION_MEDIA_SCANNER_SCAN_FILE也不起作用。 - AlikElzin-kilaka
@hackbod - 添加了一个新的SO问题:https://dev59.com/GHnZa4cB1Zd3GeqPlxcf - AlikElzin-kilaka

1

这里有另一种强制扫描的方法:

context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE,"uri to file"));

系统会发出ACTION_MEDIA_SCANNER_FINISHED广播,因此您可以使用BroadcastReceiver对其进行反应。
为了能够接收ACTION_MEDIA_SCANNER_FINISHED广播,意图过滤器应包含数据方案:
IntentFilter intentFilter = new IntentFilter(Intent.ACTION_MEDIA_SCANNER_FINISHED);
intentFilter.addDataScheme("file");
context.registerReceiver(mMediaScannerFinishReceiver, intentFilter);

来自文档:

public static final String ACTION_MEDIA_SCANNER_SCAN_FILE

添加于API级别1 广播操作:请求媒体扫描器扫描文件并将其添加到媒体数据库中。文件的路径包含在Intent.mData字段中。

public static final String ACTION_MEDIA_SCANNER_FINISHED

添加于API级别1 广播操作:媒体扫描器已完成扫描目录。扫描目录的路径包含在Intent.mData字段中。


0
你可以使用 MediaStore.Images.Media.insertImage 将一个项目插入到相册中。

感谢帮助。但是我无法让 "insertImage" 正常工作。不过我在另一个论坛上找到了答案。 - Brian

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接