MediaProvider分析

MediaProvider在android源代码的packages目录下,作为一个系统的ContentProvider,给外部提供二维表访问的方法。

根据常识,当sd卡,松动时我们是不能播放到sd卡的资源的,所以判断是不是有个广播进行事件监听sd卡的状态。儿系统广播一般会在文件注册,在android.mainfest.xml文件中,我们看到:

 <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>

当满足这三个系统的action时,intentfilter动能匹配成功,查阅android文档发现:分别是开机,加载sd卡,浏览文件三种广播事件。接下来查看MediaScannerReceiver的具体实现,它重写的onReceive方法,

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 both internal and external storage
            scan(context, MediaProvider.INTERNAL_VOLUME);
            scan(context, MediaProvider.EXTERNAL_VOLUME);

        } else {
            if (uri.getScheme().equals("file")) {
                // handle intents related to external storage
                String path = uri.getPath();
                String externalStoragePath = Environment.getExternalStorageDirectory().getPath();
                String legacyPath = Environment.getLegacyExternalStorageDirectory().getPath();
                // [5700][Media][Jazz] Burst shoot photos scattered by gallery can be scanned by scanner [Begin]
                String sdcardPath = null;
                // [5700][Media][Jazz] Burst shoot photos scattered by gallery can be scanned by scanner [End]

                try {
                    path = new File(path).getCanonicalPath();
                } catch (IOException e) {
                    Log.e(TAG, "couldn't canonicalize " + path);
                    return;
                }
                if (path.startsWith(legacyPath)) {
                    path = externalStoragePath + path.substring(legacyPath.length());
                }

                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);
                // [5700][Media][Jazz] Burst shoot photos scattered by gallery can be scanned by scanner [Begin]
                } else if (Intent.ACTION_MEDIA_SCANNER_SCAN_FILE.equals(action)) {
                    if (path != null) {
                        if (path.startsWith(externalStoragePath + "/")) {
                            scanFile(context, path);
                        } else {
                            StorageManager storageManager =
                                (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
                            ArrayList externalVolumes =
                                storageManager.getPhysicalExternalVolume(
                                    storageManager.getVolumeList());

                            for (StorageVolume vol : externalVolumes) {
                                sdcardPath = vol.getPath();
                                if (sdcardPath != null && path.startsWith(sdcardPath + "/")) {
                                    scanFile(context, path);
                                    break;
                                }
                            }
                        }
                    }
                }
                // [5700][Media][Jazz] Burst shoot photos scattered by gallery can be scanned by scanner [End]
            }
        }
    }

    private void scan(Context context, String volume) {
        Bundle args = new Bundle();
        args.putString("volume", volume);
        context.startService(
                new Intent(context, MediaScannerService.class).putExtras(args));
    }    

    private void scanFile(Context context, String path) {
        Bundle args = new Bundle();
        args.putString("filepath", path);
        context.startService(
                new Intent(context, MediaScannerService.class).putExtras(args));
    }    

如果是开机启动,那么就开启MediaScannerService这个服务,扫描内部存储和外部存储的sqlite内容,作为第二个参数通知过去。
在else语句里,看到externalStoragePath表示的内置sd卡的文件路径,legacyPath表示的是外置sd卡的路径,查看Environment.java

/** {@hide} */
    public static File getLegacyExternalStorageDirectory() {
        return new File(System.getenv(ENV_EXTERNAL_STORAGE));
    }
         @Deprecated
        public File getExternalStorageDirectory() {
            return mExternalDirsForApp[0];
        }

在后续的代码里,对path进行获取,如果是外置sd卡,path需要怎样处理。
这里有个StorageVolume,storageManager,受MountService的管控,
最后看scanfile这个方法,相比于scan,他把文件路径传递给了MediaScannerService。。。
到此为止广播接收者的处理完成了,进入到MediaScannerService类,一股脑复制过来
public class MediaScannerService extends Service implements Runnable
{
private static final String TAG = “MediaScannerService”;

private volatile Looper mServiceLooper;
private volatile ServiceHandler mServiceHandler;
private PowerManager.WakeLock mWakeLock;
private String[] mExternalStoragePaths;

private void openDatabase(String volumeName) {
    try {
        ContentValues values = new ContentValues();
        values.put("name", volumeName);
        getContentResolver().insert(Uri.parse("content://media/"), values);
    } catch (IllegalArgumentException ex) {
        Log.w(TAG, "failed to open media database");
    }         
}

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;
}

private void scan(String[] directories, String volumeName) {
    Uri uri = Uri.parse("file://" + directories[0]);
    // don't sleep while scanning
    mWakeLock.acquire();

    try {
        ContentValues values = new ContentValues();
        values.put(MediaStore.MEDIA_SCANNER_VOLUME, volumeName);
        Uri scanUri = getContentResolver().insert(MediaStore.getMediaScannerUri(), values);

        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);

    } finally {
        sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_FINISHED, uri));
        mWakeLock.release();
    }
}

