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

相关文章
|
开发工具 git 缓存
Git忽略规则.gitignore不生效
在项目开发过程中个,一般都会添加 .gitignore 文件,规则很简单,但有时会发现,规则不生效。 原因是 .gitignore 只能忽略那些原来没有被track的文件,如果某些文件已经被纳入了版本管理中,则修改.gitignore是无效的。
64083 6
|
6月前
|
存储 人工智能 搜索推荐
AI Agent 记忆系统:从短期到长期的技术架构与实践
本文系统阐述AI Agent记忆系统的核心技术:短期记忆(会话级上下文管理)与长期记忆(跨会话知识沉淀)。涵盖上下文工程策略(压缩、卸载、隔离)、Record/Retrieve架构、主流框架(ADK/LangChain/AgentScope)实现差异,及Mem0等开源方案集成,并探讨MaaS、多模态记忆等前沿趋势。(239字)
10014 2
AI Agent 记忆系统:从短期到长期的技术架构与实践
|
缓存 网络安全 开发工具
全面掌握 Git 和 Gitee:从安装到上传的完整指南
本文档介绍了如何安装和配置Git,以及如何与Gitee进行连接。首先从官网下载Git并安装,接着配置用户名和邮箱,生成SSH密钥并将其添加到Gitee账户,完成无密码登录的设置。文档还提供了基本的命令使用指南,包括文件操作、Git命令和gitee代码上传流程,最后讲解了提交信息的规范格式和回滚操作的方法。
1813 1
|
安全 数据处理 数据安全/隐私保护
C/S架构与B/S架构的适用场景分析
C/S架构(客户端/服务器架构)与B/S架构(浏览器/服务器架构)在适用场景上各有特点,主要取决于应用的具体需求、用户群体、系统维护成本、跨平台需求等因素。
1599 6
|
人工智能 安全 Android开发
手机也能跑通义Qwen3大模型,手把手教你部署!
全球开源模型冠军Qwen3与端到端全模态模型Qwen2.5-Omni现已成功在手机上跑通!借助MNN支持,适配Android、iOS及桌面端,实现低延迟、本地化、高安全的AI体验。用户可通过自定义Sampler设置、System Prompt和Max New Tokens调节模型输出风格与长度。
6167 11
|
机器学习/深度学习 数据采集 自然语言处理
基于Python的情感分析与情绪识别技术深度解析
本文探讨了基于Python的情感分析与情绪识别技术,涵盖基础概念、实现方法及工业应用。文中区分了情感分析与情绪识别的核心差异,阐述了从词典法到深度学习的技术演进,并通过具体代码展示了Transformers架构在细粒度情感分析中的应用,以及多模态情绪识别框架的设计。此外,还介绍了电商评论分析系统的构建与优化策略,包括领域自适应训练和集成学习等方法。未来,随着深度学习和多模态数据的发展,该技术将更加智能与精准。
913 1
|
数据库 Android开发 数据库管理
java.lang.NullPointerException: Attempt to invoke virtual method ‘int android.database.sqlite异常
java.lang.NullPointerException: Attempt to invoke virtual method ‘int android.database.sqlite异常
1145 0
|
机器学习/深度学习 人工智能 缓存
基于AIGC的自动化内容生成与应用
基于AIGC的自动化内容生成与应用
902 3
|
弹性计算 Linux 开发工具
阿里云学生服务器免费用半年_1个月加6个月_学生验证
阿里云学生服务器免费用半年_1个月加6个月_学生验证流程,阿里云学生服务器优惠活动:高效计划,可以免费领取一台阿里云服务器,如果你是一名高校学生,想搭建一个linux学习环境、git代码托管服务器,或者创建个人博客网站记录自己的学习成长历程,拥有一台云服务器是很有必要的。阿里云的飞天加速计划3.0——高校计划,面向学生开发者提供免费的云服务器福利,通过学生身份认证及续费任务后,最多可领取7个月免费云服务器ECS资源
93808 40