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