@Override
public void onCreate()
{
    PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
    mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
    StorageManager storageManager = (StorageManager)getSystemService(Context.STORAGE_SERVICE);
    mExternalStoragePaths = storageManager.getVolumePaths();

    // Start up the thread running the service.  Note that we create a
    // separate thread because the service normally runs in the process's
    // main thread, which we don't want to block.
    Thread thr = new Thread(null, this, "MediaScannerService");
    thr.start();
}

@Override
public int onStartCommand(Intent intent, int flags, int startId)
{
    while (mServiceHandler == null) {
        synchronized (this) {
            try {
                wait(100);
            } catch (InterruptedException e) {
            }
        }
    }

    if (intent == null) {
        Log.e(TAG, "Intent is null in onStartCommand: ",
            new NullPointerException());
        return Service.START_NOT_STICKY;
    }

    Message msg = mServiceHandler.obtainMessage();
    msg.arg1 = startId;
    msg.obj = intent.getExtras();
    mServiceHandler.sendMessage(msg);

    // Try again later if we are killed before we can finish scanning.
    return Service.START_REDELIVER_INTENT;
}

@Override
public void onDestroy()
{
    // Make sure thread has started before telling it to quit.
    while (mServiceLooper == null) {
        synchronized (this) {
            try {
                wait(100);
            } catch (InterruptedException e) {
            }
        }
    }
    mServiceLooper.quit();
}

public void run()
{
    // reduce priority below other background threads to avoid interfering
    // with other services at boot time.
    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND +
            Process.THREAD_PRIORITY_LESS_FAVORABLE);
    Looper.prepare();

    mServiceLooper = Looper.myLooper();
    mServiceHandler = new ServiceHandler();

    Looper.loop();
}

private Uri scanFile(String path, String mimeType) {
    String volumeName = MediaProvider.EXTERNAL_VOLUME;
    openDatabase(volumeName);
    MediaScanner scanner = createMediaScanner();
    try {
        // make sure the file path is in canonical form
        String canonicalPath = new File(path).getCanonicalPath();
        return scanner.scanSingleFile(canonicalPath, volumeName, mimeType);
    } catch (Exception e) {
        Log.e(TAG, "bad path " + path + " in scanFile()", e);
        return null;
    }
}

@Override
public IBinder onBind(Intent intent)
{
    return mBinder;
}

private final IMediaScannerService.Stub mBinder = 
        new IMediaScannerService.Stub() {
    public void requestScanFile(String path, String mimeType, IMediaScannerListener listener)
    {
        if (false) {
            Log.d(TAG, "IMediaScannerService.scanFile: " + path + " mimeType: " + mimeType);
        }
        Bundle args = new Bundle();
        args.putString("filepath", path);
        args.putString("mimetype", mimeType);
        if (listener != null) {
            args.putIBinder("listener", listener.asBinder());
        }
        startService(new Intent(MediaScannerService.this,
                MediaScannerService.class).putExtras(args));
    }

    public void scanFile(String path, String mimeType) {
        requestScanFile(path, mimeType, null);
    }
};

private final class ServiceHandler extends Handler
{
    @Override
    public void handleMessage(Message msg)
    {
        Bundle arguments = (Bundle) msg.obj;
        String filePath = arguments.getString("filepath");

        try {
            if (filePath != null) {
                IBinder binder = arguments.getIBinder("listener");
                IMediaScannerListener listener = 
                        (binder == null ? null : IMediaScannerListener.Stub.asInterface(binder));
                Uri uri = null;
                try {
                    uri = scanFile(filePath, arguments.getString("mimetype"));
                } catch (Exception e) {
                    Log.e(TAG, "Exception scanning file", e);
                }
                if (listener != null) {
                    listener.scanCompleted(filePath, uri);
                }
            } else {
                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",
                    };
                }
                else if (MediaProvider.EXTERNAL_VOLUME.equals(volume)) {
                    // scan external storage volumes
                    directories = mExternalStoragePaths;
                }

                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);
                }
            }
        } catch (Exception e) {
            Log.e(TAG, "Exception in handleMessage", e);
        }

        stopSelf(msg.arg1);
    }
};

}

得出uml图,借鉴别人的
MediaProvider分析_第1张图片
MediaScannerService是一个Service,并实现Runnable,实现工作线程。
MediaScannerService通过ServiceHandler这个Handler把主线程需要大量计算的工作放到工作线程中去做。
在Runnable.run()中执行消息循环,把通过Handler发送过来的消息在工作线程中执行。
onCreate方法中, 为了防止在媒体扫描过程中,CPU睡死过去,用PowerManager的WakeLock告诉PowerManager,我这边还在忙,别睡死了[
在Android的主线程中要快速返回,大量的计算任务交给工作线程去做,这里启了一个工作线程,而这个线程的执行体就是MediaScannerService所实现Runnable的run()方法,用Handler发消息之前,一定要先启动该线程的 。
这里scan和scanFile一个是全盘扫描,一个对特定目录扫描。有兴趣可以查看相关代码。

你可能感兴趣的:(Android)