MediaStore访问图片、视频


使用”MediaStore”访问图片、视频的缩略图

需要动态申请权限

需要配置文件中添加要 申请的权限 “AndroidManifest.xml”

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

注册动态权限

  • 需要自定义 “checkReadExternalStoragePermission()”方法

  • 需要 “Build.VERSION.SDK_INT”方法获得当前系统的版本号

  • 需要 “Build.VERSION_CODES.M”表示安卓6

  • 需要 “ContextCompat.checkSelfPermission()”方法, 判断是否获得了权限.

参数1:上下文.参数2:要判断的权限.

返回值:PackageManager.PERMISSION_GRANTED 表示授予权限,PackageManager.PERMISSION_DENIED = -1 表示权限未开启

  • 需要 “shouldShowRequestPermissionRationale()”方法.

判断当前权限是否被拒绝,并且显示窗口,显示”运行”和”拒绝”选项.

第一次不显示”不再提醒”按钮,第二次窗口添加”不再显示选择框”。

返回值:拒绝返回 true ,允许返回 false 或者 不再提醒选中后 返回 false

  • 需要 “requestPermission()”方法.

用来申请系统权限. 每次被调用时都会回调”onRequestPermissionsResult()”方法。

参数1:要申请的权限.

参数2:要申请权限的 code 以用来在申请方法回调时确定是当前的申请操作.

在需要注册权限的活动上添加

    private fun checkReadExternalStoragePermission() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            if (ContextCompat.checkSelfPermission(
                    this,
                    permission.READ_EXTERNAL_STORAGE
                ) == PackageManager.PERMISSION_GRANTED
            ) {       
            } else {
                if (shouldShowRequestPermissionRationale(permission.READ_EXTERNAL_STORAGE)) {
                    Toast.makeText(this, "App needs to view thumbnails", Toast.LENGTH_LONG).show()
                }

                requestPermissions(
                    arrayOf(permission.READ_EXTERNAL_STORAGE),
                    READ_EXTERNAL_STORAGE_PERMISSION_RESULT
                )
            }
        } else {

        }
    }
    private val READ_EXTERNAL_STORAGE_PERMISSION_RESULT: Int = 0
  • 需要重写 “onRequestPermissionsResult()”方法

申请权限时会调用它(申请权限时的回调)

参数1:申请权限时的Code,表示申请权限的操作是否相同.

参数2:这是要申请的权限永远不为空.

参数3:表示权限授权的结果永远封装在grantResult数组中

override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        when (requestCode) {
            READ_EXTERNAL_STORAGE_PERMISSION_RESULT -> {
                if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    Toast.makeText(this, "now have access to view thumbs", Toast.LENGTH_LONG).show()
                }
            }
        }
    }

需要RecyclerView的适配器

使用此案例创建适配器

初始化控件不再复述了,

  • 需要变量”mMediaStoreCursor”

表示当前的Cursor

class MediaStoreAdapter(private val mActivity: Activity) :
    RecyclerView.Adapter<MediaStoreAdapter.ViewHolder>() {
    private var mMediaStoreCursor: Cursor? = null    
    }
  • 需要自定义”getBitmapFromMediaStore()”方法

获得系统中图片和视频的缩略图.通过Cursor获得照片和Video的缩略图

参数1:移动到对应Cursor的行.

  • 需要使用”Cursor.getColumnIndex()”方法

获得当前MediaStoreCursor的”参数1”的索引

参数1:要索引的位置

返回值: 返回在当前 Cursor 中的位置,没有返回 -1

  • 需要使用”Cursor.getInt()’方法

获得”Cursor”中索引(列)的数值.

参数1:你想要获得的列

返回值:返回指定列的数值

  • 需要 “MediaStore.Images.Thumbnails.getThumbnail”方法

获得缩略图.

参数1:表示内容提供器. 参数2:当前”Cursor”缩略图的ID.

参数3:返回时的清晰度.MICRO_KIND,MINI_KIND字面意思为微型和迷你缩略模式,前者分辨率更低.

返回值:返回一个Bitmap格式

在适配器中添加

class MediaStoreAdapter(private val mActivity: Activity) :
    RecyclerView.Adapter<MediaStoreAdapter.ViewHolder>() {       
       private fun getBitmapFromMediaStore(position: Int): Bitmap? {
        val idIndex: Int = mMediaStoreCursor!!.getColumnIndex(MediaStore.Files.FileColumns._ID)
        val mediaTypeIndex: Int =
            mMediaStoreCursor!!.getColumnIndex(MediaStore.Files.FileColumns.MEDIA_TYPE)
        mMediaStoreCursor!!.moveToPosition(position)
        when (mMediaStoreCursor!!.getInt(mediaTypeIndex)) {
            // 当前类型为 IMAGE 时
            MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE -> {
                return MediaStore.Images.Thumbnails.getThumbnail(
                    mActivity.contentResolver,
                    mMediaStoreCursor!!.getLong(idIndex),
                    MediaStore.Images.Thumbnails.MICRO_KIND, null
                )

            }
            // 当前类型为 VIDEO
            MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO -> {
                return MediaStore.Video.Thumbnails.getThumbnail(
                    mActivity.contentResolver,
                    mMediaStoreCursor!!.getLong(idIndex),
                    MediaStore.Video.Thumbnails.MICRO_KIND, null
                )

            }

        }
        return null
    }
}
  • 需要自定义方法”swapCursor()”

