Performance Optimization for Emoji Deletion in Android EditText Using Profiler and Emoji2
Using Android Profiler we discovered that deleting many custom emojis in an EditText triggers repeated DynamicLayout ChangeWatcher calls, causing up to two‑second lag, and by adopting the emoji2 library’s EmojiSpan rendering with a custom EditableFactory that suppresses onSpanChanged for emoji spans, the lag is eliminated.
In real‑world usage, deleting a large number of emojis from the middle of a text can cause a noticeable lag of up to 2 seconds, severely affecting user experience.
Using Android Profiler we traced the latency to the following call chain: SpannableStringBuilder.delete → SpannableStringBuilder.sendSpanChanged → DynamicLayout$ChangeWatcher.onSpanChanged . The onSpanChanged method is invoked many times, and each invocation performs expensive layout calculations.
Profiling revealed that DynamicLayout$ChangeWatcher is called repeatedly for every EmojiSpan affected by the delete operation, which explains the long pause.
Tests showed that system emojis do not exhibit the same lag, suggesting that the issue originates from the handling of custom emojis (e.g., large bitmap size).
The open‑source emoji2 library (androidx.emoji2) adopts a different rendering strategy: it uses EmojiSpan together with TypefaceEmojiRasterizer and draws emojis via canvas.drawText instead of ImageSpan . This approach reduces the number of layout passes.
/**
* Draws the emoji onto a canvas with origin at (x,y), using the specified paint.
*
* @param canvas Canvas to be drawn
* @param x x-coordinate of the origin of the emoji being drawn
* @param y y-coordinate of the baseline of the emoji being drawn
* @param paint Paint used for the text (e.g. color, size, style)
*/
public void draw(@NonNull final Canvas canvas, final float x, final float y,
@NonNull final Paint paint) {
final Typeface typeface = mMetadataRepo.getTypeface();
final Typeface oldTypeface = paint.getTypeface();
paint.setTypeface(typeface);
final int charArrayStartIndex = mIndex * 2;
canvas.drawText(mMetadataRepo.getEmojiCharArray(), charArrayStartIndex, 2, x, y, paint);
paint.setTypeface(oldTypeface);
}The library also provides EmojiEditableFactory , which creates a custom Editable for EditTextView . It replaces the default ChangeWatcher with a WatcherWrapper that suppresses onSpanChanged calls when the changed span is an EmojiSpan .
@Override
public Editable newEditable(@NonNull final CharSequence source) {
if (sWatcherClass != null) {
return SpannableBuilder.create(sWatcherClass, source);
}
return super.newEditable(source);
} @Override
public void onSpanChanged(Spannable text, Object what, int ostart, int oend, int nstart, int nend) {
if (mBlockCalls.get() > 0 && isEmojiSpan(what)) {
return; // block expensive calls for EmojiSpan
}
// platform‑specific work‑arounds omitted for brevity
((SpanWatcher) mObject).onSpanChanged(text, what, ostart, oend, nstart, nend);
}By copying EmojiEditableFactory and SpannableBuilder into our project and replacing the isEmojiSpan check with our own custom‑emoji detection, we can apply the same optimisation to custom emojis.
The final integration step is to set the custom factory on the EditTextView :
editTextView.setEditableFactory(EmojiEditableFactory.getInstance());This reduces the number of DynamicLayout$ChangeWatcher invocations during middle‑deletion of custom emojis, eliminating the lag.
In summary, the Android Profiler is essential for locating performance bottlenecks. The emoji2 source offers a proven solution for optimizing emoji editing, which can be adapted to custom emoji implementations to improve user experience.
Sohu Tech Products
A knowledge-sharing platform for Sohu's technology products. As a leading Chinese internet brand with media, video, search, and gaming services and over 700 million users, Sohu continuously drives tech innovation and practice. We’ll share practical insights and tech news here.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.