MediaProvider流程分析

MediaProvider流程分析

1. 概要

Android MediaProvider 使用 SQLite 数据库存储图片、视频、音频、文档等多媒体文件的信息,供视频播放器、音乐播放器、图库、文档编辑器使用。按照谷歌的要求,Android上其他应用获取文件列表的时候修需要通过contentprovider来读取.类似的还有TelephonyProvider、CalendarProvider、ContactsProvider,掌握其中一个其他的也就不难理解了。

2. MediaProvider基础介绍

2.1. MediaProvider在Android中的定位

MediaProvider在Android中的角色参看如下图:

MediaProvider流程分析_第1张图片

2.2. MediaProvider 扫描流程

MediaProvider 的扫描流程图如下

[外链图片转存失败(img-aMediaProvider流程分析_第2张图片

2.3. MddiaProvider数据库相关

2.3.1. 数据库文件位置以及查看方式

MediaProvicer涉及到的数据库文件如下:

/data/data/com.android.providers.media/databases # ls

external.db external.db-shm external.db-wal internal.db

该数据库文件的查看方式:

以 root 权限进入 adb shell,使用 sqlite3 打开位于手机上 /data/data/com.android.providers.media/databases 上的一个数据库。以 external 开头的数据库存储的是 SD 卡媒体信息,一张卡对应一个,所以如果手机使用过多张卡会有多个数据库。以 internal 开头的数据库存储手机内部存储器的媒体信息。因为一般用户无法访问手机内部存储器,而且这两个数据库结构是大体上是相同的,所以只需要关注 external 数据库即可。

Note: 数据库都是以类似 external-ffffffff.db 的形式命名的, 后面的 8 个 16 进制字符是该 SD 卡 FAT 分区的 Volume ID。该 ID 是分区时决定的,只有重新分区或者手动改变才会更改,可以防止插入不同 SD 卡时数据库冲突。要简单了解 FAT 文件系统请看 Understanding FAT Filesystems

2.3.2. 数据库表、视图、索引、触发器列表

sqlite3 external.db 中 表、视图、索引、触发器列表如下:

MediaProvider流程分析_第3张图片

我是用的sqlite的数据库软件直接连接DB文件进行查看的,类似的sqlite、mysql的查看工具非常多,比如 navicat 还可以直接生成ER关系图。还有visio 的逆向工程、mysql workbench 也可以生成ER关系图。

2.3.3. 各数据表的简要介绍

如下以 external.db 进行分析.

Table:files 这个表里面存放了所有的文件/文件夹的信息, 而其他表则存放了各自有针对的信息, 比如截屏图片文件既存放在了files表里,也存放在了images视图里.而各种表之间的关联基本上是通过 TRIGGER 来管理的.要查看表结构请使用 .schema进行直接查看,这些表有些复杂,其创建位置在packages/providers/MediaProvider/src/com/android/providers/media/MediaProvider.java

图片数据库

images:图片信息

thumbnails:缩略图

视频数据库

video:视频信息

videothumbnails:视频缩略图

音频数据库

album_art:专辑封面

albums:专辑信息

android_metadata:当前字符编码

artists:艺术家

audio_genres:流派

audio_genres_map:音频流派映射

audio_meta:音频信息

audio_playlists:播放列表

audio_playlists_map:音频播放列表映射

每一个表里面的字段可以用如下命令查看:

方法1: select * from sqlite_master where type=“table”

方法2: .schema

3. MediaProvider中重要文件介绍

3.1. 直接管理数据库的MediaProvider.java

文件路径:

MediaProvider/src/com/android/providers/media/MediaProvider.java

MediaProvider.java就是创建数据库,对外提供URI以实现对数据库的增删改查功能以及URI管理。直接与数据库打交道。如果要修改数据库中的字段,则可以在此文件中修改。比如现在流行的在拍照的时候记录下城市信息,则可以做如下修改:

db.execSQL("CREATE VIEW images AS SELECT _id,_data,_size,_display_name,mime_type,title,"
- + "date_added,date_modified,description,picasa_id,isprivate,latitude,longitude,"
+ + "date_added,date_modified,description,picasa_id,isprivate,city,latitude,longitude,"
+ "datetaken,orientation,mini_thumb_magic,bucket_id,bucket_display_name,width,"
+ "height FROM files WHERE media_type=1");
…
private static final String IMAGE_COLUMNS =
"_data,_size,_display_name,mime_type,title,date_added," +
- "date_modified,description,picasa_id,isprivate,latitude,longitude," +
+ "date_modified,description,picasa_id,isprivate,city,latitude,longitude," +
"datetaken,orientation,mini_thumb_magic,bucket_id,bucket_display_name," +
"width,height";

private static final String IMAGE_COLUMNSv407 =
"_data,_size,_display_name,mime_type,title,date_added," +
- "date_modified,description,picasa_id,isprivate,latitude,longitude," +
+ "date_modified,description,picasa_id,isprivate,city,latitude,longitude," +
"datetaken,orientation,mini_thumb_magic,bucket_id,bucket_display_name";

MediaProvider 另外一个非常重要的功能就是通过URI给其他应用提供数据。那么MediaProvider提供的url如下:

URI_MATCHER.addURI("media", "*/images/media", IMAGES_MEDIA);   // * 可换成 external 或者 internal , 下同
URI_MATCHER.addURI("media", "*/images/media/#", IMAGES_MEDIA_ID);
URI_MATCHER.addURI("media", "*/images/thumbnails", IMAGES_THUMBNAILS);
URI_MATCHER.addURI("media", "*/images/thumbnails/#", IMAGES_THUMBNAILS_ID);
URI_MATCHER.addURI("media", "*/audio/media", AUDIO_MEDIA);
URI_MATCHER.addURI("media", "*/audio/media/#", AUDIO_MEDIA_ID);
URI_MATCHER.addURI("media", "*/audio/media/#/genres", AUDIO_MEDIA_ID_GENRES);
URI_MATCHER.addURI("media", "*/audio/media/#/genres/#", AUDIO_MEDIA_ID_GENRES_ID);
URI_MATCHER.addURI("media", "*/audio/media/#/playlists", AUDIO_MEDIA_ID_PLAYLISTS);
URI_MATCHER.addURI("media", "*/audio/media/#/playlists/#", AUDIO_MEDIA_ID_PLAYLISTS_ID);
URI_MATCHER.addURI("media", "*/audio/genres", AUDIO_GENRES);
URI_MATCHER.addURI("media", "*/audio/genres/#", AUDIO_GENRES_ID);
URI_MATCHER.addURI("media", "*/audio/genres/#/members", AUDIO_GENRES_ID_MEMBERS);
URI_MATCHER.addURI("media", "*/audio/genres/all/members", AUDIO_GENRES_ALL_MEMBERS);
URI_MATCHER.addURI("media", "*/audio/playlists", AUDIO_PLAYLISTS);
URI_MATCHER.addURI("media", "*/audio/playlists/#", AUDIO_PLAYLISTS_ID);
URI_MATCHER.addURI("media", "*/audio/playlists/#/members", AUDIO_PLAYLISTS_ID_MEMBERS);
URI_MATCHER.addURI("media", "*/audio/playlists/#/members/#", AUDIO_PLAYLISTS_ID_MEMBERS_ID);
URI_MATCHER.addURI("media", "*/audio/artists", AUDIO_ARTISTS);
URI_MATCHER.addURI("media", "*/audio/artists/#", AUDIO_ARTISTS_ID);
URI_MATCHER.addURI("media", "*/audio/artists/#/albums", AUDIO_ARTISTS_ID_ALBUMS);
URI_MATCHER.addURI("media", "*/audio/albums", AUDIO_ALBUMS);
URI_MATCHER.addURI("media", "*/audio/albums/#", AUDIO_ALBUMS_ID);
URI_MATCHER.addURI("media", "*/audio/albumart", AUDIO_ALBUMART);
URI_MATCHER.addURI("media", "*/audio/albumart/#", AUDIO_ALBUMART_ID);
URI_MATCHER.addURI("media", "*/audio/media/#/albumart", AUDIO_ALBUMART_FILE_ID);
URI_MATCHER.addURI("media", "*/video/media", VIDEO_MEDIA);
URI_MATCHER.addURI("media", "*/video/media/#", VIDEO_MEDIA_ID);
URI_MATCHER.addURI("media", "*/video/thumbnails", VIDEO_THUMBNAILS);
URI_MATCHER.addURI("media", "*/video/thumbnails/#", VIDEO_THUMBNAILS_ID);
URI_MATCHER.addURI("media", "*/media_scanner", MEDIA_SCANNER);
URI_MATCHER.addURI("media", "*/fs_id", FS_ID);
URI_MATCHER.addURI("media", "*/version", VERSION);
URI_MATCHER.addURI("media", "*/mtp_connected", MTP_CONNECTED);
URI_MATCHER.addURI("media", "*", VOLUMES_ID);
URI_MATCHER.addURI("media", null, VOLUMES);
// Used by MTP implementation
URI_MATCHER.addURI("media", "*/file", FILES);
URI_MATCHER.addURI("media", "*/file/#", FILES_ID);
URI_MATCHER.addURI("media", "*/object", MTP_OBJECTS);
URI_MATCHER.addURI("media", "*/object/#", MTP_OBJECTS_ID);
URI_MATCHER.addURI("media", "*/object/#/references", MTP_OBJECT_REFERENCES);
// Used only to trigger special logic for directories
URI_MATCHER.addURI("media", "*/dir", FILES_DIRECTORY);
/**
* @deprecated use the 'basic' or 'fancy' search Uris instead
*/
URI_MATCHER.addURI("media", "*/audio/" + SearchManager.SUGGEST_URI_PATH_QUERY,
AUDIO_SEARCH_LEGACY);
URI_MATCHER.addURI("media", "*/audio/" + SearchManager.SUGGEST_URI_PATH_QUERY + "/*",
AUDIO_SEARCH_LEGACY);
// used for search suggestions
URI_MATCHER.addURI("media", "*/audio/search/" + SearchManager.SUGGEST_URI_PATH_QUERY,
AUDIO_SEARCH_BASIC);
URI_MATCHER.addURI("media", "*/audio/search/" + SearchManager.SUGGEST_URI_PATH_QUERY +
"/*", AUDIO_SEARCH_BASIC);

// used by the music app's search activity
URI_MATCHER.addURI("media", "*/audio/search/fancy", AUDIO_SEARCH_FANCY);
URI_MATCHER.addURI("media", "*/audio/search/fancy/*", AUDIO_SEARCH_FANCY);

在命令行中可以直接通过content query进行数据的查询,比如常用的:

adb shell content query --uri content://media/external/file //查询外部存储器扫描到的文件详情列表

adb shell content query --uri content://media/external/dir //参照上一条

adb shell content query --uri content://media/internal/audio/media/ --where “_id=7” //查询内部存储器中 audio文件,且id 为 7 的内容

adb shell content query --uri content://media/external/images/media/ //查询外部存储器上有多少图片存储

adb shell content query --uri content://media/external/images/media/ --user 0 //只查询user id为0 的用户的外部存储器的图片列表

Tips:adb shell content query 对于分析为什么有的应用不能显示图片、音频文件、doc文件等非常有帮助。

​ 比如:在有的机种中,APP无法显示DOCX/PPTX/XLSX文件的列表,其实就是mediaprovider 在扫描到这类型文件的时候,并没有将其标识为指定的格式,而将其mime_type标识为了null。所以APP去读取指定类型文件的时候就不会列举到它。

重要成员变量与函数:

private void ensureDefaultFolders(DatabaseHelper helper, SQLiteDatabase db) {
public static int getDatabaseVersion(Context context) {
private static void createLatestSchema(SQLiteDatabase db, boolean internal) {
private static void updateDatabase(Context context, SQLiteDatabase db, boolean internal,
private boolean queryThumbnail(SQLiteQueryBuilder qb, Uri uri, String table,String column, boolean hasThumbnailId) {
public Cursor query(Uri uri, String[] projectionIn, String selection,String[] selectionArgs, String sort) {
public String getType(Uri url)
public Uri insert(Uri uri, ContentValues initialValues) {
private long insertDirectory(DatabaseHelper helper, SQLiteDatabase db, String path) {
private long insertFile(DatabaseHelper helper, Uri uri, ContentValues initialValues, int mediaType,boolean notify, ArrayList notifyRowIds) {
public int delete(Uri uri, String userWhere, String[] whereArgs) {
public int update(Uri uri, ContentValues initialValues, String userWhere,String[] whereArgs) {
private DatabaseHelper getDatabaseForUri(Uri uri) {
private Uri attachVolume(String volume) {
private static String getVolumeName(Uri uri) {

3.2. 扫描文件的入口MediaScannerReceiver.java

文件路径:

packages/providers/MediaProvider/src/com/android/providers/media/MediaScannerService.java

MediaScannerReceiver.java主要监听四个广播,分别是:

ACTION_BOOT_COMPLETED //开机完成广播

ACTION_LOCALE_CHANGED // 区域改变广播

ACTION_MEDIA_MOUNTED //磁盘挂载完成广播

ACTION_MEDIA_SCANNER_SCAN_FILE //指定扫描某个文件的广播

MediaScannerReceiver收到广播之后,收集整理数据随即就去启动 MediaScannerService.

其关键代码以及相关注释如下:

public class MediaScannerReceiver extends BroadcastReceiver {
    private final static String TAG = "MediaScannerReceiver";

    @Override
    public void onReceive(Context context, Intent intent) {
        final String action = intent.getAction();
        final Uri uri = intent.getData();
        if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
            // Scan internal only.
            scan(context, MediaProvider.INTERNAL_VOLUME);  // 开机的时候扫描内部存储区
        } else if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
            scanTranslatable(context);      // 当所在地区发生改变之后,对数据进行区域更新等操作
        } else {
            if (uri.getScheme().equals("file")) {
                Log.d(TAG, "action: " + action + " path: " + path);
                if (Intent.ACTION_MEDIA_MOUNTED.equals(action)) {
                    // scan whenever any volume is mounted
                    scan(context, MediaProvider.EXTERNAL_VOLUME);   //扫描外部分区,其实就是SD卡(分内部sd卡和外置sd卡)
                } else if (Intent.ACTION_MEDIA_SCANNER_SCAN_FILE.equals(action) &&
                        path != null && path.startsWith(externalStoragePath + "/")) {
                    scanFile(context, path);  //扫描外部存储器上的一个媒体文件
                }
            }
        }
}
    private void scan(Context context, String volume) {
        Bundle args = new Bundle();
        args.putString("volume", volume);
        context.startService(
                new Intent(context, MediaScannerService.class).putExtras(args));  //启动MediaScannerService 进行相关实际动作,同理 scanFilescanTranslatable 也是如此。
    }

    private void scanFile(Context context, String path) {
    }

    private void scanTranslatable(Context context) {
    }

3.3. 扫描文件的核心类MediaScannerService.java

MediaScannerService 是一个service ,提供两种调用方式,所有扫描文件相关的操作,都会调用这个文件函数。比如MediaScannerReceiver 在收到要求扫描的广播之后,随机启动MediaScannerService 进行扫描。这种情况主要针对全盘扫描,比如开机、挂载SD/U盘之后。

还有另外一种方式,就是直接bind 此service以进行针对某一个文件的扫描,此时需要通过

文件 MediaScannerConnection.java 来配合。MediaScannerConnection.java 主要实现bind MediaScannerService ,而其他应用则可以直接通过如下方式实现扫描。

packages/apps/Gallery2/src/com/android/gallery3d/ingest/IngestService.java

mScannerConnection = new MediaScannerConnection(context, this);

mScannerConnection.scanFile(path, null);

packages/apps/Bluetooth/src/com/android/bluetooth/opp/BluetoothOppService.java

mConnection = new MediaScannerConnection(mContext, this);

mConnection.scanFile(mInfo.mFilename, mInfo.mMimetype);

负责扫描媒体文件,然后将扫描得到的信息插入到媒体数据库中。

关键核心的代码如下:

public class MediaScannerService extends Service implements Runnable {
private static final String TAG = "MediaScannerService";
...
    public void onCreate() {
        PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
...
        Thread thr = new Thread(null, this, "MediaScannerService");  //本身MediaScannerService  是一个runnable, 此处创建一个thread,来实现MediaScannerService  的功能。
        thr.start();
}
    public void run() {
...
        mServiceLooper = Looper.myLooper();
        mServiceHandler = new ServiceHandler();

        Looper.loop();
}
    private final class ServiceHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            Bundle arguments = (Bundle) msg.obj;
....
                    String volume = arguments.getString("volume");
                    String[] directories = null;

                    if (MediaProvider.INTERNAL_VOLUME.equals(volume)) {
                        // scan internal media storage
                        directories = new String[] {
                                Environment.getRootDirectory() + "/media",
                                Environment.getOemDirectory() + "/media",
                                Environment.getProductDirectory() + "/media",
                        }; //构建内部存储器的目录 directories 
                    }
                    else if (MediaProvider.EXTERNAL_VOLUME.equals(volume)) {
                        // scan external storage volumes
                        if (getSystemService(UserManager.class).isDemoUser()) {
                            directories = ArrayUtils.appendElement(String.class,
                                    mExternalStoragePaths,
                                    Environment.getDataPreloadsMediaDirectory().getAbsolutePath());
                        } else {
                            directories = mExternalStoragePaths; //如果此处插有外置sd卡,则directories 的值为:
                        }
                    }

                    if (directories != null) {
                        if (false) Log.d(TAG, "start scanning volume " + volume + ": "
                                + Arrays.toString(directories));
                        scan(directories, volume);   //对指定的路径进行扫描。
                        if (false) Log.d(TAG, "done scanning volume " + volume);
                    }
                }
...
    private void scan(String[] directories, String volumeName) {
...
            sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_STARTED, uri));  //发送 扫描开始的广播。

            try {
                if (volumeName.equals(MediaProvider.EXTERNAL_VOLUME)) {
                    openDatabase(volumeName);
                }

                try (MediaScanner scanner = new MediaScanner(this, volumeName)) {
                    scanner.scanDirectories(directories);  //调用 MediaScanner  进行扫描。
                }
        } finally {
            sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_FINISHED, uri));   //发送扫描完成的广播。
            mWakeLock.release();
        }
    }

3.4.MTP事物处理入口MtpReceiver.java

文件路径:
packages/providers/MediaProvider/src/com/android/providers/media/MtpReceiver.java
此广播接收器,只要是监听广播 ACTION_USB_STATE,实现对USB状态的监听。在监听到开机广播之后就立即再次动态注册了ACTION_USB_STATE。以实现对USB状态的变化。MtpReceiver 收到广播之后,收集整理数据随即就去启动 MtpService.

其关键代码,以及相关注释如下:

public class MtpReceiver extends BroadcastReceiver {
    private static final String TAG = MtpReceiver.class.getSimpleName();
    private static final boolean DEBUG = false;

    @Override
    public void onReceive(Context context, Intent intent) {
        final String action = intent.getAction();
        if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
            final Intent usbState = context.registerReceiver(
                    null, new IntentFilter(UsbManager.ACTION_USB_STATE));  //动态注册ACTION_USB_STATE 广播
            if (usbState != null) {
                handleUsbState(context, usbState);
            }
        } else if (UsbManager.ACTION_USB_STATE.equals(action)) {
            handleUsbState(context, intent);
        }
    }
    private void handleUsbState(Context context, Intent intent) {
        Bundle extras = intent.getExtras();
        boolean configured = extras.getBoolean(UsbManager.USB_CONFIGURED);
        boolean connected = extras.getBoolean(UsbManager.USB_CONNECTED);
        boolean mtpEnabled = extras.getBoolean(UsbManager.USB_FUNCTION_MTP);//是否启用mtp
        boolean ptpEnabled = extras.getBoolean(UsbManager.USB_FUNCTION_PTP);//是否启用ptp
        boolean unlocked = extras.getBoolean(UsbManager.USB_DATA_UNLOCKED);//是否解锁数据
        boolean isCurrentUser = UserHandle.myUserId() == ActivityManager.getCurrentUser();
//查看当前手机是否启用mtp,adb,ptp等可以通过系统属性(persist.sys.usb.config)进行查看。比如有的机型配置的是:getprop persist.sys.usb.config : adb  ,此配置则说明没有打开mtp,ptp,
        if (configured && (mtpEnabled || ptpEnabled)) {
            if (!isCurrentUser)
                return;
            context.getContentResolver().insert(Uri.parse(
"content://media/none/mtp_connected"), null);
            intent = new Intent(context, MtpService.class);
            intent.putExtra(UsbManager.USB_DATA_UNLOCKED, unlocked);  //unlocked 如果为false,则电脑上能够看到手机这个设备,但是不能看到具体内容, 如果为true则可以看到手机里面的具体内容。
            if (ptpEnabled) {
                intent.putExtra(UsbManager.USB_FUNCTION_PTP, true);
            }
            if (DEBUG) { Log.d(TAG, "handleUsbState startService"); }
            context.startService(intent);   //参数组织完成之后,启动 MtpService 。
        } else if (!connected || !(mtpEnabled || ptpEnabled)) {
            // Only unbind if disconnected or disabled.
            boolean status = context.stopService(new Intent(context, MtpService.class));
            if (DEBUG) { Log.d(TAG, "handleUsbState stopService status=" + status); }
            // tell MediaProvider MTP is disconnected so it can unbind from the service
            context.getContentResolver().delete(Uri.parse(
"content://media/none/mtp_connected"), null, null);
        }
    }

从函数handleUsbState()中可以看出,其实就是监听usb state广播,为启动MtpService 组织参数。

3.5. 与PC通信关键类MTPService.java

3.5.1. MTP相关代码

packages/providers/MediaProvider/src/com/android/providers/media/

MediaProvider.java

MediaScannerReceiver.java

MediaScannerService.java

MediaUpgradeReceiver.java

MtpReceiver.java

MtpService.java

frameworks/base/media/java/android/media/

MediaScanner.java

./frameworks/base/media/java/android/mtp/

MtpDatabase.java

MtpServer.java

MtpStorage.java

MtpStorageInfo.java

frameworks/base/media/jni/

android_media_MediaScanner.cpp

android_mtp_MtpDatabase.cpp

android_mtp_MtpServer.cpp

frameworks/av/media/mtp/

MtpServer.h

MtpServer.cpp

3.5.2. MTP协议

手机和电脑连接之后,可以通过MTP模式进行传输数据,这是由于Android 和电脑之间都支持了MTP协议。MTP协议简单介绍如下(抄自网络):

媒体传输协议(英语:MediaTransferProtocol,缩写:MTP)是一个基于图片传输协议(PictureTransferProtocol,PTP)的自定义扩展协议。该协议允许用户在移动设备上线性访问媒体文件。PTP只是被设计用于从数码相机下载照片,而MTP可以支持数字音频播放器上的音乐文件和便携式媒体播放器上的媒体文件,以及个人数字助理的个人信息的传输。MTP是WMDRM10-PD的一个关键部分,而WMDRM10-PD是WindowsMedia的一项数字版权管理(DRM)服务。

媒体传输协议(即通常所说的MTP)是“WindowsMedia”框架的一部分,从而与WindowsMediaPlayer密切相关。Windows系统从WindowsXPSP2开始支持MTP。WindowsXP需要安装WindowsMediaPlayer10或更高版本来获得对MTP的支持。在这之后的系统则原生支持MTP。微软同时向Windows98之后的旧有操作系统提供MTP驱动包。OSX和Linux各自拥有可支持MTP的升级软件包.

USB开发者论坛在2008年5月将MTP标准化为一个成熟的USB类。自此以后MTP成为PTP的官

方扩展,两者拥有相同的类代码。

3.5.3. Android MTP 介绍

从上一节可以看到,当手机接上USB之后,MTPReceiver 会收到 ACTION_USB_STATE 并且检查参数之后启动MTPService。

那么MTPService启动的时候关键代码请看如下:

packages/providers/MediaProvider/src/com/android/providers/media/MtpService.java
    @Override
    public void onCreate() {
        mVolumes = StorageManager.getVolumeList(getUserId(), 0);
        mVolumeMap = new HashMap<>();//map 为后续保存挂载分区做准备,

        mStorageManager = this.getSystemService(StorageManager.class);
        mStorageManager.registerListener(mStorageEventListener); //注册存储的监听器,当手机存储有任何变化之后,都能够通过MTP协议反馈到PC端。
    }


    private synchronized void startServer(StorageVolume primary, String[] subdirs) {
            final MtpDatabase database = new MtpDatabase(this, MediaProvider.EXTERNAL_VOLUME, subdirs);   //创建 MTPDataBase
...
            final MtpServer server =
                    new MtpServer(database, fd, mPtpMode,
                            new OnServerTerminated(), Build.MANUFACTURER,
                            Build.MODEL, "1.0", deviceSerialNumber);
            database.setServer(server);
            sServerHolder = new ServerHolder(server, database);
            // Add currently mounted and enabled storages to the server
            if (mUnlocked) {  //只有在 data unlock情况下,才会将手机里面的存储卷挂载到MTP协议上
                if (mPtpMode) {
                    addStorage(primary);
                } else {
                    for (StorageVolume v : mVolumeMap.values()) {
                        addStorage(v);
                    }
                }
            }
            server.start();    // MTPServer 启动
        }
}
    private void addStorage(StorageVolume volume) {
        Log.v(TAG, "Adding MTP storage:" + volume.getPath());
.....
         sServerHolder.database.addStorage(volume);  //调用 MtpDatabase 进行添加存储卷到MTP协议
    }
    public synchronized int onStartCommand(Intent intent, int flags, int startId) {
        mUnlocked = intent.getBooleanExtra(UsbManager.USB_DATA_UNLOCKED, false); //读取intent的参数,这关系到mtp模式是否能够被成功打开。
        mPtpMode = intent.getBooleanExtra(UsbManager.USB_FUNCTION_PTP, false);

        for (StorageVolume v : mVolumes) {
            if (v.getState().equals(Environment.MEDIA_MOUNTED)) {
                mVolumeMap.put(v.getPath(), v); //读取所有的磁盘卷
            }
        }
.....
      final StorageVolume primary = StorageManager.getPrimaryVolume(mVolumes);
        startServer(primary, subdirs);  //启动MTP模式中关键服务:MTPServer。
        return START_REDELIVER_INTENT;
    }

MTP启动一级添加存储卷流程图如下:

MediaProvider流程分析_第4张图片

frameworks/base/media/java/android/mtp/MtpStorageManager.java 监听到,代码如下:

45  public class MtpStorageManager {
55      private class MtpObjectObserver extends FileObserver {
65          public void onEvent(int event, String path) {
66              synchronized (MtpStorageManager.this) {
71                  MtpObject obj = mObject.getChild(path);
72                  if ((event & MOVED_TO) != 0 || (event & CREATE) != 0) {
75                      handleAddedObject(mObject, path, (event & IN_ISDIR) != 0);//添加文件操作
76                  } else if ((event & MOVED_FROM) != 0 || (event & DELETE) != 0) {
83                      handleRemovedObject(obj);  //删除文件操作
84                  } else if ((event & IN_IGNORED) != 0) {
85                      if (sDebug)
86                          Log.i(TAG, "inotify for " + mObject.getPath() + " deleted");
87                      if (mObject.mObserver != null)
88                          mObject.mObserver.stopWatching();
89                      mObject.mObserver = null;
90                  } 

MTP模式下保存的文件,在文件保存完毕之后即马上调用mMediaProvider insert数据,以实现了MTP与MediaProvider之间关于数据的记录共享。

frameworks/base/media/java/android/mtp/MtpDatabase.java
373      private int beginSendObject(String path, int format, int parent, int storageId) {
....
382      }
384      private void endSendObject(int handle, boolean succeeded) {
385          MtpStorageManager.MtpObject obj = mManager.getObject(handle);
390          // Add the new file to MediaProvider
391          if (succeeded) {
413                  Uri uri = mMediaProvider.insert(mObjectsUri, values);//在收到文件传输结束的命令之后,直接调用insert命令保存相关信息到MediaProvider中

3.6. 数据库升级类MediaUpgradeReceiver

文件路径:

packages/providers/MediaProvider/src/com/android/providers/media/MediaUpgradeReceiver.java

在系统启动阶段,核心模块启动完成之后,而开机并没有完成之前,会读取apk的版本,其实就是 android:versionCode。 如果两个版本相同,则不进行任何操作,如果和之前的版本不一样则会进行数据升级的相关操作。对于OSV升级过程中,需要对数据库的字段进行升级的时候,可以在这里进行操作,以实现数据的完美继承。MediaUpgradeReceiver 在开机的时候会被启动起来,以实现数据库的升级,更新。

4. 常见MediaProvider问题

4.1. 截屏文件如何存入MediaProvider数据库中的

MediaProvider提供了MediaScannerService类,来对文件进行扫描, 但是还有另外一种使用更广泛的方式,就是直接用ContentProvider将图片信息存进MediaProvider的数据库中。截屏就是这种方式实现的,相关代码如下:

frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java
1806      private class ScreenshotRunnable implements Runnable {   //ScreenshotRunnable  是一个异步执行类,其在什么情况下以及如何调用的,比较简单,这里就不描述了,直接进入存数据的环节
1807          private int mScreenshotType = TAKE_SCREENSHOT_FULLSCREEN;
1808  
1809          public void setScreenshotType(int screenshotType) {
1810              mScreenshotType = screenshotType;
1811          }
1812  
1813          @Override
1814          public void run() {
1815              mScreenshotHelper.takeScreenshot(mScreenshotType,
1816                      mStatusBar != null && mStatusBar.isVisibleLw(),
1817                      mNavigationBar != null && mNavigationBar.isVisibleLw(), mHandler);
1818          }
PhoneWindowManager 收到截屏事件之后调用mScreenshotHelper进行截屏准备,

frameworks/base/core/java/com/android/internal/util/ScreenshotHelper.java
19      private static final String SYSUI_PACKAGE = "com.android.systemui";
20      private static final String SYSUI_SCREENSHOT_SERVICE =
21              "com.android.systemui.screenshot.TakeScreenshotService";
...
52              final ComponentName serviceComponent = new ComponentName(SYSUI_PACKAGE,
53                      SYSUI_SCREENSHOT_SERVICE);
54              final Intent serviceIntent = new Intent();
55  //绑定服务 TakeScreenshotService 实现截屏。
56              final Runnable mScreenshotTimeout = new Runnable() {
57                  @Override public void run() {
58                      synchronized (mScreenshotLock) {
59                          if (mScreenshotConnection != null) {
60                              mContext.unbindService(mScreenshotConnection);
61                              mScreenshotConnection = null;
62                              notifyScreenshotError();
63                          }
64                      }
65                  }
66              };

frameworks/base/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
59              if (mScreenshot == null) {
60                  mScreenshot = new GlobalScreenshot(TakeScreenshotService.this);
61              }
62  
63              switch (msg.what) {
64                  case WindowManager.TAKE_SCREENSHOT_FULLSCREEN:
65                      mScreenshot.takeScreenshot(finisher, msg.arg1 > 0, msg.arg2 > 0);  //TakeScreenshotService 收到截屏要求,调用GlobalScreenshot 进行截屏

frameworks/base/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
545      private void saveScreenshotInWorkerThread(Runnable finisher) {
546          SaveImageInBackgroundData data = new SaveImageInBackgroundData();
547          data.context = mContext;
548          data.image = mScreenBitmap;
549          data.iconSize = mNotificationIconSize;
550          data.finisher = finisher;
551          data.previewWidth = mPreviewWidth;
552          data.previewheight = mPreviewHeight;
553          if (mSaveInBgTask != null) {
554              mSaveInBgTask.cancel(false);
555          }
556          mSaveInBgTask = new SaveImageInBackgroundTask(mContext, data, mNotificationManager)
557                  .execute();   //启动异步调用 SaveImageInBackgroundTask  进行保存相关的数据,
558      }
...
114  class SaveImageInBackgroundTask extends AsyncTask {
115      private static final String TAG = "SaveImageInBackgroundTask";
...
234      protected Void doInBackground(Void... params) {
262              ContentValues values = new ContentValues();  //开始组织需要保存数据的各项参数
263              ContentResolver resolver = context.getContentResolver();
264              values.put(MediaStore.Images.ImageColumns.DATA, mImageFilePath);
265              values.put(MediaStore.Images.ImageColumns.TITLE, mImageFileName);
266              values.put(MediaStore.Images.ImageColumns.DISPLAY_NAME, mImageFileName);
267              values.put(MediaStore.Images.ImageColumns.DATE_TAKEN, mImageTime);
268              values.put(MediaStore.Images.ImageColumns.DATE_ADDED, dateSeconds);
269              values.put(MediaStore.Images.ImageColumns.DATE_MODIFIED, dateSeconds);
270              values.put(MediaStore.Images.ImageColumns.MIME_TYPE, "image/png");
271              values.put(MediaStore.Images.ImageColumns.WIDTH, mImageWidth);
272              values.put(MediaStore.Images.ImageColumns.HEIGHT, mImageHeight);
273              values.put(MediaStore.Images.ImageColumns.SIZE, new File(mImageFilePath).length());
274              Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);    //最后往数据库中存储数据。


5. 总结

整个MediaProvider流程比较简单,稍稍复杂一点的就是base库下面的MediaScan、调用native那部分以及MTP那部分。从前面的流程图中也可以看到,native层主要实现了对路径的遍历寻找,而java层则实现对检查到的结果进行识别imie type 然后插入到数据库中。

MTP这块有较大部分的MTP协议,当通过MTP发送、删除一个手机上面文件的时候, 都是会通过协议调用到 MtpDatabase 以实现更新数据库。涉及到MTP协议部分还比较模糊,如果有这方面需求还需要更进一步分析。

你可能感兴趣的:(android)