java nomedia_Android nomedia问题分析

一、问题起源

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

@Overridepublic voidrun() {

SystemClock.sleep(2000);if(nomedia.exists()) {

hidePath(helper, db, path);

}else{

Log.w(TAG,"does not exist: " + path, newException());

}

}}).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 voidhidePath(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= newFile(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);

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

ContentValues mediatype = newContentValues();

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

returnmClient.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,其定义如下:

@Overridepublic void scanFile(String path, long lastModified, longfileSize,boolean isDirectory, booleannoMedia) {//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系统上经常看到,也只能遇到一次规避一次。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值