Android Glide AppGlideModule DataFetcher loadThumbnail, Kotlin

Android Glide AppGlideModule DataFetcher loadThumbnail, Kotlin

XML 复制代码
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
    <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
XML 复制代码
plugins {
    id 'org.jetbrains.kotlin.kapt'
}


    implementation 'com.github.bumptech.glide:glide:4.16.0' //4.16.0
    kapt 'com.github.bumptech.glide:compiler:4.16.0' //4.16.0
Kotlin 复制代码
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Color
import android.graphics.drawable.Drawable
import android.net.Uri
import android.os.Bundle
import android.provider.MediaStore
import android.text.TextUtils
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.AppCompatImageView
import androidx.core.content.ContextCompat
import androidx.core.view.setPadding
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.signature.MediaStoreSignature
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.text.SimpleDateFormat


class MainActivity : AppCompatActivity() {
    companion object {
        const val TAG = "fly/MainActivity"

        const val THUMB_SIZE = 10
        const val SIZE = 400

        const val VIEW_TYPE = 0
        const val DATE_TYPE = 1

        const val SPAN_COUNT = 8
        const val PAD_SIZE = 1
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.rv_layout)

        val rv: RecyclerView = findViewById(R.id.rv)

        val layoutManager = MyGridLayoutManager(this, SPAN_COUNT)
        layoutManager.orientation = LinearLayoutManager.VERTICAL
        rv.layoutManager = layoutManager

        val adapter = MyAdapter(this)
        rv.adapter = adapter

        lifecycleScope.launch(Dispatchers.IO) {
            val imgs = readAllImage(this@MainActivity)
            val videos = readAllVideo(this@MainActivity)

            val items = arrayListOf<MyData>()
            items.addAll(videos)
            items.addAll(imgs)

            items.sortByDescending {
                it.dateModified
            }

            val lists = items.distinctBy {
                it.dateString
            }

            lists.forEach { it_lists ->
                val idx = items.indexOfFirst {
                    it_lists.dateString == it.dateString
                }

                val data = MyData()
                data.type = DATE_TYPE
                data.dateString = it_lists.dateString
                items.add(idx, data) //不要直接加 it_Lists,这里面涉及到List的深拷贝/浅拷贝问题。
            }

            withContext(Dispatchers.Main) {
                adapter.dataChanged(items)
            }
        }

