Android nomedia问题分析
迁移本人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问题分析相关推荐
- android释放acitity内存,Android 内存泄漏分析与解决方法
在分析Android内存泄漏之前,先了解一下JAVA的一些知识 1. JAVA中的对象的创建 使用new指令生成对象时,堆内存将会为此开辟一份空间存放该对象 垃圾回收器回收非存活的对象,并释放对应的内 ...
- android挂载usb设备,android usb挂载分析---MountService启动
在android usb挂载分析----vold启动,我们的vold模块已经启动了,通信的机制也已经建立起来了,接下来我们分析一下MountService的启动,也就是我们FrameWork层的启动, ...
- Android nomedia 避免图片等资源泄露在系统图库其中
总结 Android nomedia 避免文件泄露在系统图库和系统铃声中 在应用开发中 项目的图片总是被系统的图库收录了 避免图片被系统图库收录的发现有2个方法 第一种针对图片 将 .png为后缀的图 ...
- Android源码分析-全面理解Context
前言 Context在android中的作用不言而喻,当我们访问当前应用的资源,启动一个新的activity的时候都需要提供Context,而这个Context到底是什么呢,这个问题好像很好回答又好像 ...
- Android Studio +MAT 分析内存泄漏实战
点击打开链接 对于内存泄漏,在Android中如果不注意的话,还是很容易出现的,尤其是在Activity中,比较容易出现,下面我就说下自己是如何查找内存泄露的. 首先什么是内存泄漏? 内存泄漏就是一些 ...
- Android源码分析--MediaServer源码分析(二)
在上一篇博客中Android源码分析–MediaServer源码分析(一),我们知道了ProcessState和defaultServiceManager,在分析源码的过程中,我们被Android的B ...
- Android系统启动流程分析之安装应用
2016六月 21 原 Android系统启动流程分析之安装应用 分类:Android系统源码研究 (295) (0) 举报 收藏 跟随上一篇博客Android系统的启动流程简要分析继续分析an ...
- android settings源代码分析(2)
通过前一篇文章 Android settings源代码分析(1) 分析,大概知道了Settings主页面是如何显示,今天主要分析"应用"这一块google是如何实现的. 应用对 ...
- Android `AsyncTask`简要分析
Android `AsyncTask`简要分析 AsyncTask简要分析 经典异步任务:AsyncTask,使用场景有:批量下载,批量拷贝等.官方文档就直接给出了一个批量下载的示例. private ...
最新文章
- 今天起,在广东可以用百度App一键报警!
- 人群行为分类数据库--Novel Dataset for Fine-grained Abnormal Behavior Understanding in Crowd
- Windows10 家庭版没有本地组策略解决方法
- hdu 1053 Entropy (哈夫曼树)
- 现代操作系统: 第五章 输入/输出
- python import出错_Python ImportError: cannot import name urlopen错误分析
- 【限时免费】LiveVideoStack Meet | 北京:卷时代,多媒体人 生存指北
- 简练软考知识点整理-控制范围
- python3新式类_python新式类和旧式类区别
- php保存ppt,ppt怎么保存到电脑桌面?
- 消息称华为计划推出自有品牌电动汽车 官方重申不造车
- 个人管理 - 如何阅读一本书
- 目标检测(十八)--FPN
- apktook 反编译错误
- 学习 Cesium (五):加载离线高程数据
- 中基鸿业人人都要懂的投资理财常识
- 部署超级账本fabric区块可视化浏览器
- 框架里面的标签采集不到怎么办_怎么做微信生态的全数据采集和打通?
- 编程之禅 (小赵译版)(2)
- 宁夏中卫市:新一代云计算走向世界
热门文章
- 网页播放器的参数含义
- 给Java程序猿们推荐一个个人觉得超级好的Java学习网站
- 不要为明天忧虑 (10月14日)
- 8266 + 巴法云 小爱远程控制电脑开关机 arduino
- MicroPython 交互式解释器模式(REPL)
- Linux搭建LAMP平台与DISCUZ论坛
- 视频教程 | Egret Pro 入门学习笔记(10):认识模型
- 使用树莓派SPI接口实现RFID门禁功能
- Glarysoft File Recovery Pro v1.7.0.9 数据恢复软件便携版
- 干货 | 选择“正激”还是“反激”?这份宝典请收好~