详细解读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系统上经常看到,也只能遇到一次规避一次。

相关文章
|
4月前
|
SQL 监控 关系型数据库
常见的SQL优化和排查性能异常秘籍
常见的SQL优化和排查性能异常秘籍
65 1
|
4月前
|
SQL 索引
|
SQL 存储 运维
能解决 80% 故障的排查思路 ,建议大家收藏。。
能解决 80% 故障的排查思路 ,建议大家收藏。。
237 0
能解决 80% 故障的排查思路 ,建议大家收藏。。
|
数据库
实现日志功能的思路
实现日志功能的思路
实现日志功能的思路
小技巧 - LeetCode 如何查看他人耗时更优的代码答案?
小技巧 - LeetCode 如何查看他人耗时更优的代码答案?
346 0
小技巧 - LeetCode 如何查看他人耗时更优的代码答案?
|
缓存 NoSQL 关系型数据库
语音聊天系统,问题分析是找到解决方法的关键
语音聊天系统,问题分析是找到解决方法的关键
记一次并发引起的问题及排查过程
聚合支付系统(第四方支付),协议支付模块一直有个小问题。 商户调用协议支付接口,该模块会调用下层第三方支付渠道的协议支付服务,如果第三方支付渠道没有同步返回支付结果,则协议支付模块会通过定时任务向第三方支付渠道批量第查询支付结果(每查一笔订单就调一次第三方支付渠道,“批量”相当于并发调用第三方支付渠道)
记一次并发引起的问题及排查过程
|
弹性计算 分布式计算 数据库
|
安全 Unix 测试技术