How to fix Slow Rendering (Android vitals)

11,588

Solution 1

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.

enter image description here

enter image description here

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: enter image description here

    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);
    }
  };

Solution 2

I checked your code. Not sure if this is the actual code or if you have more to this. In any case I will draw attention to some of the Rendering issues in android.

1. OverDraw

Overdraw is where you waste GPU processing time by coloring in pixels that only get colored in again by something else. These can be common if you have added a background to your parent container layout and then the children are also added a background or if you have added a common background on you styles file for the application theme and then added backgrounds to the rest of the xml layout files you have created.

The causes for the overdraw can be anything, try checking your code for this. There is a developer tool installed in all mobile devices to check Overdraw in developer options. Here is the official documentation for overdraw.

2. View hierarchy

To render each view, Android goes through three stages:

1.measure

2.layout

3.draw

The time it takes Android to complete these stages is proportional to the number of views in your hierarchy. I see in your layout file that you have constraint layout which includes a linear layout. I don't see the use of this. Constraint layout was introduced to help developers reduce view hierarchy. Reduce the number of childs a particular layout can have. There is also a tool to help you with this. Here is the official android guide to it. Try these steps to figure out the GPU rendering issues.

Share:
11,588

Related videos on Youtube

Steve M
Author by

Steve M

Updated on July 18, 2022

Comments

  • Steve M
    Steve M almost 2 years

    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?

  • Steve M
    Steve M about 7 years
    I set layout_width to match_parent and still mostly janky frames, on both Moto X Pure and a Galaxy S8+. Though with the X Pure the 'Slow UI Thread' category has few frames and the 90th - 99th percentile frames are reduced to just over 20ms.
  • Tin Tran
    Tin Tran about 7 years
    Are u talking about the sample project or your production app ?
  • Steve M
    Steve M about 7 years
    Janky frames on the sample project even with match_parent
  • Steve M
    Steve M about 7 years
    How did you get 60 FPS with postOnAnimation(), its not working for me. Do you mean only update the views in the postOnAnimation() runnable, and update the count value somewhere else?
  • Steve M
    Steve M about 7 years
    This is dependent on the interactive governor also. On one device if you touch the screen it instantly becomes <16ms. I updated my production app with a class that sends NOP commands to keep the CPU frequency maxed out on Nexus and Pixel devicesand it halved my "slow" sessions from 10% to 5%. Flagged with orange "borderline" icon in Developer Console, instead of red icon on the tab as previously.
  • Tin Tran
    Tin Tran about 7 years
    postOnAnimation() is for posting the Runnable in the beginning of the next frame/VSYNC. Yes. I mean only update the view in the postOnAnimation() runnable. you can update the count value in a separate Runnable using view.postDelay as you used to.
  • Tin Tran
    Tin Tran about 7 years
    I can help open a Github PR if you want .
  • Tin Tran
    Tin Tran about 7 years
    I have updated the answer with the postOnAnimation method
  • Steve M
    Steve M about 7 years
    It is updating very frequently, is there anyway to do so it only updates every 250ms and still keeps it under 16ms. That frequent updates (whether 16ms or using the constant postOnAnimation) is going to drain battery and block main thread, counterproductive to goals of Android vitals.
  • Vijayadhas Chandrasekaran
    Vijayadhas Chandrasekaran almost 4 years
    @TinTran I think you can help me on my two questions. Please review. Question 1 Question 2