安卓系统会在每次开机之后扫描所有文件并分类整理存入数据库,这个数据库保存了手机上存储的所有文件的信息。该数据库文件存放在Android设备的/data/data/com.android.providers.media/databases或/data/data/com.android.providers.media.module/databases目录当中,该目录下有两个数据库文件分别是internal.db(内部存储数据库文件)和external.db(外部存储数据库文件), 这两个数据库文件中的数据表和表结构都大体相似,区别在于internal.db是用来存放内部存储中的文件信息的,而external.db是用来存储外部存储中的文件信息的。因此可以通过访问这两个数据库获取例如媒体文件(音频、视频、图片)等的文件信息, 而不必通过遍历媒体文件的方式来获取文件信息。但是在android设备中是禁止应用程序直接对这个数据进行直接操作的,而是将这个数据库的操作通过ContentProvider(内容提供者) 将数据操作提供出来, 如要对ContentProvider中的数据进行操作,可以通过ContentResolver(数据调用者) 对象结合Uri进行调用 来实现 。 ContentResolver(数据调用者)可以实现与ContentProvider进行通信,通过ContentResolver调用ContentProvider的添加(insert)、删除(delete)、查询(query)、修改(update)等操作的同名方法,从而让ContentProvider对象接收数据请求、执行请求的操作并返回结果,这是一套标准的Android内容提供者数据模型。ContentProvider将其存储的数据以数据表的形式提供给访问者,在数据表中每一行为一条记录,每一列为具有特定类型和意义的数据。每一条数据记录都包括一个 “_ID” 数值字段,改字段唯一标识一条数据。每一个Content Provider 都对外提供一个能够唯一标识自己数据集(data set)的公开URI, 如果一个Content Provider管理多个数据集,其将会为每个数据集分配一个独立的URI。所有的Content Provider 的URI 都以"content://" 开头,其中"content:"是用来标识数据是由Content Provider管理的 schema。Android 系统为一些常见的数据类型(如音乐、视频、图像、手机通信录联系人信息等)内置了一系列的 Content Provider,这些都位于android.provider包下。持有特定的许可,可以在自己开发的应用程序中访问这些Content Provider。
获取ContentResolver实例:
val mResolver = context.getContentResolver();
ContentResolver实例获得后,就可以进行各种增删改查的方法 ,ContentResolver类也提供了与ContentProvider类相对应的四个方法可供调用:
| 返回值 | 函数声明 | 说明 |
|---|---|---|
| final Uri | insert(Uri url, ContentValues values) | 该方法用于往ContentProvider添加数据。 |
| final int | delete(Uri url, String where, String[] selectionArgs) | 该方法用于从ContentProvider删除数据。 |
| final Cursor | query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) | 该方法用于从ContentProvider中获取数据。 |
| final int | update(Uri uri, ContentValues values, String where, String[] selectionArgs) | 该方法用于更新ContentProvider中的数据。 |
ContentResolver是通过Uri来查询ContentProvider中提供的数据的。因此想操作ContentProvider,必须要知道内容提供者的Uri,在正确得到Uri之后,就可以通过ContentResolver对象来操作ContentProvider中的数据了。
上文中提到了Android提供内容的叫ContentProvider,那么在Android中怎么区分各个Provider?有的是提供联系人的,有的是提供图片的,有的是提供视频的等等。所以就需要有一个唯一的标识来标识这个Provider,Uri(通用资源标识符 Universal Resource Identifier)就是起到了这个标识的作用。每一个ContentProvider都会有一个唯一的Uri地址,通过这个Uri标识可以获取到ContentProvider和其中的数据,然后进行数据操作。
ContentProvider使用的Uri语法结构如下:
content://media/external/images/media
对于系统已经提供了如通讯录、多媒体、短信等的URI,可以直接用ContentResolver调用这些URI,对系统数据库进行增删改查等操作,从而保证整个Android设备中数据的统一。
以content://media/external/images/media为例,其URI有三种写法:
Uri uri1 = Uri.parse("content://media/external/images/media");
Uri uri2 = MediaStore.Images.Media.getContentUri("external");
Uri uri3 = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
Android 提供了 MediaStore 类来对数据库的的多媒体数据进行封装。
我们知道Android系统中的每一种媒体文件有两种地址描述方式:
MediaStore.Images.Media.INTERNAL_CONTENT_URI和MediaStore.Images.Media.EXTERNAL_CONTENT_URI这两个地址,其值分别是content://media/internal/images/media和content://media/external/images/media,对应内部库和外部库地址。每一张图片的地址基本上是上面的基础URL地址下加上图片的内部ID。例如外部存储上的图片ID为52,其对应的Uri地址就是 content://media/external/images/media/52. 知道了这个地址,基本上就可以操作这张图片的所有信息了。同样,对于其他多媒体文件例如外部库的音频 Uri 地址为 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI ,其它 Uri 地址都是类似的。通过上文可以知道Android为多媒体提供的ContentProvider的Uri都记录在MediaStore这个类里。MediaStore获取文件信息,是通过文件的mime-type来获取的,也就说文件储存库中有mime-type这个字段。
MediaStore内部类
MediaStore内部类对外提供的多媒体Uri常量有如下
注意:MediaStore.Downloads.EXTERNAL_CONTENT_URI是Android10版本新增API,用于创建、访问非媒体文件。
Android系统给我们定义好了许多的媒体文件对应的URI路径如下表:
| 媒体类型 | Uri路径 | MediaStore内部类常量 | 默认存储目录 | 允许存储目录 |
|---|---|---|---|---|
| Image(图片) | content://media/external/images/media | MediaStore.Images.Media.EXTERNAL_CONTENT_URI | Pictures | DCIM、Pictures |
| Audio(音频) | content://media/external/audio/media | MediaStore.Audio.Media.EXTERNAL_CONTENT_URI | Music | Alarms、Music、Notifications、Podcasts、Ringtones |
| Video(视频) | content://media/external/video/media | MediaStore.Video.Media.EXTERNAL_CONTENT_URI | Movies | DCIM 、Movies |
| Download(下载文件) | content://media/external/downloads | MediaStore.Downloads.EXTERNAL_CONTENT_URI | Download | Download |
有了对应的Uri 地址,我们可以通过ContentResolver调用添加(insert)、删除(delete)、查询(query)、 修改(update)等操作了。
有了 Uri 地址,我们可以通过ContentResolver的 query() 方法来获取到 Cursor,从而获取到数据库资源。
public final Cursor query (Uri uri, String[] projection,String selection,String[] selectionArgs, String sortOrder)
ContentResolver的query方法接受几个参数,参数意义如下:
示例:
//查找所有图片的信息
Cursor cursor = contentResolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
null,
null,
null,
null);
//查找所有图片的信息的DISPLAY_NAME字段
Cursor cursor = contentResolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
new String[]{MediaStore.Images.Media.DISPLAY_NAME},
null,
null,
null);
//查找图片名字叫“xx.png”的图片信息,null表示不进行筛选。
Cursor cursor = contentResolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
new String[]{MediaStore.Images.Media.DISPLAY_NAME},
//第三个参数设置条件,相当于SQL语句中的where。
//null表示不进行筛选。
MediaStore.Images.Media.DISPLAY_NAME + "='xx.png'",
null,
null);
//查找图片名字叫“xx.png”的图片信息,如果在selection参数里面有?,那么你在selectionArgs写的数据就会替换掉?,
Cursor cursor = contentResolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
new String[]{MediaStore.Images.Media.DISPLAY_NAME},
MediaStore.Images.Media.DISPLAY_NAME + "=?",
new String[]{"xx.png"},
null);
//默认排序是升序。注意:desc前有空格
Cursor cursor = contentResolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
null,
null,
null,
ContactsContract.Contacts._ID + " DESC");
上面就是各个参数的意义,它返回的查询结果一个Cursor结果集。那么如何使用Cursor对象获取数据?
Cursor就是游标,把查询到的结果集封装在一个Cursor对象当中。我们知道只要是数据表都是可以通过行和列定位到具体的位置然后数据将其取出,Cursor也不例外,可以通过cursor.moveToNext()让行向后移动,然后根据getColumnIndex(String columnName)获取到列名的索引位置。有了每一行的的列名索引位置就可以就可以取出每一行中对应的数据了。当然这些数据类型也是多种多样的,Cursor也给我们提供了以下的方法去获取不同类型的值:
示例:使用ContentResolver通过Uri获取图片文件相关的信息集Cursor,然后通过Cursor取出图片文件相关的信息:
// 先拿到图片提供者的Uri
val imageUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
// 需要获取图片数据表中的哪几列信息,注意这里需要和cursor取出的列名相对应否则会出现空指针
val projection = arrayOf(
//获取ID列的数据
MediaStore.Images.Media._ID,
//获取MIME_TYPE列的值
MediaStore.Images.Media.MIME_TYPE,
//获取DISPLAY_NAME列的值
MediaStore.Images.Media.DISPLAY_NAME
)
// 查询条件:因为是查询全部图片,传null
//String selection = MediaStore.Images.Media.DISPLAY_NAME +"= ?";
// 条件参数:因为是查询全部图片,传null
//String[] args = new String[] {“xxx.png”}
// 排序:可以添加排序
// val order = MediaStore.Files.FileColumns._ID + "DESC"
// 开始查询
val cursor = contentResolver.query(imageUri, projection, null, null, null)
//获取我们需要的数据在数据的第几列,这里需要和projection查询的数据对应起来,因为cursor结果集只包含projection的列信息,否则会出现空指针
if (cursor != null) {
// 获取id字段是第几列
val idIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID)
val mimeTypeIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.MIME_TYPE)
val displayNameIndex = cursor.getColumnIndex(MediaStore.Images.Media.DISPLAY_NAME)
//循环取出cursor每一行的数据
while (cursor.moveToNext()) {
//根据列的坐标,取出对应行数的数据
val id = cursor.getLong(idIndex)
//根据列的坐标,取出对应行数的数据
val type = cursor.getString(mimeTypeIndex)
//根据列的坐标,取出对应行数的数据
val disName = cursor.getString(displayNameIndex)
//根据ID和图片提供者的Uri可以合成图片的Uri
val imageUri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id)
//TODO
}
//关闭游标
cursor.close()
}
通过上面的例子,可以获取到手机外部存储中所有的图片文件信息的ID、MIME_TYPE、DISPLAY_NAME等信息,如果要获取到文件大小等其他的信息也是可以的,只需要在projection集合中添加对应的列名然后,在cursor中找到每一行对应的列的索引值就可以取出对应的值,这些列名可以在MediaStore.MediaColumns取公共常量字段,也可以根据文件类型的不同在MediaStore的内部类中取值:
图片文件比较常见的列名有:
视频文件比较常见的列名有:
音频文件比较常见的列名有:
如果需要插入数据只需要调用contentResolver.insert(uri, contentValues);构造一个 ContentValues 对象,通过 ContentResolver.insert 插入到对应的目录中,该方法会返回一个 Uri,通过对该 Uri 进行文件流写入即可:
private fun saveImage(bitmap: Bitmap){
val values = ContentValues()
val insertUri = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,values)
insertUri?.let {
contentResolver.openOutputStream(it).use {outputStream->
bitmap.compress(Bitmap.CompressFormat.PNG,100,outputStream)
}
}
}
ContentValues类和Bundle类很类似,都是使用HashMap的泛型形式来存储的,并且都是HashMap<String, Object>()。通过ContentValues可以设置列的数据,和上文中提到的一样可以在MediaStore.MediaColumns中
取公共常量字段也可以在MediaStore.MediaColumns取公共常量字段:
private fun saveImage(bitmap: Bitmap){
val values = ContentValues().apply {
//设置文件的 MimeType
put(MediaStore.Images.Media.MIME_TYPE,"image/png")
//指定保存的文件名,
put(MediaStore.Images.Media.DISPLAY_NAME,"${System.currentTimeMillis()}.png")
//指定保存的文件目录,如果不设置这个值,则会被默认保存到对应的媒体类型的文件夹下,
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
put(MediaStore.Images.Media.RELATIVE_PATH,"${Environment.DIRECTORY_PICTURES}/DemoPicture")
} else {
put(MediaStore.MediaColumns.DATA,"${Environment.getExternalStorageDirectory().path}${File.separator}${Environment.DIRECTORY_DCIM}${File.separator}${System.currentTimeMillis()}.png")
}
}
//插入文件数据库并获取到文件的Uri
val insertUri = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,values)
insertUri?.let {
//通过outputStream将图片文件内容写入Url
contentResolver.openOutputStream(it).use {outputStream->
bitmap.compress(Bitmap.CompressFormat.PNG,100,outputStream)
}
}
}
对于 Android 中的媒体类型,应该按照不同的MimeType放到对应的公共目录媒体文件夹下:
| 媒体类型 | Uri路径 | MediaStore内部类常量 | 文件类型(MimeType) | 默认存储目录 | 允许存储目录 |
|---|---|---|---|---|---|
| Image(图片) | content://media/external/images/media | MediaStore.Images.Media.EXTERNAL_CONTENT_URI | image/* | Pictures | DCIM、Pictures |
| Audio(音频) | content://media/external/audio/media | MediaStore.Audio.Media.EXTERNAL_CONTENT_URI | audio/* | Music | Alarms、Music、Notifications、Podcasts、Ringtones |
| Video(视频) | content://media/external/video/media | MediaStore.Video.Media.EXTERNAL_CONTENT_URI | video/* | Movies | DCIM 、Movies |
| Files(下载) | content://media/external/downloads | MediaStore.Downloads.EXTERNAL_CONTENT_URI | file/* | Download | Download |
当通过MediaStore API创建文件时,文件会保存到对应的类型默认目录,例如图片文件(mimeType = image/*)会被保存到Pictures(Environment#DIRECTORY_PICTURES) 目录下;也可以使用MediaStore.xxx.Media.RELATIVE_PATH自己指定要存放的目录或者子目录,如:contentValues.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES + "/自定义子目录"),文件就会放在Pictures/自定义子目录/ 中;或者使用contentValues.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_DCIM),将文件放到DCIM/ 中。
需要注意的是,不能将文件放置到允许存储目录之外的其他文件夹下,比如将一个 mimeType 为 audio/mpeg 放到 Pictures目录下,这样的行为是不被允许的,也就是如果设置 MIME_TYPE = audia/* 并将 RELATIVE_PATH 设置为 Environment#DIRECTORY_PICTURES 这样是会报 **Throw IllegalArgumentException **。当然也可以将所有的文件都通过 MediaStore 放到 Downloads 文件夹下。
是MediaStore.Images.Media.RELATIVE_PATH是在Android10才被添加进来的因此在低版本中可以通过FilePath去适配。
我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div
我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看rubyzip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d
类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc
很好奇,就使用rubyonrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提
假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于
我正在尝试使用ruby和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我
关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。
我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t
我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h
我想为Heroku构建一个Rails3应用程序。他们使用Postgres作为他们的数据库,所以我通过MacPorts安装了postgres9.0。现在我需要一个postgresgem并且共识是出于性能原因你想要pggem。但是我对我得到的错误感到非常困惑当我尝试在rvm下通过geminstall安装pg时。我已经非常明确地指定了所有postgres目录的位置可以找到但仍然无法完成安装:$envARCHFLAGS='-archx86_64'geminstallpg--\--with-pg-config=/opt/local/var/db/postgresql90/defaultdb/po