迁移本人cnblog文章:

一、问题起源

  最近有同事反馈试用的机器出现问题,图库的照片全部消失,新下载的第三方应用图片,也无法显示。针对该问题,当时以为是媒体库scan过程和数据库存在异常,查了半天无任何结论。内部讨论后,初步怀疑是nomedia导致,查看外置存储根目录的隐藏文件,果然有.nomdia生成,但这个是谁生成的呢?无从知晓,随后让同事提供试用过程,一步步盘查,结果定位到国内某度应用导致。对比国内其他机器,无此问题,应该是规避了。那么如何规避该问题,删除此文件或者排除此路径的隐藏机制?

二、nomedia实现方式

  既然规避,自然需要弄清楚系统如何实现nomedia隐藏的机制。那么nomedia到底如何定义的呢?

frameworks/base/core/java/android/provider/MediaStore.java

/*** Name of the file signaling the media scanner to ignore media in the containing directory* and its subdirectories. Developers should use this to avoid application graphics showing* up in the Gallery and likewise prevent application sounds and music from showing up in* the Music app.*/
public static final String MEDIA_IGNORE_FILENAME = ".nomedia";

  如上定义,顾名思义,是隐藏此文件当前目录以及子目录的媒体文件。那么系统是如何利用.nomedia实现该机制的呢?