        layoutManager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
            override fun getSpanSize(position: Int): Int {
                return if (adapter.getItemViewType(position) == DATE_TYPE) {
                    //group,标题
                    SPAN_COUNT
                } else {
                    //单个小格子
                    1
                }
            }
        }
    }

    class MyGridLayoutManager : GridLayoutManager {
        constructor(ctx: Context, cnt: Int) : super(ctx, cnt) {

        }

        override fun getExtraLayoutSpace(state: RecyclerView.State?): Int {
            return 0
        }
    }

    class MyAdapter : RecyclerView.Adapter<MyVH> {
        private var mItems = arrayListOf<MyData>()

        private var mContext: Context? = null
        private var mPlaceholder: Drawable? = null
        private var mError: Drawable? = null

        constructor(ctx: Context) {
            mContext = ctx

            mPlaceholder = ContextCompat.getDrawable(mContext!!, android.R.drawable.ic_menu_gallery)
            mError = ContextCompat.getDrawable(mContext!!, android.R.drawable.stat_sys_warning)
        }

        fun dataChanged(items: ArrayList<MyData>) {
            this.mItems = items
            notifyDataSetChanged()
        }

        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyVH {
            val v: View?
            if (viewType == VIEW_TYPE) {
                v = MyIV(mContext!!)
            } else {
                v = LayoutInflater.from(mContext!!).inflate(android.R.layout.simple_list_item_1, null)
                v.setBackgroundColor(Color.LTGRAY)
            }

            return MyVH(v!!)
        }

        override fun getItemCount(): Int {
            return mItems.size
        }

        override fun getItemViewType(position: Int): Int {
            return mItems[position].type
        }

        override fun onBindViewHolder(holder: MyVH, @SuppressLint("RecyclerView") position: Int) {
            val type = getItemViewType(holder.adapterPosition)

            if (type == VIEW_TYPE) {
                val miv = holder.itemView as MyIV

                val signature = MediaStoreSignature("", 0, 0)
                val mt = MyThumb(mItems[holder.adapterPosition], SIZE, SIZE, signature)
                val thumbReq = GlideApp.with(mContext!!)
                    .asBitmap()
                    .load(mt)
                    .override(THUMB_SIZE)
                    .diskCacheStrategy(mt.diskCacheStrategy())
                    .signature(signature)

                GlideApp.with(mContext!!)
                    .asBitmap()
                    .load(mItems[holder.adapterPosition].uri)
                    .thumbnail(thumbReq)
                    .override(SIZE)
                    .centerCrop()
                    .placeholder(mPlaceholder)
                    .error(mError)
                    .into(miv)
            } else if (type == DATE_TYPE) {
                holder.itemView.findViewById<TextView>(android.R.id.text1).text = "${mItems[holder.adapterPosition].dateString}"
            }
        }
    }

    class MyVH : RecyclerView.ViewHolder {
        constructor(itemView: View) : super(itemView) {

        }
    }

    class MyIV : AppCompatImageView {
        constructor(ctx: Context) : super(ctx) {
            setPadding(PAD_SIZE)
        }

        override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec)
            val w = MeasureSpec.getSize(widthMeasureSpec)
            val h = MeasureSpec.getSize(heightMeasureSpec)
            val size = Math.max(w, h) //取w,h的最大值。
            setMeasuredDimension(size, size) //使得ImageView为正方形。
        }
    }

    class MyData {
        var type = VIEW_TYPE

        var dateModified: Long? = 0L
        var dateString: String? = null
        var filePath: String? = null
        var uri: Uri? = null
        var width = 0
        var height = 0
        var size = 0L
        var name: String? = null

        private var hashCodeVal = 0

        override fun equals(other: Any?): Boolean {
            if (other is MyData) {
                return (dateModified == other.dateModified)
                        && (filePath == other.filePath)
                        && (uri == other.uri)
                        && (width == other.width)
                        && (height == other.height)
                        && (size == other.size)
                        && (name == other.name)
            }

            return false
        }

        override fun hashCode(): Int {
            if (hashCodeVal == 0) {
                hashCodeVal = dateModified.hashCode()
                hashCodeVal = hashCodeVal * 31 + filePath.hashCode()
                hashCodeVal = hashCodeVal * 31 + uri.hashCode()
                hashCodeVal = hashCodeVal * 31 + width
                hashCodeVal = hashCodeVal * 31 + height
                hashCodeVal = hashCodeVal * 31 + size.toInt()
                hashCodeVal = hashCodeVal * 31 + name.hashCode()
            }

            return hashCodeVal
        }

        override fun toString(): String {
            return "MyData(type=$type, dateModified=$dateModified, dateString=$dateString, filePath=$filePath, uri=$uri, width=$width, height=$height, size=$size, name=$name, hashCodeVal=$hashCodeVal)"
        }
    }

    private fun readAllImage(context: Context): ArrayList<MyData> {
        val items = ArrayList<MyData>()

        //读所有图资源
        val cursor = context.contentResolver.query(
            MediaStore.Images.Media.EXTERNAL_CONTENT_URI,//MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
            null,
            null,
            null,
            null
        )

        val sdf = SimpleDateFormat("yyyy-MM-dd")
        val baseUri = Uri.parse("content://media/external/images/media")
        while (cursor!!.moveToNext()) {
            //文件路径
            val path = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA))
            if (TextUtils.isEmpty(path)) {
                continue
            }

            val id = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID))
            val uri = Uri.withAppendedPath(baseUri, "" + id)

            val dateModified = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATE_MODIFIED))

            val width = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.WIDTH))
            val height = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.HEIGHT))

            //名称
            val name = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME))

            //大小
            val size = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.SIZE))

            val dateStr = sdf.format(dateModified?.toLong()!! * 1000)

            val data = MyData()
            data.type = VIEW_TYPE
            data.filePath = path
            data.uri = uri
            data.dateModified = dateModified.toLong()
            data.dateString = dateStr
            data.width = width?.toIntOrNull() ?: -1
            data.height = height?.toIntOrNull() ?: -1
            data.size = size
            data.name = name

            items.add(data)
        }
        cursor.close()

        Log.d(TAG, "image size=${items.size}")

        return items
    }

    private fun readAllVideo(context: Context): ArrayList<MyData> {
        val items = ArrayList<MyData>()

        //读所有视频资源
        val cursor = context.contentResolver.query(
            MediaStore.Video.Media.EXTERNAL_CONTENT_URI,//MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
            null,
            null,
            null,
            null
        )

        val sdf = SimpleDateFormat("yyyy-MM-dd")
        val baseUri = Uri.parse("content://media/external/video/media")
        while (cursor!!.moveToNext()) {
            //文件路径
            val path = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DATA))
            if (TextUtils.isEmpty(path)) {
                continue
            }

            val id = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Video.Media._ID))
            val uri = Uri.withAppendedPath(baseUri, "" + id)

            val dateModified = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DATE_MODIFIED))

            //名称
            //val name = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME))
            //大小
            //val size = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.SIZE))

            val dateStr = sdf.format(dateModified?.toLong()!! * 1000)

            val data = MyData()
            data.type = VIEW_TYPE
            data.filePath = path
            data.uri = uri
            data.dateModified = dateModified.toLong()
            data.dateString = dateStr

            items.add(data)
        }
        cursor.close()

        Log.d(TAG, "video size=${items.size}")

        return items
    }
}
Kotlin 复制代码
import android.content.Context
import android.graphics.Bitmap
import android.util.Log
import com.bumptech.glide.Glide
import com.bumptech.glide.GlideBuilder
import com.bumptech.glide.Registry
import com.bumptech.glide.annotation.GlideModule
import com.bumptech.glide.load.DecodeFormat
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.load.engine.cache.InternalCacheDiskCacheFactory
import com.bumptech.glide.load.engine.cache.MemorySizeCalculator
import com.bumptech.glide.load.engine.executor.GlideExecutor
import com.bumptech.glide.module.AppGlideModule
import com.bumptech.glide.request.RequestOptions


