Android Glide DiskCacheStrategy.NONE DataFetcher loadThumbnail, Kotlin(二)

Android Glide set DiskCacheStrategy.NONE, in DataFetcher use loadThumbnail achieve faster decode speed, Kotlin(二)

 

 


import android.annotation.SuppressLint
import android.content.ContentValues
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.load.engine.DiskCacheStrategy
import com.bumptech.glide.signature.MediaStoreSignature
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.text.SimpleDateFormat


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

        const val SIZE = 450

        const val VIEW_TYPE = 0
        const val DATE_TYPE = 1

        const val SPAN_COUNT = 6
        const val EXTRA_HEIGHT = 1000
        const val PAD_SIZE = 1
    }

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

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

        rv.setHasFixedSize(true)
        rv.setItemViewCacheSize(
            SPAN_COUNT * 10
        )

        rv.setRecyclerListener(object : RecyclerView.RecyclerListener {
            override fun onViewRecycled(holder: RecyclerView.ViewHolder) {
                if ((holder as MyVH).itemView is MyIV) {
                    GlideApp.with(holder.itemView.context).clear(holder.itemView)
                }
            }
        })

        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 items = readAllImage(this@MainActivity)

            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 EXTRA_HEIGHT
        }
    }

    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_notify_error)
        }

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

        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyVH {
            var 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
        }

        private fun getImageUri(context: Context, filePath: String): Uri? {
            val cursor = context.contentResolver.query(
                MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                arrayOf(MediaStore.Images.Media._ID),
                MediaStore.Images.Media.DATA + "=? ",
                arrayOf(filePath),
                null
            )

            return if (cursor != null && cursor.moveToFirst()) {
                val id = cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns._ID))
                val baseUri = Uri.parse("content://media/external/images/media")
                Uri.withAppendedPath(baseUri, "" + id)
            } else {
                val values = ContentValues()
                values.put(MediaStore.Images.Media.DATA, filePath)
                context.contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
            }
        }

        override fun onBindViewHolder(holder: MyVH, @SuppressLint("RecyclerView") position: Int) {
            val type = getItemViewType(holder.adapterPosition)
            if (type == VIEW_TYPE) {
                val path = mItems[holder.adapterPosition].path
                val miv = holder.itemView as MyIV

                CoroutineScope(Dispatchers.IO).launch {
                    val uri = getImageUri(mContext!!, path!!)
                    val signature = MediaStoreSignature("", 0, 0)
                    val mt = MyThumb(uri!!, SIZE, SIZE, signature)
                    Log.d(TAG, "onBindViewHolder pos=$position uri=$uri")
                    withContext(Dispatchers.Main) {
                        GlideApp.with(mContext!!)
                            .asBitmap()
                            .load(mt)
                            .diskCacheStrategy(DiskCacheStrategy.NONE)
                            .fitCenter()
                            .signature(signature)
                            .override(SIZE)
                            .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)
            scaleType = ScaleType.CENTER_CROP
        }

        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 path: String? = null
        var index: Int? = null

        override fun toString(): String {
            return "MyData(type=$type, dateModified=$dateModified, dateString=$dateString, path=$path, index=$index)"
        }
    }

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

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

        var index = 0
        val sdf = SimpleDateFormat("yyyy-MM-dd")
        while (cursor!!.moveToNext()) {
            //路径 uri
            val path = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA))
            if (TextUtils.isEmpty(path)) {
                continue
            }

            val dateModified = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.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.path = path
            data.dateModified = dateModified.toLong()
            data.dateString = dateStr
            data.index = index++

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

        return photos
    }
}

 

 



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)
            .setMaxSizeMultiplier(maxSizeMultiplier)
            .setLowMemoryMaxSizeMultiplier(maxSizeMultiplier * 0.8F)
            .setArrayPoolSize(((1024 * 1024 * memoryCacheScreens / 2).toInt()))
            .build()
        builder.setMemorySizeCalculator(calculator)

        val memoryCache = MyLruResourceCache.Instance().init(context, calculator.memoryCacheSize.toLong())
        builder.setMemoryCache(memoryCache)

        val diskCacheSize = 1024 * 1024 * 2000L
        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))
    }
}

 

 



import android.content.Context
import android.util.Log
import com.bumptech.glide.load.Key
import com.bumptech.glide.load.engine.Resource
import com.bumptech.glide.load.engine.cache.LruResourceCache

class MyLruResourceCache {
    companion object {
        private val inst by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { MyLruResourceCache() }
        fun Instance() = inst
    }

    private var ctx: Context? = null
    private var cache: InnerLruResourceCache? = null

    private constructor() {

    }

