I have an app that is listed as in the bottom 25% in the new Google Play Console - Android vitals section for Slow Rendering. I am concerned of this because of such articles that seem to say Google Play may penalize your app in the Play Store rankings if you fall in the bottom 25%.
However, it seems impossible to improve this metric for my app. It plays music and has a SeekBar and TextView which is updated every 250ms as any music player would. I made the minimum basic program to demonstrate:
public class MainActivity extends AppCompatActivity {
int count;
SeekBar seekBar;
TextView textView;
Runnable runnable =
new Runnable() {
@Override
public void run() {
textView.setText(Integer.toString(count));
seekBar.setProgress(count);
++count;
seekBar.postDelayed(runnable, 250);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
seekBar = (SeekBar) findViewById(R.id.seek);
textView = (TextView) findViewById(R.id.text);
seekBar.post(runnable);
}
}
Full project here: https://github.com/svenoaks/SlowRendering.git
When I run this program on hardware similar to the Nexus devices, I get these results for a
adb shell dumpsys gfxinfo com.example.xyz.slowrendering
command:
Stats since: 19222191084749ns
Total frames rendered: 308
Janky frames: 290 (94.16%)
90th percentile: 32ms
95th percentile: 36ms
99th percentile: 44ms
Number Missed Vsync: 2
Number High input latency: 0
Number Slow UI thread: 139
Number Slow bitmap uploads: 0
Number Slow issue draw commands: 283
This would mean almost all my frames taking >16ms to render, I guess due to the periodic nature of the updating. All other music player apps I have tested also have this Slow Rendering problem as far as I can see. I fear Google's algorithm ruining my app ranking, is there any way I can improve my score?
The TextView
in your layout is causing the problem. Because it has layout width
of wrap_content
, which said that it's width has to be equals to the width of the content (the text in this example). Therefore, every time you call TextView.setText
an expensive measure/layout pass has to occur. Simple setting the layout_width
to match_parent
will solve the issue.
Here are two images what is taken from systrace
, it demonstrate the work run on the UI thread in 1 frame. The top one is done with layout_width=wrap_content
and the bottom one is with layout_width=match_parent
.
Two following methods that i have tested will improve the frame rate:
If you post the runnable in shorter span like 16ms (seekBar.postDelayed(runnable, 16)
), you get this smooth 60fps:
P/s: I am not sure why yet.
Use some other way to update the count
value instead of inside the Runnable
. Use View.postOnAnimation(Runnable)
to reschedule the Runnable. The result is 60FPS for the sample project.
EDIT:
two Runnable
that uses postOnAnimation(Runnable)
Runnable runnable =
new Runnable() {
@Override
public void run() {
textView.setText(Integer.toString(count));
seekBar.setProgress(count);
seekBar.postOnAnimation(this);
}
};
Runnable updateCount = new Runnable() {
@Override public void run() {
++count;
seekBar.postDelayed(this, 250);
}
};