广告设计师工作内容,邢台一天seo,个人介绍网站内容,网站添加白名单官方页面 参考文章
一、概念 分区存储#xff08;Scoped Storage#xff09;的推出是针对 APP 访问外部存储的行为#xff08;乱建乱获取文件和文件夹#xff09;进行规范和限制#xff0c;以减少混乱使得用户能更好的控制自己的文件。 公有目录被分为两大类#xff1a;…官方页面 参考文章
一、概念 分区存储Scoped Storage的推出是针对 APP 访问外部存储的行为乱建乱获取文件和文件夹进行规范和限制以减少混乱使得用户能更好的控制自己的文件。 公有目录被分为两大类媒体文件图片、音频、视频的访问使用 MediaStore其它文件通过系统的文件选择器访问 Storage Access Framework简称SAF。 二、MediaStore
跳转ContentProvider
class MediaStore.Images所有图片内容的类。class MediaStore.Video所有视频内容的类。class MediaStore.Audio所有音频内容的类。class MediaStore.Files文件储存库中所有文件的索引包括非媒体文件和媒体文件类。interface MediaStore.MediaColumns文件储存库中表的公共字段文件的各种信息。
2.1 获取 Uri 使用 Context 获取到 ContentResolver 对象通过 Uri 即可获取各种媒体库的 ContentProvider从而对媒体文件进行操作。 文件类型MediaStore 常量Uri 地址图片MediaStore.Images.Media.EXTERNAL_CONTENT_URIcontent://media/external/images/media视频MediaStore.Video.Media.EXTERNAL_CONTENT_URIcontent://media/external/video/media音频MediaStore.Audio.Media.EXTERNAL_CONTENT_URIcontent://media/external/audio/media非媒体文件MediaStore.Downloads.Media.EXTERNAL_CONTENT_URIcontent://media/external/downloads
val uri1 Uri.parse(content://media/external/images/media)
val uri2 MediaStore.Images.Media.getContentUri(external)
val uri3 MediaStore.Images.Media.EXTERNAL_CONTENT_URI //推荐
2.2 读取媒体文件 列名文件信息可以在 MediaStore.MediaColumns 取公共常量字段也可以根据文件类型的不同在具体内部类中取值。 文件类型MediaStore 常量常用列名说明图片 MediaStore.Images.Media._ID 磁盘上文件的路径 MediaStore.Images.Media.DATA 磁盘上文件的路径 MediaStore.Images.Media.DATE_ADDED 文件添加到media provider的时间单位秒 MediaStore.Images.Media.DATE_MODIFIED 文件最后一次修改单元的时间 MediaStore.Images.Media.DISPLAY_NAME 文件的显示名称 MediaStore.Images.Media.HEIGHT 图像/视频的高度以像素为单位 MediaStore.Images.Media.MIME_TYPE 文件的 MIME 类型 MediaStore.Images.Media.SIZE 文件的字节大小 MediaStore.Images.Media.TITLE 标题 MediaStore.Images.Media.WIDTH 图像/视频的宽度以像素为单位视频 MediaStore.Video.Media.TITLE 名称 MediaStore.Video.Media.DURATION 总时长 MediaStore.Video.Media.DATA 地址 MediaStore.Video.Media.SIZE 大小 MediaStore.Video.Media.WIDTH 视频的宽度以像素为单位 MediaStore.Video.Media.HEIGHT 视频的高度以像素为单音频 MediaStore.Audio.Media.TITLE 歌名 MediaStore.Audio.Media.ARTIST 歌手 MediaStore.Audio.Media.DURATION 总时长 MediaStore.Audio.Media.DATA 地址 MediaStore.Audio.Media.SIZ 大小 public final Cursor query ( Uri uri, //要查询的 ContentProvider 的 Uri String[] projection, //要查询的字段列Column用 null 表示返回所有字段内容。 String selection, //查询条件相当于SQL语句中的where用 null 表示不进行筛选。 String[] selectionArgs, //如果 selection 里有?符号这里可以以实际值代替。没有的话可以为null。 String sortOrder //对结果进行排序相当于SQL语句中的Order by升序 asc /降序 descnull为默认排序。 ) 返回的是一个封装了结果集的游标对象 Cursor 资源用完需要调用 close() 关闭。
//获取图片类型的Uri
val uri MediaStore.Images.Media.EXTERNAL_CONTENT_URI
//要获取的信息列名
val projection arrayOf(MediaStore.Images.Media._ID, //获取IDMediaStore.Images.Media.MIME_TYPE, //获取MIME_TYPEMediaStore.Images.Media.DISPLAY_NAME //获取DISPLAY_NAME
)
//筛选条件(png格式的图片)
val selection ${MediaStore.Images.Media.DISPLAY_NAME}.png // xx.png 改成 ?
//筛选条件的参数
val selectionArgs arrayOf(.png) //替换筛选条件语句中?部分
//对结果的排序方式
val sortOrder ${ContactsContract.Contacts._ID} DESC //注意desc前有空格
//开始查询返回的是一个封装了结果集的游标对象资源用完需要关闭使用use函数
contentResolver.query(uri, projection, selection, selectionArgs, sortOrder)?.use { cursor -//表都是通过行和列定位到具体的位置然后数据将其取出cursor.run {//获取字段在第几列查询什么才能取出什么否则空指针异常val idIndex getColumnIndexOrThrow(MediaStore.Images.Media._ID)val mimeTypeIndex cursor.getColumnIndexOrThrow(MediaStore.Images.Media.MIME_TYPE)val displayNameIndex cursor.getColumnIndex(MediaStore.Images.Media.DISPLAY_NAME)//循环取出每一行对应字段的数据while (moveToNext()) {val id getLong(idIndex)val mineType getString(mimeTypeIndex)val displayName getString(displayNameIndex)//合成图片的UriContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id)//TODO...}}
}
//获取到的 Uri 可以通过 Glide 显示
Glide.with(context).load(uri).into(imageView)
//手动解析成图片的话
contentResolver.openFileDescriptor(uri, )?.use {val bitmap BitmapFactory.decodeFileDescriptor(it.fileDescriptor)imageView.setImageBitmap(bitmap)
}
2.3 写入媒体文件 通过 MediaStore 创建文件会保存到对应类型的默认目录中也可以指定存放到其它同类型的公有目录或子文件夹中。如果存放到不同类型的公有目录中会报错 IllegalArgumentException但是三种都可以存到Download中。 文件类型mimeType 文件类型默认存储目录其它允许存储目录图片image/*PicturesDICM视频video/*MoviesDICM音频audio/*MusicAlarms、Notifications、Podcasts、Ringtones文件file/*Download public final Uri insert( Uri url, ContentValues values) 构造一个 ContentValues 对象通过 ContentResolver.insert 插入到对应的目录中对返回的 Uri 对象进行文件流写入即可。
val values ContentValues().apply {//指定 MimeTypeput(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) {//Android 10中新增了一个RELATIVE_PATH常量表示文件存储的相对路径可选值有DIRECTORY_DCIM、DIRECTORY_PICTURES、DIRECTORY_MOVIES、DIRECTORY_MUSICput(MediaStore.Images.Media.RELATIVE_PATH, ${Environment.DIRECTORY_PICTURES}/DemoPicture)} else {//之前的系统版本中并没有RELATIVE_PATH所以要使用 DATA 并拼装出一个文件存储的绝对路径才行put(MediaStore.MediaColumns.DATA, ${Environment.getExternalStorageDirectory().path}${File.separator}${Environment.DIRECTORY_DCIM}${File.separator}${System.currentTimeMillis()}.png)}
}
//插入文件数据库并获取到文件的Uri
val uri contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
//对Uri进行文件流写入
uri?.let {//通过outputStream将本地图片bitmap或网络图片输入流写入UrlcontentResolver.openOutputStream(it)?.use { outputStream -//TODO...//bitmap.compress(Bitmap.CompressFormat.PNG,100, outputStream)}
}
2.4 下载文件到Download目录 方式和上面的写入一样将网络获取的输入流写入。 注意MediaStore.Downloads是Android 10中新增的APIAndroid 9及以下的系统版本仍然使用之前的代码来进行文件下载。
val inputStream XXX.inputStream
val bis BufferedInputStream(inputStream)
val buffer ByteArray(1024)//对Uri进行文件流写入
insertUri?.let {//通过outputStream将本地bitmap或网络输入流写入UrlcontentResolver.openOutputStream(it)?.use { outputStream -BufferedOutputStream(outputStream).use { bos -var bytes bis.read(buffer)while (bytes 0) {bos.write(buffer, 0, bytes)bos.flush()bytes bis.read(buffer)}}}
}
三、使用文件选择器 SAF 对于非媒体文件无法像之前那样手写一个文件浏览器而是必须使用系统提供的内置文件选择器。通过 Intent 启动系统的文件选择器然后在 onActivityResult() 中获取到用户选中文件的 Uri 通过ContentResolver打开文件输入流来进行读取就可以了。 const val PICK_FILE 1private fun pickFile() {val intent Intent(Intent.ACTION_OPEN_DOCUMENT)intent.addCategory(Intent.CATEGORY_OPENABLE)intent.type */*startActivityForResult(intent, PICK_FILE)
}override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {super.onActivityResult(requestCode, resultCode, data)when (requestCode) {PICK_FILE - {if (resultCode Activity.RESULT_OK data ! null) {val uri data.dataif (uri ! null) {val inputStream contentResolver.openInputStream(uri)// 执行文件读取操作}}}}
}四、第三方库不支持的解决办法 编写一个文件复制功能将Uri对象所对应的文件复制到应用程序的关联目录下然后再将关联目录下这个文件的绝对路径传递给第三方SDK这样就可以完美进行适配了。 fun copyUriToExternalFilesDir(uri: Uri, fileName: String) {val inputStream contentResolver.openInputStream(uri)val tempDir getExternalFilesDir(temp)if (inputStream ! null tempDir ! null) {val file File($tempDir/$fileName)val fos FileOutputStream(file)val bis BufferedInputStream(inputStream)val bos BufferedOutputStream(fos)val byteArray ByteArray(1024)var bytes bis.read(byteArray)while (bytes 0) {bos.write(byteArray, 0, bytes)bos.flush()bytes bis.read(byteArray)}bos.close()fos.close()}
}五、管理设备上所有的文件公有目录 自定义目录 绝大部分的应用程序都不应该申请这个权限仅适用于文件浏览器、病毒查杀类APP需要跳转到系统页面让用户手动授权Play商店上架也会更严格。即便得到授权也只能访问 公有目录 自定义目录依然无法访问私有目录。 5.1 权限声明
//不加 ignore 属性 AndroidStudio 会用警告提醒。
uses-permission android:nameandroid.permission.MANAGE_EXTERNAL_STORAGEtools:ignoreScopedStorage /
5.2 跳转系统页面授权
//系统低于11或者方法返回true说明已经拥有整个SD卡管理权限
if (Build.VERSION.SDK_INT Build.VERSION_CODES.R || Environment.isExternalStorageManager()) {Toast.makeText(this, 已获得访问所有文件权限, Toast.LENGTH_SHORT).show()
} else {//否则弹窗告知申请原因并跳转到系统授权界面让用户手动授权val builder AlertDialog.Builder(this).setMessage(本程序需要您同意允许访问所有文件权限).setPositiveButton(确定) { _, _ -val intent Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION)startActivity(intent)}builder.show()
}六、修改其它APP贡献的文件 修改其它APP贡献的文件是不安全的行为默认情况下会抛异常需要跳转到系统页面让用户手动授权仅适用于美图秀秀类APP。在 Android 10 中每次跳转授权只能操作一张图片如果一个程序需要修改很多张图片会很麻烦在 Android 11 中提供了 Batch Operations 从而一次性对多个文件的操作权限进行申请。 由于10 之前没有分区存储10 和 11以后是两套处理方案专门针对 10 一个版本去写处理方案会很麻烦由于 10 不是强制启用分区存储可以在 AndroidManifest 中配置 requestLegacyExternalStorage 来禁用。 createWriteRequest() 请求对多个文件的写入权限。 createFavoriteRequest() 请求将多个文件加入到Favorite收藏的权限。 createTrashRequest() 请求将多个文件移至回收站的权限。 createDeleteRequest() 请求将多个文件删除的权限。
if (Build.VERSION.SDK_INT Build.VERSION_CODES.R) {/创建了一个集合用于存放所有要批量申请权限的文件Urival urisToModify listOf(uri1, uri2, uri3, uri4)//创建一个PendingIntentval editPendingIntent MediaStore.createWriteRequest(contentResolver, urisToModify)//进行权限申请startIntentSenderForResult(editPendingIntent.intentSender, EDIT_REQUEST_CODE, null, 0, 0, 0)
}override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {super.onActivityResult(requestCode, resultCode, data)when (requestCode) {EDIT_REQUEST_CODE - {if (resultCode Activity.RESULT_OK) {Toast.makeText(this, 用户已授权, Toast.LENGTH_SHORT).show()} else {Toast.makeText(this, 用户没有授权, Toast.LENGTH_SHORT).show()}}}
}