获得上一个Cursor.

  • 需要 “notifyDataSetChanged()” 方法

检测当前UI线程,刷新UI

class MediaStoreAdapter(private val mActivity: Activity) :
    RecyclerView.Adapter<MediaStoreAdapter.ViewHolder>() {

   private fun swapCursor(cursor: Cursor): Cursor? {
        if (mMediaStoreCursor == cursor) {
            return null
        }
        val oldCursor: Cursor? = mMediaStoreCursor
        this.mMediaStoreCursor = cursor
        if (cursor != null) {
            this.notifyDataSetChanged()
        }
        return oldCursor
    }
}
  • 需要 自定义方法”changeCursor()”

检测上一个Cursor,用来关闭上个Cursor.

class MediaStoreAdapter(private val mActivity: Activity) :
    RecyclerView.Adapter<MediaStoreAdapter.ViewHolder>() {    
    fun changeCursor(cursor: Cursor?) {
        val oldCursor: Cursor? = swapCursor(cursor!!)
        if (oldCursor != null)
            oldCursor.close()
    }
}
  • 需要重写 “onBindViewHolder()”方法
    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val bitmap: Bitmap? = getBitmapFromMediaStore(position)
        if (bitmap != null) {
            holder.mediastoreImageView.setImageBitmap(bitmap)
        }
    }
  • 需要重写”getItemCount()”方法
    override fun getItemCount(): Int {
        return if (mMediaStoreCursor == null) 0 else mMediaStoreCursor!!.count
    }
  • 需要重写”onCreateViewHolder()”方法
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val view =
            LayoutInflater.from(parent.context).inflate(R.layout.media_image_view, parent, false)
        return ViewHolder(view)
    }

需要”LoaderManager.LoaderCallbacks”

  • 需要实现LoaderManager.LoaderCallbacks.(在MediaMainActivity)

在需要的Fragment或Activity中实现它。

class MediaMainActivity : AppCompatActivity(), LoaderManager.LoaderCallbacks<Cursor> {

}
  • 需要重写”onCreateLoader”

在调用initLoader()返回时创建一个Loader.

根据传入的ID,初始化并返回一个新的加载器.

  • 需要返回的列

MediaStore.Files.FileColums.ID 返回Id列

MediaStore.Files.FIleColums.DATE_ADDED 图片被添加的时间

MediaStore.Files.FileColums.MEDIA_TYPE 媒体类型

MediaStore.Files.FileColums.MEDIA_TYPE_IMAGE 图片列(索引)

MediaStore.Files.FileColums.MEDIA_TYPE_VIDEO 视频列(索引)

  • 需要”CursorLoader()”匿名类

它查询ContentResolver然后返回一个Cursor.

参数1:上下文. 参数2:要操作的URI.当前表示所有图片的URI.

参数3:要返回的列. 参数4:一个过滤器,表明哪些行要被返回.当前返回类型为IMAGE 或 VIDEO

参数5:用作过滤器的参数.

参数6:设置排序.相当于SQL语句中Order by.这里使用 DATA_ADDED + DESC 表示按照时间进行排序.

    override fun onCreateLoader(p0: Int, p1: Bundle?): androidx.loader.content.Loader<Cursor> {
        val projection = arrayOf<String>(
            MediaStore.Files.FileColumns._ID,
            MediaStore.Files.FileColumns.DATE_ADDED,
            MediaStore.Files.FileColumns.MEDIA_TYPE
        )
        // SQL命令
        val selection =
            MediaStore.Files.FileColumns.MEDIA_TYPE +
                    " = " + MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE + " or " + MediaStore.Files.FileColumns.MEDIA_TYPE +
                    " = " + MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO
        return object : CursorLoader(
            this,
            MediaStore.Files.getContentUri("external"),
            projection,
            selection,
            null,
            MediaStore.Files.FileColumns.DATE_ADDED + " DESC "
        ) {}

    }
  • 需要重写 “onLoadFinished()”方法

更新UI操作.当一个加载器完成了它的装载过程后被调用.

  • 需要调用changeCursor()方法

刷新当前参数1,关闭上一个Cursor

参数1:当前的Cursor

class MediaMainActivity : AppCompatActivity(), LoaderManager.LoaderCallbacks<Cursor> {
    override fun onLoadFinished(loader: androidx.loader.content.Loader<Cursor>, data: Cursor?) {
        mMediaStoreAdapter?.changeCursor(data!!)
    }
}
  • 需要重写”onLoaderReset()”方法

何时释放内存,当一个加载器完成了它的装载工作之后被调用

class MediaMainActivity : AppCompatActivity(), LoaderManager.LoaderCallbacks<Cursor> {
   override fun onLoaderReset(loader: androidx.loader.content.Loader<Cursor>) {
        mMediaStoreAdapter?.changeCursor(null)
    }
}
  • 需要在用于权限和申请权限之后添加

  • 需要在”onRequestPermissionsResult()”中调用”getSupportLoaderManager.initLoader()”