根据代码搜索到的路径分析,目前有两个地方进行了隐藏处理,MediaProvider和MediaScanner,下面先看MediaProvider:
1、MediaProvider
packages/providers/MediaProvider/src/com/android/providers/media/MediaProvider.java

    /** Sets the media type of all files below the newly added .nomedia file or* hidden folder to 0, so the entries no longer appear in e.g. the audio and* images views.** @param path The path to the new .nomedia file or hidden directory*/private void processNewNoMediaPath(final DatabaseHelper helper, final SQLiteDatabase db,final String path) {final File nomedia = new File(path);if (nomedia.exists()) {hidePath(helper, db, path);} else {// File doesn't exist. Try again in a little while.// XXX there's probably a better way of doing thisnew Thread(new Runnable() {@Overridepublic void run() {SystemClock.sleep(2000);if (nomedia.exists()) {hidePath(helper, db, path);} else {Log.w(TAG, "does not exist: " + path, new Exception());}}}).start();}}

可以看到processNewNoMediaPath方法对.nomedia进行隐藏处理,判断的代码如下:

媒体库update时:

            } else if (newPath.toLowerCase(Locale.US).endsWith("/.nomedia")) {processNewNoMediaPath(helper, db, newPath);}

媒体库insertInternal:

        if (path != null && path.toLowerCase(Locale.US).endsWith("/.nomedia")) {// need to set the media_type of all the files below this folder to 0processNewNoMediaPath(helper, db, path);}return newUri;

下面看下processNewNoMediaPath方法如何实现隐藏的:

processNewNoMediaPath方法中调用了hidePath进行隐藏实现,而hidePath方法的关键是将媒体库中的media_type更新为0:

    private void hidePath(DatabaseHelper helper, SQLiteDatabase db, String path) {// a new nomedia path was added, so clear the media pathsMediaScanner.clearMediaPathCache(true /* media */, false /* nomedia */);File nomedia = new File(path);String hiddenroot = nomedia.isDirectory() ? path : nomedia.getParent();// query for images and videos that will be affectedCursor c = db.query("files",new String[] {"_id", "media_type"},"_data >= ? AND _data < ? AND (media_type=1 OR media_type=3)"+ " AND mini_thumb_magic IS NOT NULL",new String[] { hiddenroot  + "/", hiddenroot + "0"},null /* groupBy */, null /* having */, null /* orderBy */);if(c != null) {if (c.getCount() != 0) {Uri imagesUri = Uri.parse("content://media/external/images/media");Uri videosUri = Uri.parse("content://media/external/videos/media");while (c.moveToNext()) {// remove thumbnail for image/videolong id = c.getLong(0);long mediaType = c.getLong(1);Log.i(TAG, "hiding image " + id + ", removing thumbnail");removeThumbnailFor(mediaType == FileColumns.MEDIA_TYPE_IMAGE ?imagesUri : videosUri, db, id);}}IoUtils.closeQuietly(c);}// set the media type of the affected entries to 0ContentValues mediatype = new ContentValues();mediatype.put("media_type", 0);int numrows = db.update("files", mediatype,"_data >= ? AND _data < ?",new String[] { hiddenroot  + "/", hiddenroot + "0"});helper.mNumUpdates += numrows;ContentResolver res = getContext().getContentResolver();res.notifyChange(Uri.parse("content://media/"), null);}

以上实现了媒体库的文件隐藏。下面来看MediaScanner的过程:

2、MediaScanner

frameworks/base/media/java/android/media/MediaScanner.java

isNoMediaPath中:

                  // check to see if any parent directories have a ".nomedia" file
1500                  // start from 1 so we don't bother checking in the root directory
1501                  int offset = 1;
1502                  while (offset >= 0) {
1503                      int slashIndex = path.indexOf('/', offset);
1504                      if (slashIndex > offset) {
1505                          slashIndex++; // move past slash
1506                          File file = new File(path.substring(0, slashIndex) + ".nomedia");
1507                          if (file.exists()) {
1508                              // we have a .nomedia in one of the parent directories
1509                              mNoMediaPaths.put(parent, "");
1510                              return true;
1511                          }
1512                      }

这里可以看到在 isNoMediaPath方法中,每次扫描到含有.nomedia的路径,都会被添加到mNoMediaPaths的map中。下面看下此方法的作用:

endfile中:

                int mediaType = 0;if (!MediaScanner.isNoMediaPath(entry.mPath)) {int fileType = MediaFile.getFileTypeForMimeType(mMimeType);if (MediaFile.isAudioFileType(fileType)) {mediaType = FileColumns.MEDIA_TYPE_AUDIO;} else if (MediaFile.isVideoFileType(fileType)) {mediaType = FileColumns.MEDIA_TYPE_VIDEO;} else if (MediaFile.isImageFileType(fileType)) {mediaType = FileColumns.MEDIA_TYPE_IMAGE;} else if (MediaFile.isPlayListFileType(fileType)) {mediaType = FileColumns.MEDIA_TYPE_PLAYLIST;}values.put(FileColumns.MEDIA_TYPE, mediaType);}mMediaProvider.update(result, values, null, null);

scanSignleFile中:

// always scan the file, so we can return the content://media Uri for existing files
return mClient.doScanFile(path, mimeType, lastModifiedSeconds, file.length(),false, true, MediaScanner.isNoMediaPath(path));

下面分析doScanFile:

此方法除了被scanSingleFile调用完,还被scanFile调用,说明是MediaScanner隐藏媒体文件机制的关键,下面看其实现:

                FileEntry entry = beginFile(path, mimeType, lastModified,fileSize, isDirectory, noMedia);

其又调用了beginFile,又做了下面判断:

                // rescan for metadata if file was modified since last scanif (entry != null && (entry.mLastModifiedChanged || scanAlways)) {if (noMedia) {result = endFile(entry, false, false, false, false, false);} else {String lowpath = path.toLowerCase(Locale.ROOT);boolean ringtones = (lowpath.indexOf(RINGTONES_DIR) > 0);

beginFile:

            if (!isDirectory) {if (!noMedia && isNoMediaFile(path)) {noMedia = true;}mNoMedia = noMedia;

这里mNoMedia就是关键了,调用如下:

endFile中:

            if (!mNoMedia) {if (MediaFile.isVideoFileType(mFileType)) {tableUri = mVideoUri;} else if (MediaFile.isImageFileType(mFileType)) {tableUri = mImagesUri;} else if (MediaFile.isAudioFileType(mFileType)) {tableUri = mAudioUri;}}

toValue中:

            if (!mNoMedia) {if (MediaFile.isVideoFileType(mFileType)) {map.put(Video.Media.ARTIST, (mArtist != null && mArtist.length() > 0? mArtist : MediaStore.UNKNOWN_STRING));map.put(Video.Media.ALBUM, (mAlbum != null && mAlbum.length() > 0? mAlbum : MediaStore.UNKNOWN_STRING));map.put(Video.Media.DURATION, mDuration);

本次我们追踪的是.nomedia文件隐藏机制,可以看到与传入的noMedia的值有关,noMedia和mNoMedia决定了扫描到的媒体数据是否保存,而mNoMedia在本次分析中又取决于传入的noMedia,那么noMedia的值是如何来的呢?前面我们已经知道部分是 scanSignleFile中的isNoMediaPath调用值,另外的就是scanFile,其定义如下:

        @Overridepublic void scanFile(String path, long lastModified, long fileSize,boolean isDirectory, boolean noMedia) {// This is the callback funtion from native codes.// Log.v(TAG, "scanFile: "+path);doScanFile(path, null, lastModified, fileSize, isDirectory, false, noMedia);}

这个值又是native传过来的,继续追踪native的流程,最终定位到下面流程:

frameworks/av/media/libmedia/MediaScanner.cpp

    // Treat all files as non-media in directories that contain a  ".nomedia" fileif (pathRemaining >= 8 /* strlen(".nomedia") */ ) {strcpy(fileSpot, ".nomedia");if (access(path, F_OK) == 0) {ALOGV("found .nomedia, setting noMedia flag");noMedia = true;}// restore pathfileSpot[0] = 0;}

理清了上面的处理流程,接下来问题的解决就清晰了。

三、总结

  本次处理的问题,应该是三方应用设计不规范导致,系统提供的nomedia机制本来是方便应用隐藏缓存文件,结果有些app设计者不清楚其实现机制,随意创建该文件,导致出现本问题。从用户角度考虑,该问题其实是系统的设计缺陷,不能因为ap调用不规范就引起其他应用出现问题,此类问题在Android系统上经常看到,也只能遇到一次规避一次.

Android nomedia问题分析相关推荐

  1. android释放acitity内存,Android 内存泄漏分析与解决方法

    在分析Android内存泄漏之前,先了解一下JAVA的一些知识 1. JAVA中的对象的创建 使用new指令生成对象时,堆内存将会为此开辟一份空间存放该对象 垃圾回收器回收非存活的对象,并释放对应的内 ...

  2. android挂载usb设备,android usb挂载分析---MountService启动

    在android usb挂载分析----vold启动,我们的vold模块已经启动了,通信的机制也已经建立起来了,接下来我们分析一下MountService的启动,也就是我们FrameWork层的启动, ...

  3. Android nomedia 避免图片等资源泄露在系统图库其中

    总结 Android nomedia 避免文件泄露在系统图库和系统铃声中 在应用开发中 项目的图片总是被系统的图库收录了 避免图片被系统图库收录的发现有2个方法 第一种针对图片 将 .png为后缀的图 ...

  4. Android源码分析-全面理解Context

    前言 Context在android中的作用不言而喻,当我们访问当前应用的资源,启动一个新的activity的时候都需要提供Context,而这个Context到底是什么呢,这个问题好像很好回答又好像 ...

  5. Android Studio +MAT 分析内存泄漏实战

    点击打开链接 对于内存泄漏,在Android中如果不注意的话,还是很容易出现的,尤其是在Activity中,比较容易出现,下面我就说下自己是如何查找内存泄露的. 首先什么是内存泄漏? 内存泄漏就是一些 ...

  6. Android源码分析--MediaServer源码分析(二)

    在上一篇博客中Android源码分析–MediaServer源码分析(一),我们知道了ProcessState和defaultServiceManager,在分析源码的过程中,我们被Android的B ...

  7. Android系统启动流程分析之安装应用

    2016六月 21 原 Android系统启动流程分析之安装应用 分类:Android系统源码研究 (295)  (0)  举报  收藏 跟随上一篇博客Android系统的启动流程简要分析继续分析an ...

  8. android settings源代码分析(2)

    通过前一篇文章  Android settings源代码分析(1)  分析,大概知道了Settings主页面是如何显示,今天主要分析"应用"这一块google是如何实现的. 应用对 ...

  9. Android `AsyncTask`简要分析

    Android `AsyncTask`简要分析 AsyncTask简要分析 经典异步任务:AsyncTask,使用场景有:批量下载,批量拷贝等.官方文档就直接给出了一个批量下载的示例. private ...

最新文章

  1. 今天起,在广东可以用百度App一键报警!
  2. 人群行为分类数据库--Novel Dataset for Fine-grained Abnormal Behavior Understanding in Crowd
  3. Windows10 家庭版没有本地组策略解决方法
  4. hdu 1053 Entropy (哈夫曼树)
  5. 现代操作系统: 第五章 输入/输出
  6. python import出错_Python ImportError: cannot import name urlopen错误分析
  7. 【限时免费】LiveVideoStack Meet | 北京:卷时代,多媒体人 生存指北
  8. 简练软考知识点整理-控制范围
  9. python3新式类_python新式类和旧式类区别
  10. php保存ppt,ppt怎么保存到电脑桌面?
  11. 消息称华为计划推出自有品牌电动汽车 官方重申不造车
  12. 个人管理 - 如何阅读一本书
  13. 目标检测(十八)--FPN
  14. apktook 反编译错误
  15. 学习 Cesium (五):加载离线高程数据
  16. 中基鸿业人人都要懂的投资理财常识
  17. 部署超级账本fabric区块可视化浏览器
  18. 框架里面的标签采集不到怎么办_怎么做微信生态的全数据采集和打通?
  19. 编程之禅 (小赵译版)(2)
  20. 宁夏中卫市:新一代云计算走向世界

热门文章

  1. 网页播放器的参数含义
  2. 给Java程序猿们推荐一个个人觉得超级好的Java学习网站
  3. 不要为明天忧虑 (10月14日)
  4. 8266 + 巴法云 小爱远程控制电脑开关机 arduino
  5. MicroPython 交互式解释器模式(REPL)
  6. Linux搭建LAMP平台与DISCUZ论坛
  7. 视频教程 | Egret Pro 入门学习笔记(10):认识模型
  8. 使用树莓派SPI接口实现RFID门禁功能
  9. Glarysoft File Recovery Pro v1.7.0.9 数据恢复软件便携版
  10. 干货 | 选择“正激”还是“反激”?这份宝典请收好~