目录
前言
Android 9时扫描逻辑还在MediaScanner中(这块之前有介绍过),而后续Android高版本开始变化,以Android 13来说,扫描逻辑已经放在MediaProvider中了,也就是ModernMediaScanner。
Android 10,11和12项目少
今天就简单介绍一下Android 13中MediaProvider的扫描流程。
正文
MediaProvider目录
packages\providers\MediaProvider
这里主要接扫下面几个java文件
MediaProvider.java
ModernMediaScanner.java
MediaReceiver.java
MediaService.java
一开机时,系统会自动拉起MediaProvider,我们知道ContentProvider的启动比Application的启动更早,因此我这里以开机先启动介绍。
以插入U盘为例
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的使用,可以看看《》,这里不过多介绍。
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()
扫描开始广播[ACTION_MEDIA_SCANNER_STARTED]发送
扫描结束广播[ACTION_MEDIA_SCANNER_FINISHED]发送
添加磁盘扫描标志位attachVolume()
广播发送就不过多叙述了,依次进入attachVolume()和scanDirectory()。
MediaProvider.java
attachVolume()
这里重点是
查看磁盘的盘符名是否支持
查看磁盘是否挂载成功,也就是文件[根目录]是否存在
添加到扫描过的磁盘列表,用于判断是否扫描过
回到上面,看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,至于具体的使用,暂时没摸透,略过。
小结
暂时就记录到这吧,后面有空会深入介绍。