Diagnosing and Fixing Slow Rendering (Jank) in Android Apps
Denis Koome
Mobile Developer | Writer | Web 3 | AI & Tech Enthusiast | Kotlin | Flutter | React
Slow rendering, also known as jank, occurs when the app's UI frames take too long to render, causing stuttering and a poor user experience. To ensure smooth interaction, an app must render frames within 16ms to achieve 60 frames per second (fps), with higher frame rates like 90 fps or 120 fps demanding even tighter windows of 11ms and 8ms. If these timings are not met, the Android system's Choreographer will drop the frame, causing visible jank.
Identifying Jank
val frameMetrics = FrameMetricsAggregator()
frameMetrics.add(frameMetricsCallback)
Understanding Slow Frames, Frozen Frames, and ANRs
Understanding these different types of performance issues is crucial. While slow frames affect smooth scrolling or animations, frozen frames and ANRs (Application Not Responding) occur when rendering takes excessively long or when the app fails to respond to system events.
Best Practices for Resolving Jank
Common Jank Sources and Solutions
A. Scrollable Lists (RecyclerView Issues)
// Bad practice
fun onNewDataArrived(news: List<News>) {
myAdapter.news = news
myAdapter.notifyDataSetChanged()
}
// Better practice using DiffUtil
fun onNewDataArrived(news: List<News>) {
val oldNews = myAdapter.items
val result = DiffUtil.calculateDiff(MyCallback(oldNews, news))
myAdapter.news = news
result.dispatchUpdatesTo(myAdapter)
}
领英推荐
class OuterAdapter : RecyclerView.Adapter<OuterAdapter.ViewHolder>() {
private val sharedPool = RecyclerView.RecycledViewPool()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val innerRv = LayoutInflater.from(parent.context).inflate(R.layout.inner_recycler_view, parent, false)
val innerLLM = LinearLayoutManager(parent.context, LinearLayoutManager.HORIZONTAL, false)
innerRv.apply {
layoutManager = innerLLM
recycledViewPool = sharedPool
}
return ViewHolder(innerRv)
}
}
fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
return (convertView ?: layoutInflater.inflate(R.layout.my_layout, parent, false)).apply {
// Bind content from position to convertView.
}
}
B. Drawable or View Customizations
C. Rendering Performance
// Avoid this:
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bitmap)
canvas.drawRoundRect(...)
// Instead, use:
fun setBitmap(bitmap: Bitmap) {
shaderPaint.shader = BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
invalidate()
}
D. Thread Scheduling Delays
E. Object Allocation and Garbage Collection
fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
return (convertView ?: layoutInflater.inflate(R.layout.my_layout, parent, false)).apply {
// Bind content from position to convertView.
}
}
Concluscion
To ensure a smooth user experience, diagnose and fix jank by identifying slow frames, using tools like Systrace and Profile GPU Rendering, and adopting best practices for rendering optimization. By addressing common sources of jank and running long tasks asynchronously, you can significantly improve your app's performance.