Android MediaScanner深入研究

来源:互联网 发布:dota2手机直播软件 编辑:程序博客网 时间:2024/05/12 03:06


http://www.apkbus.com/android-5376-1-1.html


MediaScanner分析

  一 MediaScannerService


  多媒体扫描是从MediaScannerService开始的。这是一个单独的package。位于packagesprovidersMediaProvider:含以下java文件

java代码:


  1. MediaProvider.java
  2. MediaScannerReceiver.java
  3. MediaScannerService.java
  4. MediaThumbRequest.java
复制代码


  分析这个目录的Android.mk文件,发现它运行的进程名字就是android.process.media。
application android:process=android.process.media

  1.1 MediaScannerReceiver

  这个类从BroadcastReceiver中派生,用来接收任务的。MediaScannerReceiver extends BroadcastReceiver
在它重载的onRecieve函数内有以下几种走向:

java代码:


  1. if (action.equals(Intent.ACTION_BOOT_COMPLETED)) {

  2. // 收到”启动完毕“广播后,扫描内部存储
  3. scan(context, MediaProvider.INTERNAL_VOLUME);
  4. } else {
  5. if (action.equals(Intent.ACTION_MEDIA_MOUNTED) &&
  6. externalStoragePath.equals(path)) {
  7. /收到MOUNT信息后,扫描外部存储
  8. scan(context, MediaProvider.EXTERNAL_VOLUME);
  9. }
  10. else if (action.equals(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE) &&
  11. path != null && path.startsWith(externalStoragePath + "/")) {
  12. //收到请求要求扫描某个文件,注意不会扫描内部存储上的文件
  13. scanFile(context, path);

  14. }
复制代码


下面是它调用的scan函数:

java代码:


  1. scan(Context context, String volume)

  2. Bundle args = new Bundle();
  3. args.putString("volume", volume);
  4. //直接启动MediaScannerService了,
  5. context.startService(new Intent(context, MediaScannerService.class).putExtras(args));
复制代码


   总结:

  MediaScannerReceiver是用来接收任务的,它收到广播后,会启动MediaService进行扫描工作。
  下面看看MediaScannerService.

  1.2 MediaScannerService

       MSS标准的从Service中派生下来,
  MediaScannerService extends Service implements Runnable
  //注意:是一个Runnable…,可能有线程之类的东西存在
  下面从Service的生命周期的角度来看看它的工作。

       1. onCreate

java代码:


  1. public void onCreate()

  2. PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
  3. mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
  4. //获得电源锁,防止在扫描过程中休眠
  5. //单独搞一个线程去跑扫描工作,防止ANR
  6. Thread thr = new Thread(null, this, "MediaScannerService");
  7. thr.start();
复制代码


  2. onStartCommand

java代码:


  1. @Override
  2. public int onStartCommand(Intent intent, int flags, int startId){

  3. //注意这个handler,是在另外一个线程中创建的,往这个handler里sendMessage
  4. //都会在那个线程里边处理
  5. //不明白的可以去查看handler和Looper机制
  6. //这里就是同步机制,等待mServiceHandler在另外那个线程创建完毕

  7. while (mServiceHandler == null) {
  8. synchronized (this) {
  9. try {
  10. wait(100);
  11. } catch (InterruptedException e) {
  12. }
  13. }
  14. }

  15. if (intent == null) {
  16. Log.e(TAG, "Intent is null in onStartCommand: ",new NullPointerException());
  17. return Service.START_NOT_STICKY;
  18. }
  19. Message msg = mServiceHandler.obtainMessage();
  20. msg.arg1 = startId;
  21. msg.obj = intent.getExtras();
  22. //把MediaScannerReceiver发出的消息传递到另外那个线程去处理。

  23. mServiceHandler.sendMessage(msg);
复制代码



     3. run

java代码:


  1. public void run(){

  2. // reduce priority below other background threads to avoid interfering
  3. // with other services at boot time.

  4. Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND +Process.THREAD_PRIORITY_LESS_FAVORABLE);

  5. //不明白的去看看Looper和handler的实现
  6. Looper.prepare();
  7. //把这个looper对象设置到线程本地存储
  8. mServiceLooper = Looper.myLooper();
  9. mServiceHandler = new ServiceHandler();
  10. //创建handler,默认会把这个looper
  11. //的消息队列赋值给handler的消息队列,这样往handler中发送消息就是往这个线程的looper发
  12. Looper.loop();
  13. //消息循环,内部会处理消息队列中的消息
  14. //也就是handleMessage函数
  15. }
复制代码


    上面handler中加入了一个扫描请求(假设是外部存储的),所以要分析handleMessage函数。

     4. handleMessage

java代码:


  1. private final class ServiceHandler extends Handler{

  2. @Override
  3. public void handleMessage(Message msg){

  4. Bundle arguments = (Bundle) msg.obj;
  5. String filePath = arguments.getString("filepath");
  6. try {

  7. 这里不讲了
  8. } else {
  9. String volume = arguments.getString("volume");
  10. String[] directories = null;
  11. if (MediaProvider.INTERNAL_VOLUME.equals(volume)) {
  12. //是扫描内部存储的请求?
  13. // scan internal media storage
  14. directories = new String[] {

  15. Environment.getRootDirectory() + "/media",
  16. };
  17. }

  18. else if (MediaProvider.EXTERNAL_VOLUME.equals(volume)) {
  19. //是扫描外部存储的请求?获取外部存储的路径
  20. directories = new String[] {
  21. Environment.getExternalStorageDirectory().getPath(),
  22. };
  23. }
  24. if (directories != null) {
  25. //真正的扫描开始了,上面只不过是把存储路径取出来罢了.
  26. scan(directories, volume);
  27. //扫描完了,就把service停止了
  28. stopSelf(msg.arg1);
  29. }

  30. };
复制代码


    5. scan函数

java代码:


  1. private void scan(String[] directories, String volumeName) {
  2. mWakeLock.acquire();
  3. //下面这三句话很深奥…
  4. //从 getContentResolver获得一个ContentResover,然后直接插入
  5. //根据AIDL,这个ContentResover的另一端是MediaProvider。只要去看看它的
  6. //insert函数就可以了
  7. //反正这里知道获得了一个扫描URI即可。
  8. ContentValues values = new ContentValues();
  9. values.put(MediaStore.MEDIA_SCANNER_VOLUME, volumeName);
  10. Uri scanUri = getContentResolver().insert(MediaStore.getMediaScannerUri(), values);
  11. Uri uri = Uri.parse("file://" + directories[0]);
  12. //发送广播,通知扫描开始了
  13. sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_STARTED, uri));

  14. try {
  15. if (volumeName.equals(MediaProvider.EXTERNAL_VOLUME)) {
  16. openDatabase(volumeName);
  17. }
  18. //创建真正的扫描器
  19. MediaScanner scanner = createMediaScanner();
  20. //交给扫描器去扫描文件夹 scanDirectories
  21. scanner.scanDirectories(directories, volumeName);
  22. } catch (Exception e) {
  23. Log.e(TAG, "exception in MediaScanner.scan()", e);
  24. }
  25. //删除扫描路径
  26. getContentResolver().delete(scanUri, null, null);
  27. //通知扫描完毕
  28. sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_FINISHED, uri));
  29. mWakeLock.release();
  30. }
