Android 13 MediaProvider简单记录

源码分析  2024年5月8日 pm5:38发布1个月前更新 91es.com站长
131 0 0

前言

Android 9时扫描逻辑还在MediaScanner中(这块之前有介绍过),而后续Android高版本开始变化,以Android 13来说,扫描逻辑已经放在MediaProvider中了,也就是ModernMediaScanner

Android 10,11和12项目少

今天就简单介绍一下Android 13中MediaProvider的扫描流程。

正文

MediaProvider目录

packages\providers\MediaProvider

这里主要接扫下面几个java文件

  1. MediaProvider.java

  2. ModernMediaScanner.java

  3. MediaReceiver.java

  4. MediaService.java

  5. ExternalStorageServiceImpl.java

一开机时,系统会自动拉起MediaProvider,我们知道ContentProvider的启动比Application的启动更早,因此我这里以开机先启动介绍。

  1. 以插入U盘为例

  2. U盘挂载监听有两个地方,一个是MediaReceiver,另外一个地方ExternalStorageServiceImpl,后面都会单独介绍。

MediaProvider.java

开机时会先执行onCreate()方法。

@Override
public boolean onCreate() {
    //略
    //初始化ModernMediaScanner
    mMediaScanner = new ModernMediaScanner(context);
    //初始化DatabaseHelper,包括External和Internal的
    mInternalDatabase = new DatabaseHelper(context, INTERNAL_DATABASE_NAME, false, false,
            Column.class, ExportedSince.class, Metrics::logSchemaChange, mFilesListener,
            MIGRATION_LISTENER, mIdGenerator, true);
    mExternalDatabase = new DatabaseHelper(context, EXTERNAL_DATABASE_NAME, false, false,
            Column.class, ExportedSince.class, Metrics::logSchemaChange, mFilesListener,
            MIGRATION_LISTENER, mIdGenerator, true);
    //略
    //attach内置磁盘
    attachVolume(MediaVolume.fromInternal(), /* validate */ false);
    //略
    return true;
}

很多不是很懂,暂时略去,只关注自己需要的。

上面初始化ModernMediaScanner对象,也就这里扫描数据的。

ModernMediaScanner.java

public ModernMediaScanner(Context context) {
    mContext = context;
    mDrmClient = new DrmManagerClient(context);
    for (DrmSupportInfo info : mDrmClient.getAvailableDrmSupportInfo()) {
        Iterator<String> mimeTypes = info.getMimeTypeIterator();
        while (mimeTypes.hasNext()) {
            mDrmMimeTypes.add(mimeTypes.next());
        }
    }
}

这里主要添加DRM的MimeTypes,略过。

MediaReceiver.java

广播监听很慢,是一个[致命]的缺点。因此U盘挂载和卸载流程走ExternalStorageServiceImpl中了。

插入U盘是监听U盘的挂载,这个是是通过静态广播监听。

先看AndroidManifest.xml注册了哪些广播

<receiver android:name="com.android.providers.media.MediaReceiver"
    android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED" />
    </intent-filter>
    <intent-filter>
        <action android:name="android.intent.action.LOCALE_CHANGED" />
    </intent-filter>
    <intent-filter>
        <action android:name="android.intent.action.PACKAGE_FULLY_REMOVED" />
        <action android:name="android.intent.action.PACKAGE_DATA_CLEARED" />
        <data android:scheme="package" />
    </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_SCANNER_SCAN_FILE" />
        <data android:scheme="file" />
    </intent-filter>
</receiver>

这些广播都需要处理,我们这里以android.intent.action.MEDIA_MOUNTED为例,也就是U盘的挂载成功时发出来的广播。

public class MediaReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        final String action = intent.getAction();
        //开机广播
        if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
            IdleService.scheduleIdlePass(context);
        } else {
            //其他的广播走这里
            intent.setComponent(new ComponentName(context, MediaService.class));
            MediaService.enqueueWork(context, intent);
        }
    }
}

重点关注

MediaService.enqueueWork(context, intent);

MediaService.java

MediaService继承JobIntentService,至于JobIntentService的使用,可以看看《JobIntentService的使用》,这里不过多介绍。

enqueueWork()
public static void enqueueWork(Context context, Intent work) {
    enqueueWork(context, MediaService.class, JOB_ID, work);
}

传入的work是在onHandleWork()中处理。

onHandleWork()

隐藏内容!
评论可看后才能查看!

onMediaMountedBroadcast()
private static void onMediaMountedBroadcast(Context context, Intent intent)
        throws IOException {
    final StorageVolume volume = intent.getParcelableExtra(StorageVolume.EXTRA_STORAGE_VOLUME);
    //挂载的磁盘信息,如果不为null才需要扫描
    if (volume != null) {
        MediaVolume mediaVolume = MediaVolume.fromStorageVolume(volume);
        try (ContentProviderClient cpc = context.getContentResolver()
                .acquireContentProviderClient(MediaStore.AUTHORITY)) {
            //判断是否已经isVolumeAttached
            if (!((MediaProvider)cpc.getLocalContentProvider()).isVolumeAttached(mediaVolume)) {
                //进入扫描逻辑
                onScanVolume(context, mediaVolume, REASON_MOUNTED);
            } else {
                //已经扫描过了
            }
        }
    } else {
        Log.e(TAG, "Couldn't retrieve StorageVolume from intent");
    }
}

