Android RecyclerView性能优化及Glide流畅加载图片丢帧率低的一种8宫格实现,Kotlin
XML
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
XML
plugins {
id 'org.jetbrains.kotlin.kapt'
}
implementation 'com.github.bumptech.glide:glide:4.16.0'
kapt 'com.github.bumptech.glide:compiler:4.16.0'
Kotlin
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
}
}
Kotlin
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博客