    fun init(c: Context, size: Long): InnerLruResourceCache? {
        if (cache == null) {
            ctx = c
            cache = InnerLruResourceCache(size)
        }

        return cache
    }

    class InnerLruResourceCache : LruResourceCache {
        constructor(size: Long) : super(size) {

        }

        override fun put(key: Key, item: Resource<*>?): Resource<*>? {
            Log.d(MainActivity.TAG, "put key=$key")
            return super.put(key, item)
        }

        override fun remove(key: Key): Resource<*>? {
            Log.d(MainActivity.TAG, "remove key=$key")
            return super.remove(key)
        }
    }
}

 

 



import android.net.Uri
import com.bumptech.glide.signature.MediaStoreSignature

class MyThumb {
    var mUri: Uri? = null
    var mWith: Int = 0
    var mHeight: Int = 0
    var mSignature: MediaStoreSignature? = null
    private var mHashCode = 0

    constructor(u: Uri, w: Int, h: Int, signature: MediaStoreSignature) {
        this.mUri = u
        this.mWith = w
        this.mHeight = h
        this.mSignature = signature
    }

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

        return false
    }

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

        return mHashCode
    }

    override fun toString(): String {
        return "MyThumb(mUri=$mUri, mWith=$mWith, mHeight=$mHeight, mSignature=$mSignature)"
    }
}

 

 

import android.content.Context
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Matrix
import android.graphics.RectF
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
import java.lang.Exception
import kotlin.math.min

class MyThumbDataFetcher : DataFetcher<Bitmap> {
    private var mModel: MyThumb? = null
    private var mContext: Context? = null
    private var mSignal: CancellationSignal? = null

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

    override fun loadData(priority: Priority, callback: DataFetcher.DataCallback<in Bitmap>) {
        val t = System.currentTimeMillis()
        val bmp = mContext?.contentResolver?.loadThumbnail(mModel?.mUri!!, Size(MainActivity.SIZE, MainActivity.SIZE), mSignal)
        if (bmp != null) {
            Log.d(MainActivity.TAG, "loadData 耗时:${System.currentTimeMillis() - t} uri=${mModel?.mUri}")
            callback.onDataReady(scale(bmp))
        } else {
            callback.onLoadFailed(Exception("loadThumbnail Error"))
        }
    }

    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 uri=${mModel?.mUri}")
        return DataSource.LOCAL
    }

    //Bitmap scale to centerCrop()
    private fun scale(srcBmp: Bitmap): Bitmap {
        val t = System.currentTimeMillis()

        val bmp = Bitmap.createBitmap(MainActivity.SIZE, MainActivity.SIZE, Bitmap.Config.ARGB_8888)
        val c = Canvas(bmp)

        val width: Int = srcBmp.width
        val height: Int = srcBmp.height

        val bmpCenterX: Float = width / 2f
        val bmpCenterY: Float = height / 2f

        val minVal = min(width, height)

        val srcRectF = RectF(
            bmpCenterX - minVal / 2,
            bmpCenterY - minVal / 2,
            bmpCenterX + minVal / 2,
            bmpCenterY + minVal / 2
        )

        val dstRectF = RectF(0f, 0f, MainActivity.SIZE.toFloat(), MainActivity.SIZE.toFloat())
        val matrix = Matrix()
        matrix.setRectToRect(srcRectF, dstRectF, Matrix.ScaleToFit.CENTER)
        c.drawBitmap(srcBmp, matrix, null)
        Log.d(MainActivity.TAG, "scale 耗时=${System.currentTimeMillis() - t}")

        return bmp
    }
}

 

 

 



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?.mUri == other.mModel?.mUri) && (mModel?.mWith == other.mModel?.mWith) && (mModel?.mHeight == other.mModel?.mHeight) && (mModel?.mSignature == other.mModel?.mSignature)
        }
        return false
    }

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

        return mHashCode
    }
}

 

 



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
    }
}

 

 



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

Android矩阵Matrix setRectToRect实现标准scaleType中心缩放centerCrop,Kotlin-CSDN博客文章浏览阅读676次,点赞21次,收藏18次。Android拼接合并图片生成长图代码实现合并两张图片,以第一张图片的宽度为标准,如果被合并的第二张图片宽度和第一张不同,那么就以第一张图片的宽度为准线,对第二张图片进行缩放。Android拼接合并图片生成长图代码实现合并两张图片,以第一张图片的宽度为标准,如果被合并的第二张图片宽度和第一张不同,那么就以第一张图片的宽度为准线,对第二张图片进行缩放。基础上,把剪切的区域从矩形Rect变为圆形的Path,当手指在上面的ImageView移动时候,下面同等大小对应的坐标区域显示“剪切”出来的圆形图。https://blog.csdn.net/zhangphil/article/details/139753381

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zhangphil

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值