isVolumeAttached()在启动后插入U盘时,这里大部分都是返回true的,也就是已经扫描过,因为ExternalStorageServiceImpl的对磁盘的监听更快。

这里假设没有扫描,进入onScanVolume()扫描阶段。

PS: 扫描流程一定是进入onScanVolume()

onScanVolume()

隐藏内容!
评论可看后才能查看!

  1. 扫描开始广播[ACTION_MEDIA_SCANNER_STARTED]发送

  2. 扫描结束广播[ACTION_MEDIA_SCANNER_FINISHED]发送

  3. 添加磁盘扫描标志位attachVolume()

  4. 扫描磁盘内容scanDirectory()

广播发送就不过多叙述了,依次进入attachVolume()和scanDirectory()。

MediaProvider.java

attachVolume()

隐藏内容!
评论可看后才能查看!

这里重点是

  1. 查看磁盘的盘符名是否支持

  2. 查看磁盘是否挂载成功,也就是文件[根目录]是否存在

  3. 添加到扫描过的磁盘列表,用于判断是否扫描过

回到上面,看scanDirectory()

scanDirectory()
public void scanDirectory(File file, int reason) {
    mMediaScanner.scanDirectory(file, reason);
}

哈哈,不干活的,抛给了mMediaScanner,也就是ModernMediaScanner的对象。

ModernMediaScanner.java

@Override
public void scanDirectory(File file, int reason) {
    try (Scan scan = new Scan(file, reason, /*ownerPackage*/ null)) {
        scan.run();
    } catch (OperationCanceledException ignored) {
    } catch (FileNotFoundException e) {
       Log.e(TAG, "Couldn't find directory to scan", e) ;
    }
}

呃呃,Scan继承Runnable。

暂时打住,我们今天之大概走一下U盘的挂载,至于怎么扫描后面单独分析。

ExternalStorageServiceImpl.java

我上面也说过,广播有个致命缺点,就是太慢了!

因此Android高版本引入了其他的方式监听U盘的挂载和卸载。

ExternalStorageServiceImpl继承ExternalStorageService,至于具体的使用,暂时没摸透,略过。

隐藏内容!
评论可看后才能查看!

小结

暂时就记录到这吧,后面有空会深入介绍。

参考文章

 历史上的今天

  1. 2022: Vim的光标命令(0条评论)
  2. 2018: Source Insight 4.0安装(0条评论)
版权声明 1、 本站名称: 91易搜
2、 本站网址: 91es.com3xcn.com
3、 本站内容: 部分来源于网络,仅供学习和参考,若侵权请留言
3、 本站申明: 个人流水账日记,内容并不保证有效

暂无评论

暂无评论...

随机推荐

叶芝 :当你老了(冰心版)

当你老了头发花白睡意沉沉倦坐在炉边取下这本书来慢慢读着追梦当年的眼神那柔美的神采与深幽的晕影多少人爱过你青春的片影爱过你的美貌以虚伪或是真情惟独一人爱你那朝圣者的心爱你哀戚的脸上岁月的留痕在炉栅边你弯下了腰低语着带着浅浅的伤感爱情是怎样逝去又怎样步上群山怎样在繁...

林语堂:人生的乐趣

我们只有知道一个国家人民生活的乐趣,才会真正了解这个国家,正如我们只有知道一个人怎样利用闲暇时光,才会真正了解这个人一样。只有当一个人歇下他手头不得不干的事情,开始做他所喜欢做的事情时,他的个性才会显露出来。只有当社会与公务的压力消失,金钱、名誉和野心的刺激离去,精神可以随心所欲地游荡之时,我们才会...

[NDK开发]Android JNI 开发之静态注册

前言简单记录一下,方便自己查阅。PS: Android jni开发主要依赖Android开发平台,sdk和ndk三个部分Android SDK : Version 31JAVA Sdk : java1.8NDK : android-ndk-r21d上面只是举个例...

shell脚本执行提示bad interpreter...

前言创建temp.sh脚本后,写入执行命令。执行时出行如下提示。-bash: ./temp.sh: /bin/bash^M: bad interpreter: No such file or directory正文通过鼠标右键创建文件temp.sh或者使用touch命令创建temp.sh都...

MediaPlayer源码介绍3

前言我们继续介绍MediaPlayer的源码,继《MediaPlayer源码介绍2》和《mediaserver的启动》后,MediaPlayer也进入了MediaPlayerService的接口调用中。今天我们继续以setDataSource为例,看看其在MediaPlayerService的...

MediaMetadataRetriever解析媒体文件元数据

前言记录一下,一般获取视频、音频等媒体文件的元数据信息是使用MediaMetadataRetriever这个类。正文直接上代码。MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever();//设...