@GlideModule
class MyGlideModule : AppGlideModule() {
    override fun applyOptions(context: Context, builder: GlideBuilder) {
        super.applyOptions(context, builder)
        builder.setLogLevel(Log.VERBOSE)

        val memoryCacheScreens = 200F
        val maxSizeMultiplier = 0.8F

        val calculator = MemorySizeCalculator.Builder(context)
            .setMemoryCacheScreens(memoryCacheScreens)
            .setBitmapPoolScreens(memoryCacheScreens / 5)
            .setMaxSizeMultiplier(maxSizeMultiplier)
            .setLowMemoryMaxSizeMultiplier(maxSizeMultiplier * 0.8F)
            .setArrayPoolSize(((1024 * 1024 * memoryCacheScreens / 5).toInt()))
            .build()
        builder.setMemorySizeCalculator(calculator)

        val diskCacheSize = 1024 * 1024 * 1000L
        builder.setDiskCache(InternalCacheDiskCacheFactory(context, diskCacheSize))

        val mSourceBuilder = GlideExecutor.newSourceBuilder()
            .setUncaughtThrowableStrategy(GlideExecutor.UncaughtThrowableStrategy.LOG)
            .setThreadCount(4)
            //.setThreadTimeoutMillis(1000) //线程读写超时时间。
            .setName("src")
            .build()

        val mDiskCacheBuilder = GlideExecutor.newDiskCacheBuilder()
            .setThreadCount(1)
            //.setThreadTimeoutMillis(1000) //线程读写超时时间。
            .setName("diskcache")
            .build()

        val mAnimationBuilder = GlideExecutor.newAnimationBuilder()
            .setThreadCount(1)
            //.setThreadTimeoutMillis(1000) //线程读写超时时间。
            .setName("anim")
            .build()

        builder.setSourceExecutor(mSourceBuilder)
        builder.setDiskCacheExecutor(mDiskCacheBuilder)
        builder.setAnimationExecutor(mAnimationBuilder)

        val requestOptions = RequestOptions()
            .diskCacheStrategy(DiskCacheStrategy.AUTOMATIC)
            .format(DecodeFormat.PREFER_ARGB_8888)
            .encodeQuality(100)
            .sizeMultiplier(1f)

        builder.setDefaultRequestOptions(requestOptions)
    }

