Android RecyclerView性能优化及Glide流畅加载图片丢帧率低的一种8宫格实现,Kotlin

发布于:2024-04-19 ⋅ 阅读:(29) ⋅ 点赞:(0)

Android RecyclerView性能优化及Glide流畅加载图片丢帧率低的一种8宫格实现,Kotlin

 

 

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

 

plugins {
    id 'org.jetbrains.kotlin.kapt'
}


    implementation 'com.github.bumptech.glide:glide:4.16.0'
    kapt 'com.github.bumptech.glide:compiler:4.16.0'

 

 

import android.content.Context
import android.util.Log
import com.bumptech.glide.GlideBuilder
import com.bumptech.glide.annotation.GlideModule
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


@GlideModule
class MyGlideModule : AppGlideModule() {

    override fun applyOptions(context: Context, builder: GlideBuilder) {
        super.applyOptions(context, builder)
        builder.setLogLevel(Log.DEBUG)

        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).toInt())
            .build()

        builder.setMemorySizeCalculator(calculator)

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


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

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

        val mAnimationExecutor = GlideExecutor.newDiskCacheBuilder()
            .setThreadCount(1)
            //.setThreadTimeoutMillis(1000) //线程读写超时时间。
            .setName("fly-AnimationExecutor")
            .build()

        builder.setSourceExecutor(mSourceExecutor)
        builder.setDiskCacheExecutor(mDiskCacheBuilder)
        builder.setAnimationExecutor(mAnimationExecutor)
    }

    override fun isManifestParsingEnabled(): Boolean {
        return false
    }
}

 

 

import android.content.Context
import android.os.Bundle
import android.provider.MediaStore
import android.util.Log
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.AppCompatImageView
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext


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

        const val VIEW_TYPE = 0

        const val PRELOAD_HEIGHT_COUNT = 2
        const val IMAGE_SIZE = 200
        const val ITEM_VIEW_CACHE_SIZE = 30
        const val SPAN_COUNT = 8
    }

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

        val rv = findViewById<RecyclerView>(R.id.rv)
        val layoutManager = MyLayoutManager(this, SPAN_COUNT)
        layoutManager.orientation = LinearLayoutManager.VERTICAL
        rv.layoutManager = layoutManager

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

        rv.setHasFixedSize(true)
        rv.setItemViewCacheSize(ITEM_VIEW_CACHE_SIZE)

        lifecycleScope.launch(Dispatchers.IO) {
            val items = readAllImage(this@MainActivity)
            withContext(Dispatchers.Main) {
                adapter.dataChanged(items)
            }
        }

        val ctx = this
        rv.setRecyclerListener(object : RecyclerView.RecyclerListener {
            override fun onViewRecycled(holder: RecyclerView.ViewHolder) {
                val h: MyVH? = holder as? MyVH
                if (h != null) {
                    GlideApp.with(ctx).clear(h.image!!)
                }

                Log.d(TAG, "${h?.adapterPosition} onViewRecycled")
            }
        })
    }

    class MyAdapter : RecyclerView.Adapter<MyVH> {
        private var items = arrayListOf<MyData>()
        private var mContext: Context? = null

        constructor(ctx: Context) {
            this.mContext = ctx
        }

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

        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyVH {
            val view = MyImageView(mContext!!)
            return MyVH(view)
        }

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

        override fun getItemViewType(position: Int): Int {
            return VIEW_TYPE
        }

        override fun onBindViewHolder(holder: MyVH, position: Int) {
            Log.d(TAG, "onBindViewHolder $position")

            val uri = items[holder.adapterPosition].path

            GlideApp.with(mContext!!)
                .asBitmap()
                .load(uri)
                .centerCrop()
                .override(IMAGE_SIZE, IMAGE_SIZE)
                .into(holder.image!!)
        }
    }

    class MyVH : RecyclerView.ViewHolder {
        var image: MyImageView? = null

        constructor(itemView: View) : super(itemView) {
            //image.layoutParams.height= IMAGE_SIZE
            //image.layoutParams.width= IMAGE_SIZE
            image = itemView as MyImageView
        }
    }

    class MyImageView : AppCompatImageView {
        constructor(ctx: Context) : super(ctx) {

        }
    }

    class MyLayoutManager : GridLayoutManager {
        constructor(ctx: Context, spanCount: Int) : super(ctx, spanCount) {

        }

        override fun getExtraLayoutSpace(state: RecyclerView.State?): Int {
            return PRELOAD_HEIGHT_COUNT * IMAGE_SIZE
        }
    }


    class MyData(var path: String, var index: Int)

    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
        while (cursor!!.moveToNext()) {
            //路径 uri
            val path = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA))

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

            photos.add(MyData(path, index++))
        }
        cursor.close()

        return photos
    }
}

避免使用xml定义ImageView装配到ViewHolder,这种情况在8宫格和更大宫格情况下,IO耗时,卡顿现象明显。

 

 

上面这种方式实现较为简洁、易懂,如果还要持续优化,想要更快,还有更复杂的方式:Android Glide自定义AppCompatImageView切分成若干小格子,每个小格子onDraw绘制Bitmap,Kotlin(1)_android appcompatimageview-CSDN博客 

 

 

 

 

Android性能优化RecyclerView预加载LayoutManager的getExtraLayoutSpace,Kotlin-CSDN博客文章浏览阅读104次。文章浏览阅读501次。文章浏览阅读428次。上面要预加载10条,每条item高度是100pix,也就是说,正确的情况下,如果RecyclerView不作任何调优,那它只加载当前屏幕可见区域position为0~21的item(每个item高度为100pix),如果配置了getExtraLayoutSpace,那么会多(Extra)加载10条position为22~31的item,其中22~31为屏幕底部不可见的区域中内容,被“预加载”出来。文章浏览阅读410次。https://zhangphil.blog.csdn.net/article/details/137589193

Android Glide load grid RecyclerView scroll smooth, high performance and ,Kotlin-CSDN博客文章浏览阅读393次,点赞14次,收藏8次。【代码】Android Paging 3,kotlin(1)在实际的开发中,虽然Glide解决了快速加载图片的问题,但还有一个问题悬而未决:比如用户的头像,往往用户的头像是从服务器端读出的一个普通矩形图片,但是现在的设计一般要求在APP端的用户头像显示成圆形头像,那么此时虽然Glide可以加载,但加载出来的是一个矩形,如果要Glide_android 毛玻璃圆角。现在结合他人的代码加以修改,给出一个以原始图形中心为原点,修剪图片为头像的工具类,此类可以直接在布局文件中加载使用,比。文章浏览阅读670次。https://blog.csdn.net/zhangphil/article/details/137520793

Android Glide配置AppGlideModule定制化线程池,Kotlin(1)-CSDN博客文章浏览阅读1k次,点赞14次,收藏20次。在实际的开发中,虽然Glide解决了快速加载图片的问题,但还有一个问题悬而未决:比如用户的头像,往往用户的头像是从服务器端读出的一个普通矩形图片,但是现在的设计一般要求在APP端的用户头像显示成圆形头像,那么此时虽然Glide可以加载,但加载出来的是一个矩形,如果要Glide_android 毛玻璃圆角。文章浏览阅读670次。假设实现一个简单的功能,对传入要加载的path路径增加一定的筛选、容错或“重定向”,需要自定义一个模型,基于这个模型,让Glide自动匹配模型展开加载。文章浏览阅读670次。https://blog.csdn.net/zhangphil/article/details/137356178