MediaStore数据库分析
文章目录
- MediaStore
- 概述
- 读取MediaStore数据、设置监听、显示数据
- 查询数据
- 注册监听
- 代码扩展说明
- 界面显示
- 添加点击事件
- 最终代码
- 问题处理
- 为什么网络图片下载到手机之后相册里还是没有数据?
- 如何做到下载图片之后可以立马在相册中显示?
- 图片在文件管理中删除之后MediaStore不更新问题?
MediaStore
概述
MediaStore是Android系统提供的一个多媒体数据库,专门用于存放多媒体信息,通过ContentResolver可对数据库进行操作。
本篇文章会讲述一个标准的从MediaStore数据库获取图片数据并展示到自己使用RecyclerView实现的相册中的过程,并且会带着以下几个问题来解释为什么要这么做。
MediaStore、MediaScannerReceiver、MediaScannerService、MediaScanner、ContentResolver、MediaScannerConnection 是如何实现数据库的增删改查操作的?
为什么网络图片下载到手机之后相册里还是没有数据?
如何做到下载图片之后可以立马在相册中显示?
图片在文件管理中删除之后MediaStore不更新问题?(真实工作中自家Rom出现的问题)
读取MediaStore数据、设置监听、显示数据
查询数据
通过ContentResolver提供的查询接口来获取MediaStore数据(对应下述 扩展说明A)
注册监听
调用ContentResolver的registerContentObserver方法来设置监听(对应下述 扩展说明B)
fun loadImages() {viewModelScope.launch {val imageList = queryImages() //-> A_images.postValue(imageList)if (contentObserver == null) { //-> BcontentObserver = getApplication<Application>().contentResolver.registerObserver(MediaStore.Images.Media.EXTERNAL_CONTENT_URI) {loadImages()}}}}
代码扩展说明
注释点 **A ** --> queryImages() 方法使用ContentResolver获取图片列表并赋值给imageList,然后通过LiveDate的postValue发送出去,Activity会监听此LiveData,从而拿到imageList进行ListAdapter的刷新操作。queryImages()具体方法如下:
private suspend fun queryImages(): List<MediaStoreImage> {val images = mutableListOf<MediaStoreImage>()withContext(Dispatchers.IO) {val projection = arrayOf(MediaStore.Images.Media._ID,MediaStore.Images.Media.DISPLAY_NAME,MediaStore.Images.Media.DATE_ADDED)val selection = "${MediaStore.Images.Media.DATE_ADDED} >= ?"val selectionArgs = arrayOf(dateToTimestamp(day = 22, month = 10, year = 2008).toString())val sortOrder = "${MediaStore.Images.Media.DATE_ADDED} DESC"getApplication<Application>().contentResolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,projection,selection,selectionArgs,sortOrder)?.use { cursor ->val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID)val dateModifiedColumn =cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATE_ADDED)val displayNameColumn =cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME)Log.i(TAG, "Found ${cursor.count} images")while (cursor.moveToNext()) {val id = cursor.getLong(idColumn)val dateModified =Date(TimeUnit.SECONDS.toMillis(cursor.getLong(dateModifiedColumn)))val displayName = cursor.getString(displayNameColumn)val contentUri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,id)val image = MediaStoreImage(id, displayName, dateModified, contentUri)images += imageLog.v(TAG, "Added image: $image")}}}Log.v(TAG, "Found ${images.size} images")return images}/*** 日期格式化*/private fun dateToTimestamp(day: Int, month: Int, year: Int): Long =SimpleDateFormat("dd.MM.yyyy").let { formatter ->TimeUnit.MICROSECONDS.toSeconds(formatter.parse("$day.$month.$year")?.time ?: 0)}//上述代码内容有点多,稍微做一下阐述,有以下几步操作://1、withContext(Dispatchers.IO) 协程切换到IO线程执行
//2、创建projection、selection、selectionArgs、sortOrder这几个query需要的参数
//3、执行query操作并返回Cursor对象
//4、通过Cursor对象获取图片的参数并创建MediaStoreImage对象
//5、将MediaStoreImage添加到list并返回//MediaStoreImage是我们定义的bean对象,用于在RecycleView中数据的显示
data class MediaStoreImage(val id: Long,val displayName: String,val dateAdded: Date,val contentUri: Uri
)
注释点 B --> registerObserver是我定义的ContentResolver的一个扩展方法,他实现了对MediaStore.Images.Media.EXTERNAL_CONTENT_URI的监听,当数据变化会重新调用loadImages()方法–>queryImages()方法,获取MediaStore数据 。ContentResolver的扩展方法如下:
private fun ContentResolver.registerObserver(uri: Uri,observer: (selfChange: Boolean) -> Unit
): ContentObserver {val contentObserver = object : ContentObserver(Handler()) {override fun onChange(selfChange: Boolean) {observer(selfChange)}}registerContentObserver(uri, true, contentObserver)return contentObserver
}
上述两个注释点A|B都会调用queryImages()方法来获取图片列表,A是主动触发,而B是MediaStore数据库更新之后被动触发,所以此时的代码我们已经可是保证images可以跟MediaStore下的图片保持一致了。
界面显示
最后是界面显示,我使用的是RecyclerView.Adapter的子类ListAdapter来实现列表显示和更新,并且将点击事件作为参数传入到Adapter(高阶函数方式),这个没什么好说的,不懂的可已看下ListAdapter标准写法
class GalleryAdapter(private val onClick: (MediaStoreImage) -> Unit) :ListAdapter<MediaStoreImage, ImageViewHolder>(DiffCallback) {override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ImageViewHolder {val layoutInflater = LayoutInflater.from(parent.context)val view = layoutInflater.inflate(R.layout.gallery_layout, parent, false)return ImageViewHolder(view, onClick)}override fun onBindViewHolder(holder: ImageViewHolder, position: Int) {val mediaStoreImage = getItem(position)holder.rootView.tag = mediaStoreImageGlide.with(holder.imageView).load(mediaStoreImage.contentUri).thumbnail(0.33f).centerCrop().into(holder.imageView)}companion object {val DiffCallback = object : DiffUtil.ItemCallback<MediaStoreImage>() {override fun areItemsTheSame(oldItem: MediaStoreImage, newItem: MediaStoreImage) =oldItem.id == newItem.idoverride fun areContentsTheSame(oldItem: MediaStoreImage, newItem: MediaStoreImage) =oldItem == newItem}}
}
添加点击事件
// 初始化GalleryAdapter,并通过高阶函数方式传入一个deleteImage方法
val galleryAdapter = GalleryAdapter { image ->deleteImage(image)}//界面显示的关键方法,监听了viewModel里的images(是个LiveDate),拿到List<MediaStoreImage>更新ListAdapter
viewModel.images.observe(this, Observer<List<MediaStoreImage>> { images ->galleryAdapter.submitList(images)})//deleteImage方法会在点击Adapter中任意一项的时候弹出Dialog,会调用deleteImage
// 最终调用contentResolver的delete来删除此项Image,篇幅有限删除请查看最终代码
private fun deleteImage(image: MediaStoreImage) {MaterialAlertDialogBuilder(this).setTitle(R.string.delete_dialog_title).setMessage(getString(R.string.delete_dialog_message, image.displayName)).setPositiveButton(R.string.delete_dialog_positive) { _: DialogInterface, _: Int ->viewModel.deleteImage(image)}.setNegativeButton(R.string.delete_dialog_negative) { dialog: DialogInterface, _: Int ->dialog.dismiss()}.show()}
最终代码
https://github.com/WuMaoQiang/nobo_mediastore
问题处理
为什么网络图片下载到手机之后相册里还是没有数据?
MediaStore的刷新不是实时的,比如开机,U盘挂载等场景会触发刷新,当我们下载一张网路图片,虽然图片下载成功了,在文件管理中可以找到这个图片,但是相册中却看不到,这是MediaStore设计如此。所以如果我们想让相册或者任何关心MediaStore的应用能立马看到这个图片那就要主动触发刷新MediaStore。
如何做到下载图片之后可以立马在相册中显示?
有三种方式可以做到:
1、使用MediaStore.Image.Media.inserImage()方法,直接通过ContentResolver操作数据库
2、发送ACTION_MEDIA_SCANNER_SCAN_FILE广播。查看系统类 广播类MediaScannerReceiver会对此Action做出响应,调用scanFile方法
而ScanFile方法启动了一个Service进行Scan操作
private void scanFile(Context context, String path) {80 Bundle args = new Bundle();
81 args.putString("filepath", path);
82 context.startService(
83 new Intent(context, MediaScannerService.class).putExtras(args));
84 }
85}
MediaScannerService创建MediaScanner执行scanSingleFile进行全盘扫描操作
3、使用MediaScannerConnection类进行全盘扫描,通过绑定 MediaScannerService 进行Scan操作,MediaScannerConnection会调用scanFile方法通过 connection.connect();来进行与MediaScannerService 的绑定操作
233 public static void scanFile(Context context, String[] paths, String[] mimeTypes,
234 OnScanCompletedListener callback) {235 ClientProxy client = new ClientProxy(paths, mimeTypes, callback);
236 MediaScannerConnection connection = new MediaScannerConnection(context, client);
237 client.mConnection = connection;
238 connection.connect();
239 }
240112 public void connect() {113 synchronized (this) {114 if (!mConnected) {115 Intent intent = new Intent(IMediaScannerService.class.getName());
116 intent.setComponent(
117 new ComponentName("com.android.providers.media",
118 "com.android.providers.media.MediaScannerService"));
119 mContext.bindService(intent, this, Context.BIND_AUTO_CREATE);
120 mConnected = true;
121 }
122 }
123 }
绑定MediaScannerService 之后也会调用它的 scanFile方法,之后在调用MediaScanner进行扫描操作
图片在文件管理中删除之后MediaStore不更新问题?
此问题是我再实际开发中碰到的问题,文件管理开发工程师在删除相应图片之后未通过上述手段及时通知相册或未触发重新扫描操作,导致图片确实已经删除了,但是我的相册监听的是MediaStore数据库并没有更新。
后面文件管理工程师在删除图片之后,采用了发送广播的形式来触发MediaStore重新扫描,理论上是可以实现刷新MediaStore数据库的,但是事实上没有奏效,经过排查发现,广播和形式或者MediaScannerService 的形式都无法触发MediaStore数据库的重新扫描,因为源码中如果文件已经被删除了,那么MediaScanner就return了
所以我们只能采用contentResolver操作MediaStore数据库的方式来删除数据
getApplication<Application>().contentResolver.delete(image.contentUri,"${MediaStore.Images.Media._ID} = ?",arrayOf(image.id.toString()))
MediaStore数据库分析相关推荐
- php mysql ppt,7PHP访问数据库分析.ppt
7PHP访问数据库分析 习题 4. 下列哪个函数可以将结果集的指针移动到指定的位置 A. mysql_fetch_rowB. mysql_fetch_assoc C. mysql_queryD. my ...
- 电子商务之数据库分析(二)
电子商务之数据库分析(二) 在分析数据库之前,我想倡导大家说说自己开发数据库时应用的工具,erwin?Powerdesiger?or Wrod?或者有更好的,我自己用的是Powerdesiger,但是 ...
- 数据库课程设计-图书馆管理系统(1.数据库分析部分)
图书馆管理系统第一部分,数据库分析. 主要是需求分析.设计概要模块.数据库概念结构设计(E-R图).数据库逻辑结构设计(表.存储过程.触发器) 目录 1. 需求分析 1.1 数据流图 1.2 数据字典 ...
- 模仿天猫商城数据库分析
仿天猫商城数据库分析 数据结构图 页面分析 前端 注册页面 - 页面图片 - 功能描述 登陆页面 - 功能描述 账号登陆 - 页面执行的SQL语句 校验账户密码是否匹配,匹配则为1,否则为0 SELE ...
- seer文献_文献解读:基于SEER数据库分析
大家好,这次给大家分享的文献是Signet-Ring Cell Carcinoma as an Independent Prognostic Factor for Patients With Urin ...
- 建筑公司员工财务数据库分析
建筑公司员工财务数据库分析 问题: 假设某建筑公司要设计一个数据库.公司的业务规则概括说明如下: 公司承担多个工程项目,每一项工程有:工程号.工程名称.施工人员等 公司有多名职工,每一名职工有:职工号 ...
- 主流时序数据库分析及选型
目录 一.当前主流的时序数据库 二.主流时序数据库分析 1.Influxdb 2.Timescale 3.Apache Druid 4.Kdb+ 5.Graphite 6.RRDtool 7.Open ...
- 云计算下的数据库 分析 以及部分互联网公司眼下採用的新型数据库总结
云计算下的新型数据库技术 摘要:在这个信息化的时代,我们的一举一动都离不开与数据打交道,特别是云计算和大数据时代的到来,使得传统数据库的性能已无法满足海量数据的实时交易查询需求.在性能和成本的双重压力 ...
- PetShop 4数据库分析一
虽然园子里面有很多是petShop方面的学习资料,尤其是在我的这个学习资料里面张逸老师所写的<解剖PetShop>系列,是相当精彩的,给大家展示了一个相对比较完整的.NET框架.技术以及相 ...
最新文章
- 还需要“attention”吗?一堆“前馈层”在ImageNet上表现得出奇得好
- c语言循环并行处理,C语言设计并行处理
- 其他算法-LSH局部敏感度哈希
- 使用VirtualAlloc在0x400000处申请内存
- 网易云信携手小天才电话手表 打造视频通话体验的行业标杆
- lenet pytorch 官方demo学习笔记
- 前端学习(1355) 子模板
- SpringMVC 执行流程解析
- vsftpd的主配置文件是什么linux,vsftpd.conf配置文件详解
- Digit v3.0.0 – 响应式WHMCS模板
- mysql加入新的从节点怎么配置,Mysql 5.7从节点配置多线程主从复制的方法详解
- Vue成大学核心课程
- 快速排序图解_排序算法
- 活动目录系列之一……活动目录简介及部署
- BZOJ1296[SCOI2009] 粉刷匠
- 发两本经典的C/C++教材电子版
- android studio 前言中不允许有内容。
- aix oracle 10.2.0.1 升级 10.2.0.4,【江枫 】AIX平台升级到Oracle10.2.0.4的几个问题
- 方直发展冲刺港股上市:利润连增、债务高企,董事长陈专持股95%
- OPENMV-STM32串口通信
热门文章
- 我的世界java版怎么选择版本_《我的世界》游戏版本太多,玩家该如何选择?听听老玩家怎么说...
- 给代码写注释时有哪些讲究?
- 一文尽览!弱监督语义/实例/全景分割全面调研(2022最新综述)
- CSP-J复赛复习题目(NOIP普及组2000-2011)
- 天才制造者:独行侠、科技巨头和AI|深度学习崛起十年
- 测试开发之Python核心笔记(15):迭代器与生成器
- 今日闲谈:为何国产动画能在抖音异军突起?
- 【实战】如何有效的进行测试用例评审(测试用例评审又臭又长,怎么办)
- 股票指标RSI背离检测程序,附代码
- 结绳编程【按钮事件】