    override fun isManifestParsingEnabled(): Boolean {
        return false
    }

    override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
        super.registerComponents(context, glide, registry)
        registry.append(MyThumb::class.java, Bitmap::class.java, MyThumbModelLoaderFactory(context))
    }
}
Kotlin 复制代码
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.signature.MediaStoreSignature

class MyThumb {
    var mData: MainActivity.MyData? = null
    var mWith: Int = 0
    var mHeight: Int = 0
    var mSignature: MediaStoreSignature? = null
    private var mHashCode = 0

    constructor(d: MainActivity.MyData, w: Int, h: Int, signature: MediaStoreSignature) {
        this.mData = d
        this.mWith = w
        this.mHeight = h
        this.mSignature = signature
    }

    override fun equals(other: Any?): Boolean {
        if (other is MyThumb) {
            return (mData == other.mData)
                    && (mWith == other.mWith)
                    && (mHeight == other.mHeight)
                    && (mSignature == other.mSignature)
        }

        return false
    }

    fun diskCacheStrategy(): DiskCacheStrategy {
        return DiskCacheStrategy.NONE
    }

    override fun hashCode(): Int {
        if (mHashCode == 0) {
            mHashCode = mData.hashCode()
            mHashCode = 31 * mHashCode + mWith
            mHashCode = 31 * mHashCode + mHeight
            mHashCode = 31 * mHashCode + mSignature.hashCode()
        }

        return mHashCode
    }

    override fun toString(): String {
        return "MyThumb(mData=${mData.toString()}, mWith=$mWith, mHeight=$mHeight, mSignature=$mSignature)"
    }
}
Kotlin 复制代码
import android.content.Context
import android.graphics.Bitmap
import android.os.CancellationSignal
import android.util.Log
import android.util.Size
import com.bumptech.glide.Priority
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.data.DataFetcher


class MyThumbDataFetcher : DataFetcher<Bitmap> {
    private var mThumb: MyThumb? = null

    private var mContext: Context? = null
    private var mSignal: CancellationSignal? = null

    companion object {
        const val TAG = "fly/MyThumbDataFetcher"
    }

    constructor(c: Context, m: MyThumb) {
        mContext = c
        mThumb = m
        mSignal = CancellationSignal()
    }

    override fun loadData(priority: Priority, callback: DataFetcher.DataCallback<in Bitmap>) {
        val t = System.currentTimeMillis()

        var bmp: Bitmap? = null
        try {
            bmp = mContext?.contentResolver?.loadThumbnail(
                mThumb?.mData?.uri!!,
                Size(MainActivity.THUMB_SIZE, MainActivity.THUMB_SIZE),
                mSignal
            )
        } catch (e: Exception) {
            Log.e(TAG, "loadThumbnail $e")
        }

        if (bmp != null) {
            Log.d(MainActivity.TAG, "loadData 耗时:${System.currentTimeMillis() - t} MyData=${mThumb?.mData.toString()}")
            callback.onDataReady(bmp)
        } else {
            mSignal?.cancel()
            callback.onLoadFailed(Exception("loadThumbnail Error ${mThumb?.mData?.toString()}"))
        }
    }

    override fun cleanup() {

    }

    override fun cancel() {
        mSignal?.cancel()
    }

    override fun getDataClass(): Class<Bitmap> {
        return Bitmap::class.java
    }

    override fun getDataSource(): DataSource {
        Log.d(MainActivity.TAG, "getDataSource MyData=${mThumb?.mData.toString()}")
        return DataSource.LOCAL
    }
}
Kotlin 复制代码
import com.bumptech.glide.load.Key
import java.security.MessageDigest

class MyThumbKey : Key {
    private var mModel: MyThumb? = null
    private var mHashCode = 0

