Rendering UI adalah tindakan menghasilkan frame dari aplikasi dan menampilkannya di layar. Untuk membantu memastikan bahwa interaksi pengguna dengan aplikasi berjalan lancar, aplikasi harus merender frame dalam waktu di bawah 16 md untuk mencapai 60 frame per detik (fps). Untuk memahami alasan pemilihan 60 fps, lihat Pola Performa Android: Mengapa 60 fps?. Jika Anda mencoba mencapai 90 fps, periode ini akan turun menjadi 11 md, dan untuk 120 fps, periodenya akan turun menjadi 8 md.
Jika Anda mengganti periode ini selama 1 md, bukan berarti frame ditampilkan
terlambat 1 md, tetapi Choreographer
akan menghapus frame sepenuhnya. Jika aplikasi mengalami rendering UI yang lambat, sistem akan dipaksa untuk melewati frame dan pengguna akan mengalami ketersendatan di aplikasi.
Hal ini disebut jank. Halaman ini menunjukkan cara mendiagnosis dan memperbaiki jank.
Jika Anda mengembangkan game yang tidak menggunakan
sistem View
, Anda akan mengabaikan
Choreographer
. Dalam hal ini, Library
Frame Pacing akan membantu
game OpenGL dan
Vulkan mencapai rendering yang lancar dan
memperbaiki kecepatan frame di Android.
Untuk membantu meningkatkan kualitas aplikasi, Android otomatis memantau aplikasi untuk menemukan jank dan menampilkan informasi di dasbor Android vitals. Untuk mengetahui informasi tentang cara mengumpulkan data, lihat Memantau kualitas teknis aplikasi dengan Android vitals.
Mengidentifikasi jank
Menemukan kode di aplikasi yang menyebabkan jank bisa jadi sulit. Bagian ini menjelaskan tiga metode untuk mengidentifikasi jank:
Pemeriksaan visual memungkinkan Anda memeriksa semua kasus penggunaan dengan cepat di aplikasi dalam beberapa menit, tetapi pemeriksaan ini tidak memberikan detail sebanyak Systrace. Systrace menyediakan lebih banyak detail, tetapi jika Anda menjalankan Systrace untuk semua kasus penggunaan di aplikasi, Anda bisa dibanjiri begitu banyak data yang sulit dianalisis. Pemeriksaan visual dan Systrace mendeteksi jank di perangkat lokal. Jika tidak dapat mereproduksi jank di perangkat lokal, Anda dapat mem-build pemantauan performa kustom untuk mengukur bagian tertentu aplikasi di perangkat yang berjalan di kolom.
Pemeriksaan visual
Pemeriksaan visual membantu Anda mengidentifikasi kasus penggunaan yang menghasilkan jank. Untuk melakukan pemeriksaan visual, buka aplikasi dan periksa secara manual bagian lain dari aplikasi, lalu cari jank di UI.
Berikut beberapa tips saat melakukan pemeriksaan visual:
- Jalankan versi rilis—atau setidaknya versi yang tidak dapat di-debug—dari aplikasi Anda. Waktu proses ART menonaktifkan beberapa pengoptimalan penting untuk mendukung fitur proses debug. Jadi, pastikan apa yang Anda lihat mirip dengan apa yang akan dilihat oleh pengguna.
- Aktifkan Rendering GPU Profil. Rendering GPU Profil menampilkan grafik batang di layar yang memberi Anda representasi visual cepat terkait durasi yang diperlukan untuk merender frame jendela UI yang terkait dengan tolok ukur 16 md per frame. Setiap batang memiliki komponen berwarna yang dipetakan ke suatu tahap dalam pipeline rendering sehingga Anda dapat melihat porsi mana yang memerlukan waktu terlama. Misalnya, jika frame menghabiskan banyak waktu dalam menangani input, lihat kode aplikasi yang menangani input pengguna.
- Jalankan komponen yang merupakan sumber umum jank, seperti
RecyclerView
. - Luncurkan aplikasi dari cold start.
- Jalankan aplikasi di perangkat yang lebih lambat untuk memperburuk masalah.
Saat menemukan kasus penggunaan yang menyebabkan jank, Anda mungkin telah mengetahui penyebab jank di aplikasi. Jika memerlukan informasi selengkapnya, Anda dapat menggunakan Systrace untuk mencari tahu penyebabnya lebih lanjut.
Systrace
Meskipun Systrace adalah alat yang menunjukkan performa seluruh aplikasi, alat ini dapat bermanfaat untuk mengidentifikasi jank di aplikasi. Systrace memiliki overhead sistem minimal sehingga Anda dapat mengalami jank yang realistis selama instrumentasi.
Rekam aktivitas menggunakan Systrace saat melakukan kasus penggunaan jank di perangkat. Untuk mengetahui petunjuk cara menggunakan Systrace, lihat Merekam pelacakan sistem di command line. Systrace dibagi menurut proses dan thread. Cari proses aplikasi di Systrace yang terlihat seperti gambar 1.
Contoh Systrace di gambar 1 berisi informasi berikut untuk mengidentifikasi jank:
- Systrace menunjukkan waktu penggambaran setiap frame dan memberi kode warna setiap frame untuk menandai waktu render yang lambat. Ini akan membantu Anda menemukan frame jank satu per satu dengan lebih akurat daripada saat melakukan pemeriksaan visual. Untuk mengetahui informasi selengkapnya, lihat Memeriksa peringatan dan frame UI.
- Systrace mendeteksi masalah di aplikasi dan menampilkan peringatan di setiap frame dan panel peringatan. Sebaiknya ikuti petunjuk di peringatan.
- Bagian-bagian dari framework dan library Android, seperti
RecyclerView
, berisi penanda rekaman aktivitas. Jadi, linimasa systrace menunjukkan kapan metode tersebut dieksekusi di UI thread dan durasi waktu yang diperlukan untuk mengeksekusinya.
Setelah melihat output Systrace, mungkin ada metode di aplikasi yang
Anda duga menyebabkan jank. Misalnya, jika linimasa menunjukkan bahwa
frame lambat disebabkan oleh RecyclerView
yang memerlukan waktu lama, Anda dapat menambahkan peristiwa
rekaman aktivitas kustom ke kode yang relevan dan
menjalankan ulang Systrace untuk mendapatkan informasi selengkapnya. Di Systrace baru, linimasa menunjukkan waktu
pemanggilan metode aplikasi dan durasi waktu yang diperlukan untuk mengeksekusinya.
Jika Systrace tidak menampilkan detail tentang alasan pekerjaan UI thread memerlukan waktu lama, gunakan CPU Profiler Android untuk mencatat pelacakan metode yang dijadikan sampel atau yang diinstrumentasikan. Umumnya, pelacakan metode tidak tepat untuk mengidentifikasi jank karena menghasilkan jank positif palsu akibat overhead yang berat, dan tidak dapat melihat kapan thread berjalan atau diblokir. Namun, pelacakan metode dapat membantu Anda mengidentifikasi metode di aplikasi yang memerlukan waktu paling banyak. Setelah mengidentifikasi metode ini, tambahkan penanda Rekaman Aktivitas lalu jalankan kembali Systrace untuk melihat apakah metode ini menyebabkan jank atau tidak.
Untuk mengetahui informasi selengkapnya, lihat Memahami Systrace.
Pemantauan performa kustom
Jika tidak dapat mereproduksi jank di perangkat lokal, Anda dapat mem-build pemantauan performa kustom di aplikasi untuk membantu mengidentifikasi sumber jank di perangkat yang ada di kolom.
Untuk melakukannya, kumpulkan waktu render frame dari bagian-bagian tertentu aplikasi dengan
FrameMetricsAggregator
lalu catat dan analisis data menggunakan Firebase Performance
Monitoring.
Untuk mempelajari lebih lanjut, lihat Memulai Performance Monitoring untuk Android.
Periode frozen
Periode frozen adalah frame UI yang memerlukan waktu lebih dari 700 md untuk dirender. Ini menjadi masalah karena aplikasi tampak macet dan tidak responsif terhadap input pengguna selama hampir satu detik saat frame sedang dirender. Sebaiknya optimalkan aplikasi untuk merender frame dalam waktu 16 md untuk memastikan UI yang lancar. Namun, selama aplikasi dimulai atau saat bertransisi ke layar yang berbeda, normal saja jika memerlukan waktu lebih dari 16 md untuk menggambar frame awal karena aplikasi harus meng-inflate tampilan, membuat tata letak layar, dan melakukan penggambaran awal dari nol. Itu sebabnya Android melacak periode frozen secara terpisah dari rendering lambat. Tidak boleh ada frame di aplikasi yang memerlukan waktu lebih dari 700 md untuk dirender.
Untuk membantu meningkatkan kualitas aplikasi, Android otomatis memantau aplikasi untuk menemukan periode frozen dan menampilkan informasi di dasbor Android Vitals. Untuk mengetahui informasi tentang cara mengumpulkan data, lihat Memantau kualitas teknis aplikasi dengan Android vitals.
Periode frozen adalah bentuk ekstrem dari rendering lambat, sehingga prosedur untuk mendiagnosis dan memperbaiki masalahnya sama.
Melacak jank
FrameTimeline di Perfetto dapat membantu melacak situs yang lambat atau periode frozen.
Hubungan antara periode lambat, periode frozen, dan ANR
Periode lambat, periode frozen, dan ANR adalah berbagai bentuk jank yang mungkin ditemui aplikasi Anda. Lihat tabel di bawah untuk memahami perbedaannya.
Periode lambat | Periode frozen | ANR | |
---|---|---|---|
Waktu rendering | Antara 16 md dan 700 md | Antara 700 md dan 5 d | Lebih dari 5 d |
Area dampak yang terlihat oleh pengguna |
|
|
|
Melacak periode lambat dan periode frozen secara terpisah
Selama aplikasi dimulai atau saat bertransisi ke layar yang berbeda, normal saja jika memerlukan waktu lebih dari 16 md untuk menggambar frame awal karena aplikasi harus meng-inflate tampilan, membuat tata letak layar, dan melakukan penggambaran awal dari nol.
Praktik terbaik untuk memprioritaskan dan me-resolve jank
Ingatlah praktik terbaik berikut saat mencoba me-resolve jank di aplikasi Anda:
- Identifikasi dan selesaikan instance jank yang paling mudah direproduksi.
- Prioritaskan ANR. Meskipun periode lambat atau periode frozen dapat membuat aplikasi tampak lambat, ANR menyebabkan aplikasi berhenti merespons.
- Rendering lambat sulit direproduksi, tetapi Anda dapat memulai dengan mengakhiri periode frozen 700 md. Hal ini paling umum terjadi saat aplikasi memulai atau mengubah layar.
Memperbaiki jank
Untuk memperbaiki jank, periksa frame mana yang tidak selesai dalam waktu 16 md, lalu cari bagian
yang salah. Periksa apakah Record View#draw
atau Layout
memerlukan waktu yang sangat lama di
beberapa frame. Lihat Sumber umum jank untuk masalah ini dan
juga masalah lainnya.
Untuk menghindari jank, jalankan tugas yang berjalan lama secara asinkron di luar UI thread. Selalu waspadai thread tempat kode dijalankan dan berhati-hatilah saat memposting tugas yang sulit ke thread utama.
Jika Anda memiliki UI primer yang kompleks dan penting untuk aplikasi—seperti daftar scroll pusat—sebaiknya tulis uji instrumentasi yang dapat secara otomatis mendeteksi waktu render lambat dan sering menjalankan pengujian untuk mencegah regresi.
Sumber umum jank
Bagian berikut menjelaskan sumber umum jank di aplikasi menggunakan
sistem View
dan praktik terbaik untuk mengatasinya. Untuk mengetahui informasi tentang cara memperbaiki
masalah performa pada Jetpack Compose, lihat Performa
Jetpack Compose.
Daftar yang dapat di-scroll
ListView
—dan terutama
RecyclerView
—biasanya digunakan untuk daftar scroll kompleks yang sangat
rentan terhadap jank. Keduanya berisi penanda Systrace sehingga Anda dapat menggunakan Systrace
untuk melihat apakah keduanya penyebab jank di aplikasi atau bukan. Teruskan argumen command line
-a
<your-package-name>
agar bagian rekaman aktivitas di RecyclerView
—serta
penanda rekaman aktivitas yang Anda tambahkan—dapat muncul. Jika tersedia, ikuti pedoman
notifikasi yang dibuat di output Systrace. Di dalam Systrace, Anda dapat mengklik
bagian yang direkam RecyclerView
untuk melihat penjelasan mengenai pekerjaan yang dilakukan
RecyclerView
.
RecyclerView: notifyDataSetChanged()
Jika Anda melihat setiap item di RecyclerView
di-binding ulang—dan, oleh karena itu, diletakkan ulang
dan digambar ulang dalam satu frame—pastikan bahwa
Anda tidak sedang memanggil
notifyDataSetChanged()
,
setAdapter(Adapter)
,
atau swapAdapter(Adapter,
boolean)
untuk update kecil. Metode ini menandakan bahwa ada perubahan pada
seluruh konten daftar dan muncul di Systrace sebagai RV FullInvalidate. Sebagai gantinya, gunakan
SortedList
atau
DiffUtil
untuk menghasilkan
update minimal saat konten diubah atau ditambahkan.
Misalnya, pertimbangkan aplikasi yang menerima versi baru daftar konten
berita dari server. Jika memposting informasi ini ke Adaptor, Anda
dapat memanggil notifyDataSetChanged()
, seperti yang ditunjukkan dalam contoh berikut:
Kotlin
fun onNewDataArrived(news: List<News>) { myAdapter.news = news myAdapter.notifyDataSetChanged() }
Java
void onNewDataArrived(List<News> news) { myAdapter.setNews(news); myAdapter.notifyDataSetChanged(); }
Kelemahannya adalah jika ada perubahan kecil, seperti satu item
yang ditambahkan ke atas, RecyclerView
tidak akan mengetahuinya. Oleh karena itu, Anda diberi tahu untuk melepaskan
seluruh status item yang disimpan dalam cache dan harus di-bind ulang semuanya.
Sebaiknya gunakan DiffUtil
yang menghitung dan mengirimkan update minimal
untuk Anda:
Kotlin
fun onNewDataArrived(news: List<News>) { val oldNews = myAdapter.items val result = DiffUtil.calculateDiff(MyCallback(oldNews, news)) myAdapter.news = news result.dispatchUpdatesTo(myAdapter) }
Java
void onNewDataArrived(List<News> news) { List<News> oldNews = myAdapter.getItems(); DiffResult result = DiffUtil.calculateDiff(new MyCallback(oldNews, news)); myAdapter.setNews(news); result.dispatchUpdatesTo(myAdapter); }
Untuk memberi tahu cara memeriksa daftar Anda ke DiffUtil
, tentukan MyCallback
sebagai
implementasi
Callback
.
RecyclerView: Nested RecyclerViews
Bukan hal yang aneh untuk membuat beberapa instance RecyclerView
, terutama dengan
daftar vertikal dari daftar scroll horizontal. Contohnya adalah petak
aplikasi di halaman utama Play Store. Ini dapat berfungsi dengan baik, tetapi juga melibatkan banyak
tampilan yang bergerak.
Jika Anda melihat banyak item bagian dalam meng-inflate saat pertama kali men-scroll halaman ke bawah,
sebaiknya periksa apakah Anda membagikan
RecyclerView.RecycledViewPool
di antara instance RecyclerView
bagian dalam (horizontal). Secara default, setiap
RecyclerView
memiliki kumpulan itemnya sendiri. Namun, pada kasus dengan belasan
itemViews
di layar sekaligus, akan menjadi masalah jika itemViews
tidak dapat
dibagikan oleh daftar horizontal yang berbeda jika semua baris menunjukkan
jenis tampilan yang serupa.
Kotlin
class OuterAdapter : RecyclerView.Adapter<OuterAdapter.ViewHolder>() { ... override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { // Inflate inner item, find innerRecyclerView by ID. val innerLLM = LinearLayoutManager(parent.context, LinearLayoutManager.HORIZONTAL, false) innerRv.apply { layoutManager = innerLLM recycledViewPool = sharedPool } return OuterAdapter.ViewHolder(innerRv) } ...
Java
class OuterAdapter extends RecyclerView.Adapter<OuterAdapter.ViewHolder> { RecyclerView.RecycledViewPool sharedPool = new RecyclerView.RecycledViewPool(); ... @Override public void onCreateViewHolder(ViewGroup parent, int viewType) { // Inflate inner item, find innerRecyclerView by ID. LinearLayoutManager innerLLM = new LinearLayoutManager(parent.getContext(), LinearLayoutManager.HORIZONTAL); innerRv.setLayoutManager(innerLLM); innerRv.setRecycledViewPool(sharedPool); return new OuterAdapter.ViewHolder(innerRv); } ...
Jika ingin mengoptimalkan lebih jauh, Anda juga dapat memanggil
setInitialPrefetchItemCount(int)
di
LinearLayoutManager
dari RecyclerView
bagian dalam. Misalnya, jika Anda selalu memiliki 3,5 item yang terlihat
dalam satu baris, panggil innerLLM.setInitialItemPrefetchCount(4)
. Tindakan ini akan memberi tahu
RecyclerView
bahwa saat baris horizontal akan muncul di layar, baris tersebut harus
mencoba mengambil data item di dalam terlebih dahulu jika ada waktu luang di UI thread.
RecyclerView: Terlalu banyak inflation atau Create memerlukan waktu terlalu lama
Pada umumnya, fitur pengambilan data di RecyclerView
dapat membantu mengatasi
biaya inflation dengan melakukan pekerjaan terlebih dahulu saat tidak ada aktivitas di UI thread.
Jika Anda melihat inflation selama frame dan bukan di bagian yang diberi label RV
Prefetch, pastikan Anda melakukan pengujian pada perangkat yang didukung dan menggunakan
Support Library versi terbaru.
RV Prefetch hanya didukung di Android 5.0 Level API 21 dan yang lebih tinggi.
Jika Anda sering melihat inflation yang menyebabkan jank saat item baru muncul di layar, verifikasi
bahwa jenis tampilan yang Anda miliki tidak melebihi jumlah yang dibutuhkan. Makin sedikit jenis tampilan dalam
konten RecyclerView
, makin sedikit inflation yang perlu dilakukan saat jenis
item baru muncul di layar. Jika memungkinkan dan wajar, gabungkan jenis tampilan. Jika
hanya ikon, warna, atau potongan teks yang berubah antarjenis, Anda dapat membuat
perubahan tersebut pada waktu binding dan menghindari inflation sehingga akan mengurangi jejak memori
aplikasi secara bersamaan.
Jika jenis tampilan terlihat bagus, usahakan untuk mengurangi biaya inflation.
Mengurangi container dan tampilan struktural yang tidak diperlukan dapat membantu. Sebaiknya build
itemViews
dengan ConstraintLayout
sehingga dapat membantu mengurangi tampilan struktural.
Jika ingin mengoptimalkan performa lebih jauh, hierarki item Anda sederhana, dan Anda tidak memerlukan fitur tema serta gaya yang kompleks, sebaiknya panggil konstruktor sendiri. Namun, sering kali tidak ada manfaatnya jika kesederhanaan dan fitur XML hilang.
RecyclerView: Binding memerlukan waktu terlalu lama
Binding—yaitu, onBindViewHolder(VH,
int)
— harus mudah dan menghabiskan waktu kurang dari satu milidetik (md) untuk
semuanya kecuali item yang paling kompleks. Binding harus mengambil item objek Java lama (POJO)
biasa dari data item internal adaptor dan memanggil penyetel pada tampilan di
ViewHolder
. Jika RV
OnBindView memerlukan waktu yang terlalu lama, pastikan Anda melakukan pekerjaan minimal
di kode binding.
Jika menggunakan objek POJO sederhana untuk menyimpan data di adaptor, Anda dapat
benar-benar menghindari penulisan kode binding di onBindViewHolder
menggunakan
Library Data Binding
RecyclerView atau ListView: Tata letak atau menggambar memerlukan waktu terlalu lama
Untuk masalah pada menggambar dan tata letak, lihat bagian Performa tata letak dan Performa rendering.
ListView: Inflation
Anda dapat menonaktifkan daur ulang di ListView
secara tidak sengaja jika tidak berhati-hati. Jika
Anda melihat inflation setiap kali item muncul di layar, pastikan bahwa implementasi
Adapter.getView()
menggunakan, melakukan binding ulang, dan menampilkan parameter convertView
. Jika implementasi
getView()
Anda selalu meng-inflate, aplikasi tidak akan mendapatkan manfaat dari
daur ulang di ListView
. Struktur getView()
Anda harus hampir selalu
mirip dengan implementasi berikut:
Kotlin
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. } }
Java
View getView(int position, View convertView, ViewGroup parent) { if (convertView == null) { // Only inflate if no convertView passed. convertView = layoutInflater.inflate(R.layout.my_layout, parent, false) } // Bind content from position to convertView. return convertView; }
Performa tata letak
Jika Systrace menunjukkan bahwa segmen Tata Letak Choreographer#doFrame
bekerja terlalu banyak atau terlalu sering, ini artinya Anda mengalami
masalah performa tata letak. Performa tata letak aplikasi bergantung pada bagian
hierarki tampilan yang memiliki parameter atau input tata letak yang berubah.
Performa tata letak: Biaya
Jika segmen lebih lama beberapa milidetik, ada kemungkinan bahwa Anda
mengalami skenario performa penyusunan bertingkat terburuk untuk
RelativeLayouts
, atau
weighted-LinearLayouts
. Setiap
tata letak ini dapat memicu beberapa penerusan ukuran dan tata letak turunannya. Jadi,
membuatnya bertingkat dapat menyebabkan perilaku O(n^2)
pada kedalaman tingkatan.
Coba hindari RelativeLayout
atau fitur berat LinearLayout
di semua node daun hierarki, kecuali
yang terendah. Berikut adalah cara untuk melakukannya:
- Mengatur ulang tampilan struktural.
- Menentukan logika tata letak kustom. Lihat
Mengoptimalkan hierarki tata letak
untuk mengetahui contoh yang spesifik. Anda dapat mencoba mengonversi ke
ConstraintLayout
yang menyediakan fitur serupa, tetapi tanpa kelemahan performa.
Performa tata letak: Frekuensi
Tata letak diharapkan terjadi saat konten baru muncul di layar, misalnya saat
item baru di-scroll ke tampilan di RecyclerView
. Jika tata letak yang signifikan
terjadi di setiap frame, ada kemungkinan Anda menganimasi tata letak sehingga
menyebabkan penurunan frame.
Umumnya, animasi harus berjalan di properti gambar View
, seperti
berikut:
Anda dapat mengubah semua ini menjadi jauh lebih murah daripada properti tata letak, seperti
padding, atau margin. Umumnya, juga jauh lebih murah untuk mengubah properti
menggambar tampilan dengan memanggil penyetel yang memicu
invalidate()
, diikuti dengan
draw(Canvas)
di frame berikutnya. Tindakan ini akan mencatat ulang operasi menggambar untuk tampilan yang
tidak divalidasi, dan juga umumnya jauh lebih murah daripada tata letak.
Performa rendering
UI Android bekerja dalam dua fase:
- Record View#draw di UI thread, yang menjalankan
draw(Canvas)
di setiap tampilan yang tidak divalidasi, dan dapat memanggil panggilan ke dalam tampilan kustom atau ke dalam kode Anda. - DrawFrame pada
RenderThread
, yang berjalan diRenderThread
native, tetapi beroperasi berdasarkan pekerjaan yang dihasilkan oleh fase Record View#draw.
Performa rendering: UI Thread
Jika Record View#draw memerlukan waktu terlalu lama, biasanya bitmap sedang digambar di UI thread. Proses menggambar ke bitmap menggunakan rendering CPU. Jadi. secara umum sebaiknya hindari hal ini jika memungkinkan. Anda dapat menggunakan pelacakan metode dengan Android CPU Profiler untuk mengetahui apakah ini masalahnya atau bukan.
Proses menggambar ke bitmap biasanya dilakukan saat aplikasi ingin mendekorasi bitmap sebelum menampilkannya—terkadang dekorasi bisa seperti menambahkan sudut membulat:
Kotlin
val paint = Paint().apply { isAntiAlias = true } Canvas(roundedOutputBitmap).apply { // Draw a round rect to define the shape: drawRoundRect( 0f, 0f, roundedOutputBitmap.width.toFloat(), roundedOutputBitmap.height.toFloat(), 20f, 20f, paint ) paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.MULTIPLY) // Multiply content on top to make it rounded. drawBitmap(sourceBitmap, 0f, 0f, paint) setBitmap(null) // Now roundedOutputBitmap has sourceBitmap inside, but as a circle. }
Java
Canvas bitmapCanvas = new Canvas(roundedOutputBitmap); Paint paint = new Paint(); paint.setAntiAlias(true); // Draw a round rect to define the shape: bitmapCanvas.drawRoundRect(0, 0, roundedOutputBitmap.getWidth(), roundedOutputBitmap.getHeight(), 20, 20, paint); paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY)); // Multiply content on top to make it rounded. bitmapCanvas.drawBitmap(sourceBitmap, 0, 0, paint); bitmapCanvas.setBitmap(null); // Now roundedOutputBitmap has sourceBitmap inside, but as a circle.
Jika ini adalah jenis pekerjaan yang sedang Anda lakukan di UI thread, Anda dapat melakukannya
di thread decoding di latar belakang. Dalam beberapa kasus, seperti contoh
sebelumnya, Anda bahkan dapat melakukan pekerjaan pada waktu menggambar. Jadi, jika kode
Drawable
atau
View
terlihat seperti ini:
Kotlin
fun setBitmap(bitmap: Bitmap) { mBitmap = bitmap invalidate() } override fun onDraw(canvas: Canvas) { canvas.drawBitmap(mBitmap, null, paint) }
Java
void setBitmap(Bitmap bitmap) { mBitmap = bitmap; invalidate(); } void onDraw(Canvas canvas) { canvas.drawBitmap(mBitmap, null, paint); }
Anda dapat menggantinya dengan ini:
Kotlin
fun setBitmap(bitmap: Bitmap) { shaderPaint.shader = BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP) invalidate() } override fun onDraw(canvas: Canvas) { canvas.drawRoundRect(0f, 0f, width, height, 20f, 20f, shaderPaint) }
Java
void setBitmap(Bitmap bitmap) { shaderPaint.setShader( new BitmapShader(bitmap, TileMode.CLAMP, TileMode.CLAMP)); invalidate(); } void onDraw(Canvas canvas) { canvas.drawRoundRect(0, 0, width, height, 20, 20, shaderPaint); }
Anda juga dapat melakukannya untuk perlindungan latar belakang, seperti saat menggambar gradien
di atas bitmap, dan pemfilteran gambar dengan
ColorMatrixColorFilter
—dua
operasi umum lainnya dilakukan dengan memodifikasi bitmap.
Jika Anda menggambar ke bitmap karena alasan lain—mungkin menggunakannya sebagai
cache—coba gambar ke Canvas
dengan akselerasi hardware yang diteruskan langsung ke View
atau
Drawable
Anda. Jika perlu, sebaiknya pertimbangkan juga untuk memanggil
setLayerType()
dengan
LAYER_TYPE_HARDWARE
untuk menyimpan cache output rendering yang kompleks dan masih memanfaatkan rendering GPU.
Performa rendering: RenderThread
Beberapa operasi Canvas
murah untuk direkam, tetapi memicu komputasi
mahal di RenderThread
. Systrace biasanya memanggilnya dengan notifikasi.
Menganimasi Jalur besar
Saat
Canvas.drawPath()
dipanggil di Canvas
yang diakselerasi oleh hardware dan diteruskan
ke View
, Android akan menggambar jalur ini terlebih dahulu di CPU lalu menguploadnya ke GPU.
Jika Anda memiliki jalur besar, jangan mengeditnya dari frame ke frame agar jalur besar tersebut
dapat disimpan dalam cache dan digambar secara efisien.
drawPoints()
,
drawLines()
, dan drawRect/Circle/Oval/RoundRect()
lebih efisien dan
lebih baik digunakan meskipun Anda menggunakan lebih banyak panggilan gambar.
Canvas.clipPath
clipPath(Path)
memicu perilaku clipping yang mahal, dan secara umum harus dihindari. Jika
memungkinkan, pilih menggambar bentuk, dan bukan clipping ke bentuk selain persegi panjang. Performanya
lebih baik dan mendukung anti-aliasing. Misalnya, panggilan
clipPath
berikut dapat dinyatakan dengan cara yang berbeda:
Kotlin
canvas.apply { save() clipPath(circlePath) drawBitmap(bitmap, 0f, 0f, paint) restore() }
Java
canvas.save(); canvas.clipPath(circlePath); canvas.drawBitmap(bitmap, 0f, 0f, paint); canvas.restore();
Sebagai gantinya, nyatakan contoh sebelumnya sebagai berikut:
Kotlin
paint.shader = BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP) // At draw time: canvas.drawPath(circlePath, mPaint)
Java
// One time init: paint.setShader(new BitmapShader(bitmap, TileMode.CLAMP, TileMode.CLAMP)); // At draw time: canvas.drawPath(circlePath, mPaint);
Upload bitmap
Android menampilkan bitmap sebagai tekstur OpenGL, dan saat pertama kali ditampilkan di frame, bitmap diupload ke GPU. Anda dapat melihatnya di Systrace sebagai lebar x tinggi upload Tekstur(id). Tindakan ini memerlukan waktu beberapa milidetik, seperti yang ditunjukkan pada gambar 2, tetapi perlu menampilkan gambar dengan GPU.
Jika tindakan ini memerlukan waktu lama, sebaiknya terlebih dahulu periksa jumlah lebar dan tinggi di rekaman aktivitas. Pastikan bahwa ukuran bitmap yang sedang ditampilkan tidak terlampau besar daripada area di layar yang menampilkannya. Jika ya, ukuran file yang terlampau besar ini akan menghabiskan waktu upload dan memori. Umumnya, library pemuatan Bitmap menyediakan cara mudah untuk meminta bitmap yang berukuran sesuai.
Di Android 7.0, kode pemuatan bitmap—umumnya dilakukan oleh library—dapat memanggil
prepareToDraw()
untuk
memicu upload lebih awal sebelum diperlukan. Dengan cara ini, upload terjadi lebih awal
saat RenderThread
tidak ada aktivitas. Anda dapat melakukannya setelah melakukan dekode atau saat melakukan binding
bitmap ke tampilan selama Anda mengetahui bitmap tersebut. Idealnya, library pemuatan bitmap
akan melakukannya untuk Anda, tetapi jika Anda mengelola library sendiri, atau ingin memastikan bahwa Anda
tidak mengenai upload di perangkat yang lebih baru, Anda dapat memanggil prepareToDraw()
dalam kode Anda
sendiri.
Keterlambatan penjadwalan thread
Penjadwal thread adalah bagian dari sistem operasi Android yang bertanggung jawab menentukan thread mana di sistem yang harus berjalan, kapan berjalannya, dan durasinya.
Kadang, jank muncul karena UI Thread aplikasi diblokir atau tidak berjalan. Systrace menggunakan berbagai warna, seperti yang ditunjukkan pada gambar 3, untuk menunjukkan kapan thread tidur (abu-abu), dapat dijalankan (biru: dapat berjalan, tetapi tidak dipilih oleh penjadwal untuk dijalankan), aktif berjalan (hijau), atau dalam tidur tanpa gangguan (merah atau oranye). Penggunaan berbagai warna ini sangat berguna untuk mendebug masalah jank yang disebabkan oleh keterlambatan penjadwalan thread.
Sering kali, panggilan binder—mekanisme komunikasi antarproses (IPC) di Android—menyebabkan jeda yang lama dalam eksekusi aplikasi. Di versi terbaru Android, hal tersebut menjadi salah satu alasan paling umum UI thread berhenti berjalan. Umumnya, perbaikannya adalah menghindari pemanggilan fungsi yang melakukan panggilan binder. Jika tidak dapat dihindari, simpan nilai di cache atau pindahkan pekerjaan ke thread latar belakang. Dengan makin besarnya codebase, Anda dapat secara tidak sengaja menambahkan panggilan binder dengan memanggil beberapa metode level rendah jika tidak berhati-hati. Namun, Anda dapat menemukan dan memperbaikinya dengan melakukan perekaman.
Jika memiliki transaksi binder, Anda dapat merekam stack panggilannya dengan
perintah adb
berikut:
$ adb shell am trace-ipc start
… use the app - scroll/animate ...
$ adb shell am trace-ipc stop --dump-file /data/local/tmp/ipc-trace.txt
$ adb pull /data/local/tmp/ipc-trace.txt
Kadang panggilan yang tampak tidak berbahaya, seperti
getRefreshRate()
, dapat
memicu transaksi binder dan menyebabkan masalah besar jika sering
dipanggil. Perekaman secara berkala dapat membantu Anda menemukan dan memperbaiki masalah
tersebut jika muncul.
Jika Anda tidak melihat aktivitas binder, tetapi masih juga tidak melihat UI thread berjalan, pastikan bahwa Anda tidak menunggu penguncian atau operasi lain dari thread lain. Biasanya, UI thread tidak perlu menunggu hasil dari thread lain. Thread lain harus memposting informasi ke UI thread.
Alokasi objek dan pembersihan sampah memori
Alokasi objek dan pembersihan sampah memori (GC) telah menjadi masalah yang tidak begitu signifikan karena ART diperkenalkan sebagai waktu proses default di Android 5.0, tetapi masih mungkin untuk membebani thread dengan pekerjaan ekstra ini. Boleh saja mengalokasikan dengan tujuan merespons peristiwa langka yang tidak sering terjadi dalam satu detik—seperti pengguna mengetuk tombol—tetapi perlu diingat bahwa setiap alokasi menimbulkan biaya. Jika alokasi berada dalam loop padat yang sering dipanggil, sebaiknya hindari alokasi untuk meringankan beban pada GC.
Systrace akan menunjukkan kepada Anda apakah GC sering berjalan, dan Memory Profiler Android dapat menunjukkan asal alokasi. Anda tidak akan mengalami kesulitan jika dapat menghindari alokasi, terutama dalam loop yang ketat.
Di versi terbaru Android, GC umumnya berjalan di thread latar belakang yang bernama HeapTaskDaemon. Jumlah alokasi yang signifikan dapat berarti lebih banyak resource CPU yang dihabiskan di GC, seperti yang ditunjukkan pada gambar 5.
Direkomendasikan untuk Anda
- Catatan: teks link ditampilkan saat JavaScript nonaktif
- Menjalankan benchmark untuk aplikasi
- Ringkasan pengukuran performa aplikasi
- Praktik terbaik untuk pengoptimalan aplikasi