启动加载器

参数1:一个唯一的ID来标志加载器.

参数2:可选参数,用于加载初始化时(本例为null)

参数3:LoaderManager.LoaderCallbacks的实现.被LoaderManger调用以报告加载事件,在例子中是传递给自己”this”

when (requestCode) {
            READ_EXTERNAL_STORAGE_PERMISSION_RESULT -> {
                if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                //添加 supportLoaderManager.initLoader(MEDIASTORE_LODAR_ID, null, this)
                }
            }
        }
  • 需要在”checkReadExternalStoragePermission()”中调用
  • 需要在拥有权限后调用,在”checkSelfPermission()”判断拥有权限后调用
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {      
            if (ContextCompat.checkSelfPermission(
                    this,
                    permission.READ_EXTERNAL_STORAGE
                ) == PackageManager.PERMISSION_GRANTED
            ) {
              //添加  supportLoaderManager.initLoader(MEDIASTORE_LODAR_ID, null, this)
            } 
        } else {
            //添加  supportLoaderManager.initLoader(MEDIASTORE_LODAR_ID, null, this)
        }

ps:显示了在哪里添加其他的代码不用修改.

  • 需要添加唯一的ID标志加载器.
class MediaMainActivity : AppCompatActivity(), LoaderManager.LoaderCallbacks<Cursor> {
    private val MEDIASTORE_LODAR_ID: Int = 0

}
  • 需要GridLayoutManger,在 “onCreate()” 中添加

设置RecyclerView的布局

参数1:上下文 . 参数2:多少列

val gridLayoutManager: GridLayoutManager = GridLayoutManager(this, 3)
  • 需要RecyclerView的setLayoutManager

设置 GridLayoutManager

thumbnailRecyclerView.layoutManager = gridLayoutManager
  • 需要设置适配器省略..

使用 “Glide”的方式加载图片

  • 需要添加依赖

  • 需要“MediaStore.Files.FileColumns.DATA”(需要在onCreateLoader的projection修改)

表述索引在磁盘中位置

需要创建自定义返回“getUriFromMediaStore()”(在MediaStoreAdapter中添加)

获得缩略图在ContentResolver中的uri

参数1:media的那个行

需要“Uri.parse()” 方法

将字符串解析成uri对象

    private fun getUriFromMediaStore(position: Int): Uri {
        val dataIndex: Int = mMediaStoreCursor!!.getColumnIndex(MediaStore.Files.FileColumns.DATA)
        mMediaStoreCursor!!.moveToPosition(position)
        val mediaUri: Uri =
            Uri.parse("file://" + mMediaStoreCursor!!.getString(dataIndex).toString())
        return mediaUri
    }

需要“Glide” - (在onBindViewHolder()中添加)

with(content:Content) - 需要上下文

load(uri:String) - 图片的uri

centerCrop() - 将图片按比例缩放到足以填充ImageView的尺寸,但是图片可能会显示不完整。

override(width,height) 重新改变图片大小。

into() - 你需要显示图片的目标。

      Glide.with(mActivity).load(getUriFromMediaStore(position)).centerCrop().override(96, 96)
            .into(holder.mediastoreImageView)

什么是Loader

android 加载器loader详解

Loader简化了activity和Fragment中异步加载数据的步骤.

特点:适用于每个Activity和Fragment,提供异步加载的实现方式,监听数据源,在数据发生改变时自动返回新的结果。当由于配置改变后被重新创建后,它们自动重新链接上一个加载游标,所以不必重新查数据.

loader API 说明

Class/Interface 描述
LoaderManager 一个与Activity和Fragment相关联的抽象类,它管理一个或者多个Loader实例,帮助一个应用管理哪些与Activity或Fragment的声明周期相关的长时间操作.常见方式是与CursorLoader一起使用,然后应用也可以自己写一个加载其他数据类型或者数据源的loader。每个Activity或者Fragment只有一个LoaderManager.但是一个LoaderManager可以拥有多个加载器
LoaderManager.LoaderCallbacks 用于一个客户端与LoaderManager交互的会调接口.例如,你使用回调方法onCreateLoader()来创建一个新的加载器.
Loader 一个执行异步数据加载的抽象类,它是加载器的基础类.你可以使用经典的CursorLoader,但是你也可以实现你的子类.一旦加载器被激活,它们将监听它的数据源并且在数据改变时发送新的结果.
AsyncTaskLoader 提供一个AsyncTask来执行异步加载工作的抽象类
CursorLoader AsyncTaskLoader的子类,它查询ContentResolver然后返回一个Cursor.这个类为查询Cursor以标准的方式实现了加载器协议,它的游标查询是通过AsyncTaskLoader在后台线程中执行,从而不会阻断线程

如果有些方法没有详细说明你可以自行搜索查看


文章作者: TheCara
版权声明: 本博客所有文章除特別声明外,均采用 CC BY-NC 4.0 许可协议。转载请注明来源 TheCara !
  目录