    constructor(m: MyThumb) {
        mModel = m
    }

    override fun updateDiskCacheKey(messageDigest: MessageDigest) {
        //messageDigest.update(mModel.toString().toByte())
    }

    override fun equals(other: Any?): Boolean {
        if (other is MyThumbKey) {
            return mModel?.mData == other.mModel?.mData
        }

        return false
    }

    override fun hashCode(): Int {
        if (mHashCode == 0) {
            mHashCode = 31 * mModel.hashCode()
        }

        return mHashCode
    }
}
Kotlin 复制代码
import android.content.Context
import android.graphics.Bitmap
import android.util.Log
import com.bumptech.glide.load.Options
import com.bumptech.glide.load.model.ModelLoader

class MyThumbModelLoader : ModelLoader<MyThumb, Bitmap> {
    private var mContext: Context? = null

    constructor(c: Context) {
        mContext = c
    }

    override fun buildLoadData(model: MyThumb, width: Int, height: Int, options: Options): ModelLoader.LoadData<Bitmap> {
        Log.d(MainActivity.TAG, "buildLoadData w=$width h=$height model=$model")
        return ModelLoader.LoadData(MyThumbKey(model), MyThumbDataFetcher(mContext!!, model))
    }

    override fun handles(model: MyThumb): Boolean {
        return true
    }
}
Kotlin 复制代码
import android.content.Context
import android.graphics.Bitmap
import com.bumptech.glide.load.model.ModelLoader
import com.bumptech.glide.load.model.ModelLoaderFactory
import com.bumptech.glide.load.model.MultiModelLoaderFactory

class MyThumbModelLoaderFactory : ModelLoaderFactory<MyThumb, Bitmap> {
    private var mContext: Context? = null

    constructor(c: Context) {
        mContext = c
    }

    override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader<MyThumb, Bitmap> {
        return MyThumbModelLoader(mContext!!)
    }

    override fun teardown() {

    }
}

Android Glide DiskCacheStrategy.NONE DataFetcher fast loadThumbnail Media, Kotlin(五)-CSDN博客文章浏览阅读819次,点赞22次,收藏7次。从很小的宽高开始,不断迭代增加setRectToRect的目标RectF的宽高,每次迭代加上一定时延,实现Matrix基础上的动画。【代码】Android Paging 3,kotlin(1)在实际的开发中,虽然Glide解决了快速加载图片的问题,但还有一个问题悬而未决:比如用户的头像,往往用户的头像是从服务器端读出的一个普通矩形图片,但是现在的设计一般要求在APP端的用户头像显示成圆形头像,那么此时虽然Glide可以加载,但加载出来的是一个矩形,如果要Glide_android 毛玻璃圆角。https://blog.csdn.net/zhangphil/article/details/140093531

相关推荐
呆呆小孩28 分钟前
零基础学 MySQL(上):从建库建表到增删改查,一篇搭好 SQL 基础
android·sql·mysql
catchadmin38 分钟前
NativePHP v4 让 Blade 构建原生 iOS 与 Android 界面
android·ios·ai·php
FfHUCisI2 小时前
Golang 数据库连接池深度调优
android·数据库·golang
古法安卓5 小时前
Android-epoll原理详解
android·java·android studio
字节暗面7 小时前
四款 APP 加固强度测评:让 AI 拆了四个包,结果有点出乎意料
android·逆向工程·加固
网安蟹佬霸7 小时前
蓝队防御实战:SIEM与SOAR构建安全运营中心(万字详解+实战配置)
android·安全·web安全·网络安全·网安
敢敢のwings8 小时前
世界模型与FlashWorld:从“在梦中学习“到秒级3D场景生成
android·学习·3d
Dovis(誓平步青云)9 小时前
牛奶不能减成负数:冰箱库存的筛选、去重与不可变更新
android·java·开发语言
Awna9 小时前
MySQL 存储空间与索引运维实战:从空间排查到在线扩容
android·运维·mysql
智购科技无人售货机工厂10 小时前
2026自动售货机触摸屏驱动开发:从I2C协议到多点触控校准的工程实践~YH
android·驱动开发·python·单片机·云原生·django