关闭 x
IT技术网
    技 采 号
    ITJS.cn - 技术改变世界
    • 实用工具
    • 菜鸟教程
    IT采购网 中国存储网 科技号 CIO智库

    IT技术网

    IT采购网
    • 首页
    • 行业资讯
    • 系统运维
      • 操作系统
        • Windows
        • Linux
        • Mac OS
      • 数据库
        • MySQL
        • Oracle
        • SQL Server
      • 网站建设
    • 人工智能
    • 半导体芯片
    • 笔记本电脑
    • 智能手机
    • 智能汽车
    • 编程语言
    IT技术网 - ITJS.CN
    首页 » 安卓开发 »Android扫描多媒体文件剖析

    Android扫描多媒体文件剖析

    2014-12-13 00:00:00 出处:51CTO
    分享

    该文从系统源代码分析,讲述如何将程序创建的多媒体文件加入系统的媒体库,如何从媒体库删除,以及大多数程序开发者经常遇到的无法添加到媒体库的问题等。本人将通过对源代码的分析,一一解释这些问题。

    Android中的多媒体文件扫描机制

    Android提供了一个很棒的程序来处理将多媒体文件加入的媒体库中。这个程序就是MediaProvider,现在我们简单看以下这个程序。首先看一下它的Receiver

    <receiver android:name="MediaScannerReceiver">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED" />
            </intent-filter>
            <intent-filter>
                <action android:name="android.intent.action.MEDIA_MOUNTED" />
                <data android:scheme="file" />
            </intent-filter>
            <intent-filter>
                <action android:name="android.intent.action.MEDIA_UNMOUNTED" />
                <data android:scheme="file" />
            </intent-filter>
            <intent-filter>
                <action android:name="android.intent.action.MEDIA_SCANNER_SCAN_FILE" />
                <data android:scheme="file" />
            </intent-filter>
        </receiver>

    MediaScannerReceiver只接收符合action和数据规则正确的intent。

    MediaScannerReciever如何处理Intent

    当且仅当接收到action android.intent.action.BOOT_COMPLETED才扫描内部存储(非内置和外置sdcard) 除了action为android.intent.action.BOOT_COMPLETED 的以外的intent都必须要有数据传递。 当收到 Intent.ACTION_MEDIA_MOUNTED intent,扫描Sdcard 当收到 Intent.ACTION_MEDIA_SCANNER_SCAN_FILE intent,检测没有问题,将扫描单个文件。

    MediaScannerService如何工作

    实际上MediaScannerReceiver并不是真正处理扫描工作,它会启动一个叫做MediaScannerService的服务。我们继续看MediaProvider的manifest中关于service的部分。

    <service android:name="MediaScannerService" android:exported="true">
            <intent-filter>
                <action android:name="android.media.IMediaScannerService" />
            </intent-filter>
        </service>

    MediaScannerService中的scanFile方法

    private Uri scanFile(String path, String mimeType) {
        String volumeName = MediaProvider.EXTERNAL_VOLUME;
        openDatabase(volumeName);
        MediaScanner scanner = createMediaScanner();
        return scanner.scanSingleFile(path, volumeName, mimeType);
    }

    MediaScannerService中的scan方法

    private void scan(String[] directories, String volumeName) {
        // don't sleep while scanning
        mWakeLock.acquire();
    
        ContentValues values = new ContentValues();
        values.put(MediaStore.MEDIA_SCANNER_VOLUME, volumeName);
        Uri scanUri = getContentResolver().insert(MediaStore.getMediaScannerUri(), values);
    
        Uri uri = Uri.parse("file://" + directories[0]);
        sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_STARTED, uri));
    
        try {
            if (volumeName.equals(MediaProvider.EXTERNAL_VOLUME)) {
                openDatabase(volumeName);
            }
    
            MediaScanner scanner = createMediaScanner();
            scanner.scanDirectories(directories, volumeName);
        } catch (Exception e) {
            Log.e(TAG, "exception in MediaScanner.scan()", e);
        }
    
        getContentResolver().delete(scanUri, null, null);
    
        sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_FINISHED, uri));
        mWakeLock.release();
    }

    MediaScannerService中的createMediaScanner方法

    private MediaScanner createMediaScanner() {
            MediaScanner scanner = new MediaScanner(this);
            Locale locale = getResources().getConfiguration().locale;
            if (locale != null) {
                String language = locale.getLanguage();
                String country = locale.getCountry();
                String localeString = null;
                if (language != null) {
                    if (country != null) {
                        scanner.setLocale(language + "_" + country);
                    } else {
                        scanner.setLocale(language);
                    }
                }
            }
    
            return scanner;
    }

    从上面可以发现,真正工作的其实是android.media.MediaScanner.java 具体扫描过程就请点击左侧链接查看。

    如何扫描一个刚创建的文件

    这里介绍两种方式来实现将新创建的文件加入媒体库。

    最简单的方式

    只需要发送一个正确的intent广播到MediaScannerReceiver即可。

    String saveAs = "Your_Created_File_Path"
    Uri contentUri = Uri.fromFile(new File(saveAs));
    Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE,contentUri);
    getContext().sendBroadcast(mediaScanIntent);

    上面的极简方法大多数情况下正常工作,但是有些情况下是不会工作的,稍后的部分会介绍。即使你使用上述方法成功了,还是建议你继续阅读稍后的为什么发广播不成功的部分。

    使用MediaScannerConnection

    public void mediaScan(File file) {
        MediaScannerConnection.scanFile(getActivity(),
                new String[] { file.getAbsolutePath() }, null,
                new OnScanCompletedListener() {
                    @Override
                    public void onScanCompleted(String path, Uri uri) {
                        Log.v("MediaScanWork", "file " + path
                                + " was scanned seccessfully: " + uri);
                    }
                });
    }

    MediaScannerConnection的scanFile方法从2.2(API 8)开始引入。

    创建一个MediaScannerConnection对象然后调用scanFile方法

    很简单,参考http://developer.android.com/reference/android/media/MediaScannerConnection.html

    如何扫描多个文件

    发送多个Intent.ACTION_MEDIA_SCANNER_SCAN_FILE广播 使用MediaScannerConnection,传入要加入的路径的数组。

    为什么发送MEDIA_SCANNER_SCAN_FILE广播不生效

    关于为什么有些设备上不生效,很多人认为是API原因,其实不是的,这其实和你传入的文件路径有关系。看一下接收者Receiver的onReceive代码。

    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        Uri uri = intent.getData();
        if (action.equals(Intent.ACTION_BOOT_COMPLETED)) {
            // scan internal storage
            scan(context, MediaProvider.INTERNAL_VOLUME);
        } else {
            if (uri.getScheme().equals("file")) {
                // handle intents related to external storage
                String path = uri.getPath();
                String externalStoragePath = Environment.getExternalStorageDirectory().getPath();
    
                Log.d(TAG, "action: " + action + " path: " + path);
                if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) {
                    // scan whenever any volume is mounted
                    scan(context, MediaProvider.EXTERNAL_VOLUME);
                } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE) &&
                        path != null && path.startsWith(externalStoragePath + "/")) {
                    scanFile(context, path);
                }
            }
        }
    }

    所有的部分都正确除了传入的路径。因为你可能硬编码了文件路径。因为有一个这样的判断path.startsWith(externalStoragePath + "/"),这里我举一个简单的小例子。

    final String saveAs = "/sdcard/" + System.currentTimeMillis() + "_add.png";
    Uri contentUri = Uri.fromFile(new File(saveAs));
    Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE,contentUri);
    getContext().sendBroadcast(mediaScanIntent);
    Uri uri = mediaScanIntent.getData();
    String path = uri.getPath();
    String externalStoragePath = Environment.getExternalStorageDirectory().getPath();
    Log.i("LOGTAG", "Androidyue onReceive intent= " + mediaScanIntent
                            + ";path=" + path + ";externalStoragePath=" +
                            externalStoragePath);

    我们看一下输出日志,分析原因。

    LOGTAG Androidyue onReceive intent= Intent { act=android.intent.action.MEDIA_SCANNER_SCAN_FILE dat=file:///sdcard/1390136305831_add.png };path=/sdcard/1390136305831_add.png;externalStoragePath=/mnt/sdcard

    上述输出分析,你发送的广播,action是正确的,数据规则也是正确的,而且你的文件路径也是存在的,但是,文件的路径/sdcard/1390136305831_add.png并不是以外部存储根路径/mnt/sdcard/开头。所以扫描操作没有开始,导致文件没有加入到媒体库。所以,请检查文件的路径。

    如何从多媒体库中移除

    假如我们删除一个多媒体文件的话,也就意味我们还需要将这个文件从媒体库中删除掉。

    能不能简简单单发广播?

    仅仅发一个广播能解决问题么?我倒是希望可以,但是实际上是不工作的,查看如下代码即可明白。

    // this function is used to scan a single file
    public Uri scanSingleFile(String path, String volumeName, String mimeType) {
        try {
            initialize(volumeName);
            prescan(path, true);
    
            File file = new File(path);
            if (!file.exists()) {
                return null;
            }
    
            // lastModified is in milliseconds on Files.
            long lastModifiedSeconds = file.lastModified() / 1000;
    
            // always scan the file, so we can return the content://media Uri for existing files
            return mClient.doScanFile(path, mimeType, lastModifiedSeconds, file.length(),
                    false, true, MediaScanner.isNoMediaPath(path));
        } catch (RemoteException e) {
            Log.e(TAG, "RemoteException in MediaScanner.scanFile()", e);
            return null;
        }
    }

    正如上述代码,会对文件是否存在进行检查,假如文件不存在,直接停止向下执行。所以这样是不行的。那怎么办呢?

    public void testDeleteFile() {
        String existingFilePath = "/mnt/sdcard/1390116362913_add.png";
        File  existingFile = new File(existingFilePath);
        existingFile.delete();
        ContentResolver resolver = getActivity().getContentResolver();
        resolver.delete(Images.Media.EXTERNAL_CONTENT_URI, Images.Media.DATA + "= ", new String[]{existingFilePath});
    
    }

    上述代码是可以工作的,直接从MediaProvider删除即可。 具体的删除代码请参考Code Snippet for Media on Android

    上一篇返回首页 下一篇

    声明: 此文观点不代表本站立场;转载务必保留本文链接;版权疑问请联系我们。

    别人在看

    正版 Windows 11产品密钥怎么查找/查看?

    还有3个月,微软将停止 Windows 10 的更新

    Windows 10 终止支持后,企业为何要立即升级?

    Windows 10 将于 2025年10 月终止技术支持,建议迁移到 Windows 11

    Windows 12 发布推迟,微软正全力筹备Windows 11 25H2更新

    Linux 退出 mail的命令是什么

    Linux 提醒 No space left on device,但我的空间看起来还有不少空余呢

    hiberfil.sys文件可以删除吗?了解该文件并手把手教你删除C盘的hiberfil.sys文件

    Window 10和 Windows 11哪个好?答案是:看你自己的需求

    盗版软件成公司里的“隐形炸弹”?老板们的“法务噩梦” 有救了!

    IT头条

    公安部:我国在售汽车搭载的“智驾”系统都不具备“自动驾驶”功能

    02:03

    液冷服务器概念股走强,博汇、润泽等液冷概念股票大涨

    01:17

    亚太地区的 AI 驱动型医疗保健:2025 年及以后的下一步是什么?

    16:30

    智能手机市场风云:iPhone领跑销量榜,华为缺席引争议

    15:43

    大数据算法和“老师傅”经验叠加 智慧化收储粮食尽显“科技范”

    15:17

    技术热点

    SQL汉字转换为拼音的函数

    windows 7系统无法运行Photoshop CS3的解决方法

    巧用MySQL加密函数对Web网站敏感数据进行保护

    MySQL基础知识简介

    Windows7和WinXP下如何实现不输密码自动登录系统的设置方法介绍

    windows 7系统ip地址冲突怎么办?windows 7系统IP地址冲突问题的

      友情链接:
    • IT采购网
    • 科技号
    • 中国存储网
    • 存储网
    • 半导体联盟
    • 医疗软件网
    • 软件中国
    • ITbrand
    • 采购中国
    • CIO智库
    • 考研题库
    • 法务网
    • AI工具网
    • 电子芯片网
    • 安全库
    • 隐私保护
    • 版权申明
    • 联系我们
    IT技术网 版权所有 © 2020-2025,京ICP备14047533号-20,Power by OK设计网

    在上方输入关键词后,回车键 开始搜索。Esc键 取消该搜索窗口。