详细解读Androidnomedia问题分析

简介: 详细解读Androidnomedia问题分析

一、问题起源

  最近有同事反馈试用的机器出现问题,图库的照片全部消失,新下载的第三方应用图片,也无法显示。针对该问题,当时以为是媒体库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 this

new Thread(new Runnable() {

@Override

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

processNewNoMediaPath(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 paths

MediaScanner.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 affected

Cursor 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/video

long 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)//代码效果参考:http://www.zidongmutanji.com/zsjx/124243.html

;

}

// set the media type of the affected entries to 0

ContentValues 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 //代码效果参考:http://www.zidongmutanji.com/bxxx/427516.html

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 //代码效果参考:http://www.zidongmutanji.com/zsjx/582559.html

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

if (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,其定义如下:

@Override

public 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" file

if (pathRemaining >= 8 / strlen(".nomedia") */ ) {

strcpy(fileSpot, ".nomedia");

if (access(path, F_OK) == 0) {

ALOGV("found .nomedia, setting noMedia flag");

noMedia = true;

}

// restore path

fileSpot【0】 = 0;

}

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

三、总结

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

相关文章
|
缓存 Java Android开发
【OOM异常排查经验】
【OOM异常排查经验】
226 0
|
8月前
|
SQL 监控 关系型数据库
常见的SQL优化和排查性能异常秘籍
常见的SQL优化和排查性能异常秘籍
85 1
|
消息中间件 存储 缓存
【10个OOM异常的场景以及对应的排查经验】
【10个OOM异常的场景以及对应的排查经验】
233 0
|
Java 数据挖掘
30-案例实战2:通过jps+jstat针对系统问题分析和优化
接上篇文章,如不清楚可以先看上一篇文章
109 0
30-案例实战2:通过jps+jstat针对系统问题分析和优化
|
Java 数据挖掘 BI
29-案例实战1:通过jps+jstat针对系统问题分析和优化
实际开发中有很多类似的这样的应用场景,比如每秒多少个请求,每次请求分配多少对象等,我们的目的就是通过工具分析我们系统在实际运行过程中是否频繁触发GC以及对象是否频繁进入老年代引发Full GC,哪些对象存在影响性能以及没有及时回收的问题。
110 0
|
缓存 监控 数据库连接
CPU飙高排查方案与思路
当CPU飙高时,可能是由于程序中存在一些性能问题或者死循环导致的。以下是一些排查CPU飙高的方案和思路
902 0
|
Java
OOM排查小案例
写作目的 排查过某OOM问题吗?额。。。没有
208 0
OOM排查小案例
|
消息中间件 运维 监控
线上踩坑记:项目中一次OOM的分析定位排查过程!
线上踩坑记:项目中一次OOM的分析定位排查过程!
|
SQL 存储 运维
能解决 80% 故障的排查思路 ,建议大家收藏。。
能解决 80% 故障的排查思路 ,建议大家收藏。。
270 0
能解决 80% 故障的排查思路 ,建议大家收藏。。
|
Arthas 消息中间件 监控
应用系统瓶颈排查和分析的思考-Arthas 实战
业务应用系统接入流程引擎来处理业务应用的流程执行,流程引擎提供多线程高性能异步化来执行流程元素的执行,但是如何设置流程引擎的线程池线程数执行,以及执行线程数和任务数,应用机器资源使用情况之间的关系如何,目前只能通过接入方的人工经验评估,比较粗泛评估,参数的合理性也很难直观的评估,现通过现有的应用系统默认的参数配置进行问题说明。
应用系统瓶颈排查和分析的思考-Arthas 实战