复制代码


  说说上面那个深奥的地方,在MediaProvider中重载了insert函数,insert函数会调用insertInternal函数。
  如下:

java代码:


  1. private Uri insertInternal(Uri uri, ContentValues initialValues) {
  2. long rowId;

  3. int match = URI_MATCHER.match(uri);
  4. // handle MEDIA_SCANNER before calling getDatabaseForUri()
  5. //刚才那个insert只会走下面这个分支,其实就是获得一个地址….
  6. //太绕了

  7. if (match == MEDIA_SCANNER) {
  8. mMediaScannerVolume = initialValues.getAsString(MediaStore.MEDIA_SCANNER_VOLUME);
  9. return MediaStore.getMediaScannerUri();
  10. }
复制代码


   再看看它创建了什么样的Scanner,这就是MSS中的createMediaScanner

java代码:


  1. private MediaScanner createMediaScanner() {

  2. //下面这个MediaScanner在framework/base/中,待会再分析
  3. MediaScanner scanner = new MediaScanner(this);
  4. //设置当前的区域,这个和字符编码有重大关系。
  5. Locale locale = getResources().getConfiguration().locale;

  6. if (locale != null) {
  7. String language = locale.getLanguage();
  8. String country = locale.getCountry();
  9. String localeString = null;
  10. if (language != null) {
  11. if (country != null) {
  12. //给扫描器设置当前国家和语言。
  13. scanner.setLocale(language + "_" + country);
  14. } else {
  15. scanner.setLocale(language);
  16. }
  17. }
  18. }
  19. return scanner;

  20. }
复制代码


至此,MSS的任务完成了。接下来是MediaScanner的工作了。
6. 总结

       MSS的工作流程如下:

  1 单独启动一个带消息循环的工作线程。
  2 主线程接收系统发来的任务,然后发送给工作线程去处理。
  3 工作线程接收任务,创建一个MediaScanner去扫描。
  4 MSS顺带广播一下扫描工作启动了,扫描工作完毕了。

       二 MediaScanner

  MediaScanner位置在
  frameworks asemedia下,包括jni和java文件。
  先看看java实现。
  这个类巨复杂,而且和MediaProvider交互频繁。在分析的时候要时刻回到MediaProvider去看看。
  1. 初始化

java代码:


  1. public class MediaScanner{

  2. static {
  3. //libmedia_jni.so的加载是在MediaScanner类中完成的
  4. //这么重要的so为何放在如此不起眼的地方加载???

  5. System.loadLibrary("media_jni");
  6. native_init();
  7. }

  8. public MediaScanner(Context c) {
  9. native_setup();//调用jni层的初始化,暂时不用看了,无非就是一些
  10. //初始化工作,待会在再进去看看

  11. }
复制代码



  刚才MSS中是调用scanDirectories函数,我们看看这个。

       2. scanDirectories

java代码:


  1. public void scanDirectories(String[] directories, String volumeName) {
  2. try {
  3. long start = System.currentTimeMillis();
  4. initialize(volumeName);//初始化

  5. prescan(null);//扫描前的预处理
  6. long prescan = System.currentTimeMillis();
  7. for (int i = 0; i < directories.length; i++) {

  8. //扫描文件夹,这里有一个很重要的参数 mClient
  9. // processDirectory是一个native函数
  10. processDirectory(directories[i], MediaFile.sFileExtensions, mClient);
  11. }

  12. long scan = System.currentTimeMillis();
  13. postscan(directories);//扫描后处理
  14. long end = System.currentTimeMillis();

  15. 打印时间,异常处理没了
  16. 下面简单讲讲initialize ,preScan和postScan都干嘛了。

  17. private void initialize(String volumeName) {

  18. //打开MediaProvider,获得它的一个实例

  19. mMediaProvider = mContext.getContentResolver().acquireProvider("media");
  20. //得到一些uri
  21. mAudioUri = Audio.Media.getContentUri(volumeName);
  22. mVideoUri = Video.Media.getContentUri(volumeName);
  23. mImagesUri = Images.Media.getContentUri(volumeName);
  24. mThumbsUri = Images.Thumbnails.getContentUri(volumeName);
  25. //外部存储的话,可以支持播放列表之类的东西,搞了一些个缓存池之类的
  26. //如mGenreCache等
  27. if (!volumeName.equals("internal")) {
  28. // we only support playlists on external media
  29. mProcessPlaylists = true;
  30. mGenreCache = new HashMap();
复制代码


  2. scanDirectories

java代码:


  1. public void scanDirectories(String[] directories, String volumeName) {

  2. try {
  3. long start = System.currentTimeMillis();
  4. initialize(volumeName);//初始化

  5. prescan(null);//扫描前的预处理
  6. long prescan = System.currentTimeMillis();

  7. for (int i = 0; i < directories.length; i++) {
  8. //扫描文件夹,这里有一个很重要的参数 mClient
  9. // processDirectory是一个native函数

  10. processDirectory(directories[i], MediaFile.sFileExtensions, mClient);
  11. }

  12. long scan = System.currentTimeMillis();
  13. postscan(directories);//扫描后处理
  14. long end = System.currentTimeMillis();
  15. …..打印时间,异常处理…没了…

  16. 下面简单讲讲initialize ,preScan和postScan都干嘛了。

  17. private void initialize(String volumeName) {
  18. //打开MediaProvider,获得它的一个实例

  19. mMediaProvider = mContext.getContentResolver().acquireProvider("media");
  20. //得到一些uri
  21. mAudioUri = Audio.Media.getContentUri(volumeName);
  22. mVideoUri = Video.Media.getContentUri(volumeName);
  23. mImagesUri = Images.Media.getContentUri(volumeName);
  24. mThumbsUri = Images.Thumbnails.getContentUri(volumeName);

  25. //外部存储的话,可以支持播放列表之类的东西,搞了一些个缓存池之类的
  26. //如mGenreCache等
  27. if (!volumeName.equals("internal")) {

  28. mProcessPlaylists = true;
  29. mGenreCache = new HashMap();
复制代码


  preScan,这个函数很复杂:

  大概就是创建一个FileCache,用来缓存扫描文件的一些信息,例如last_modified等。这个FileCache是从MediaProvider中已有信息构建出来的,也就是历史信息。后面根据扫描得到的新信息来对应更新历史信息。

     postScan,这个函数做一些清除工作,例如以前有video生成了一些缩略图,现在video文件被干掉了,则对应的缩略图也要被干掉。

  另外还有一个mClient,这个是从MediaScannerClient派生下来的一个东西,里边保存了一个文件的一些信息。后续再分析。

       在frameworks asemediajniandroid_media_MediaScanner.cpp中。

  刚才说到,具体扫描工作是在processDirectory函数中完成的。这个是一个native函数。
     三 MediaScanner JNI层分析

  MediaScanner JNI层内容比较多,单独搞一节分析吧。先看看android_media_MediaScanner这个文件。

  1. native_init函数,jni对应的函数如下

java代码:


  1. static void

  2. android_media_MediaScanner_native_init(JNIEnv *env){

  3. jclass clazz;
  4. clazz = env->FindClass("android/media/MediaScanner");
  5. //得都JAVA类中mNativeContext这个成员id

  6. fields.context = env->GetFieldID(clazz, "mNativeContext", "I");

  7. //不熟悉JNI的自己去学习下吧
  8. }
复制代码


2. native_setup函数,jni对应函数如下:

java代码:


  1. android_media_MediaScanner_native_setup(JNIEnv *env, jobject thiz){

  2. //创建MediaScanner对象
  3. MediaScanner *mp = createMediaScanner();
  4. //太变态了,自己不保存这个对象指针.
  5. //却把它设置到java对象的mNativeContext去保存
  6. env->SetIntField(thiz, fields.context, (int)mp);
  7. }

  8. //创建MediaScanner函数
  9. static MediaScanner *createMediaScanner() {

  10. #if BUILD_WITH_FULL_STAGEFRIGHT

  11. //使用google自己的

  12. return new StagefrightMediaScanner;

  13. #endif
  14. #ifndef NO_OPENCORE
  15. //使用opencore提供的.
  16. return new PVMediaScanner();
复制代码


  3. processDirectories函数,jni对应如下:

java代码:


  1. android_media_MediaScanner_processDirectory(JNIEnv *env, jobject thiz, jstring path, jstring extensions, jobject client){

  2. MediaScanner *mp = (MediaScanner *)env->GetIntField(thiz, fields.context);
  3. //每次都要回调到JAVA中去取这个Scanner!!

  4. const char *pathStr = env->GetStringUTFChars(path, NULL);
  5. const char *extensionsStr = env->GetStringUTFChars(extensions, NULL);

  6. //又在C++这里搞一个client,然后把java的client放到C++Client中去保存
  7. //而且还是栈上的临时变量..
  8. MyMediaScannerClient myClient(env, client);
  9. //scanner扫描文件夹,用得是C++的client

  10. mp->processDirectory(pathStr, extensionsStr, myClient, ExceptionCheck, env);
  11. env->ReleaseStringUTFChars(path, pathStr);
  12. env->ReleaseStringUTFChars(extensions, extensionsStr);
  13. }
复制代码


到这里似乎就没有了,那么扫描后的数据库是怎么更新的呢?为什么要传入一个client进去呢?看来必须得trace到scanner中去才知道了
四 PVMediaScanner

  这个在externalopencoreandroidmediascanner.cpp中。

  1. processDirectory

java代码:


  1. status_t MediaScanner::processDirectory(const char *path, const char* extensions,
  2. MediaScannerClient& client, ExceptionCheck exceptionCheck, void* exceptionEnv)
  3. {

  4. InitializeForThread();
  5. int error = 0;
  6. status_t result = PVMFSuccess;
  7. //调用client的设置区域函数
  8. client.setLocale(mLocale);
  9. //扫描文件夹,咋还没开始??
  10. result = doProcessDirectory(pathBuffer, pathRemaining, extensions, client, exceptionCheck, exceptionEnv);
复制代码


   2. doProcessDirectory

java代码:


  1. status_t MediaScanner::doProcessDirectory(char *path, int pathRemaining, const char* extensions,
  2. MediaScannerClient& client, ExceptionCheck exceptionCheck, void* exceptionEnv)
  3. {

  4. //终于看到点希望了
  5. //打开这个文件夹,枚举其中的内容。
  6. //题外话,这个时候FileManager肯定删不掉这个文件夹!!

  7. DIR* dir = opendir(path);

  8. while ((entry = readdir(dir))) {
  9. const char* name = entry->d_name;
  10. //不处理.和..文件夹


  11. if (isDirectory) {
  12. //不处理.开头的文件夹。如果是文件夹,递归调用doProcessDirectory
  13. //深度优先啊!
  14. int err = doProcessDirectory(path, pathRemaining - nameLength - 1, extensions, client, exceptionCheck, exceptionEnv);

  15. if (err) {
  16. LOGE("Error processing '%s' - skipping ", path);
  17. continue;
  18. }

  19. } else if (fileMatchesExtension(path, extensions)) {
  20. //是一个可以处理的文件,交给client处理
  21. //彻底疯掉了….这是干嘛呢???
  22. client.scanFile(path, statbuf.st_mtime, statbuf.st_size);
复制代码


这里要解释下,刚才createMediaScanner中,明明创建的是PVMediaScanner,为何这里看得是MediaScanner代码呢?
  因为PVMediaScannerMediaScanner中派生下来的,而且没有重载processDirectory函数Eclaire没有PVMediaScanner这样的东西,估计是froyo又改了点啥吧。

       FT,processDirctory无非是列举一个目录内容,然后又反回去调用client的scanFile处理了。为何搞这么复杂?只有回去看看C++的client干什么了。

       MediaScannerClient---JNI层

  JNI中的这个类是这样的:

java代码:


  1. class MyMediaScannerClient : public MediaScannerClient
  2. //这是它的scanFile实现

  3. virtual bool scanFile(const char* path, long long lastModified, long long fileSize){

  4. //再次崩溃了,C++的client调用了刚才传进去的java Client的
  5. //scanFile函数…不过这次还传进去了该文件的路径,最后修改时间和文件大小。

  6. mEnv->CallVoidMethod(mClient, mScanFileMethodID, pathStr, lastModified, fileSize);
  7. //想死,,,
  8. }
复制代码


  没办法了,只能再去看看MediaScanner.java传进去的那个client了。

         MediaScannerClient----JAVA层
         这个类在MediaScanner.java中实现。

java代码:


  1. private class MyMediaScannerClient implements MediaScannerClient:

  2. public void scanFile(String path, long lastModified, long fileSize) {
  3. //调用doScanFile..很烦..

  4. doScanFile(path, null, lastModified, fileSize, false);
  5. //下面是doScanFile

  6. public Uri doScanFile(String path, String mimeType, long lastModified, long fileSize, boolean scanAlways) {

  7. //预处理,看看之前创建的文件缓存中有没有这个文件
  8. FileCacheEntry entry = beginFile(path, mimeType, lastModified, fileSize);

  9. // rescan for metadata if file was modified since last scan
  10. if (entry != null && (entry.mLastModifiedChanged || scanAlways)) {

  11. //如果事先有这个文件的信息,则需要修改一些信息,如长度,最后修改时间等
  12. //真正的扫描文件

  13. processFile(path, mimeType, this);
  14. //扫描完了,做最后处理
  15. endFile(entry, ringtones, notifications, alarms, music, podcasts);
  16. //processFile又是jni层的。
  17. //对应android_media_MediaScanner_processFile函数

  18. android_media_MediaScanner_processFile(JNIEnv *env, jobject thiz, jstring path, jstring mimeType, jobject client){

  19. MediaScanner *mp = (MediaScanner *)env->GetIntField(thiz, fields.context);
  20. //无语了,又搞一个 MyMediaScannerClient
  21. MyMediaScannerClient myClient(env, client);
  22. mp->processFile(pathStr, mimeTypeStr, myClient);
  23. }
0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 考驾照没带身份证怎么办 上海扣满12分怎么办 美宝旅行证丢失怎么办 汽车证件全丢了怎么办 车的行驶本丢了怎么办 车和行驶证丢了怎么办 考驾照人在外地怎么办 外地考驾照没有居住证怎么办 考驾驶证预约密码忘了怎么办 考驾照密码忘了怎么办 考驾照的密码忘了怎么办 手机银行登录密码忘了怎么办 宽带账号或密码错误怎么办 车险过户联系不上原车主怎么办 换车了etc忘拆了怎么办 c1d驾驶证d证到期了怎么办 摩托车驾驶证过五年怎么办 没居住证想上东莞牌怎么办 外地考驾照需要暂住证怎么办 考驾照期间暂住证过期怎么办 b2驾照扣了6分怎么办 c1驾照扣了11分怎么办 c1驾驶证分扣9分怎么办 驾驶证c照扣6分怎么办 驾照过期1个月怎么办 上海驾驶证b证扣分怎么办 临时牌驾照丢了怎么办 行驶证年审过期两年怎么办 驾证到期了没换怎么办 在非洲被蚊子咬怎么办 身份证丢了被非法贷款怎么办 未满16岁怎么办身份证 放弃继承权后想反悔怎么办 上海居住证积分中社保断怎么办 换驾驶证但是身份证地址变动怎么办 驾驶证b证扣分了怎么办 c1驾证过期没审怎么办 驾照报名三年过期了怎么办 新车行驶证过期了怎么办 行驶证忘了审怎么办 摩托车驾驶证副本丢了怎么办