Android Performance: Overdraw的定位

  Android Performance系列的内容整理自UdacityAndroid Performance课程,是对该系列课程的笔记和总结。

  过度绘制(Overdraw)指的是一个像素在一帧里被绘制了多次,旧的绘制会被新的绘制覆盖,是无效的,造成对GPU的浪费。Overdraw通常发生于多个元素重合的情况,位于上层的元素会覆盖住下层的元素,使下层的元素不可见,此时如果还对下层元素进行绘制,就会发生Overdraw。

0. 示例代码

  下面使用的代码可以在这里找到,应用模拟了一个聊天App的界面,如图1所示。

图1

图1

1. 可视化Overdraw

  Android提供了可视化Overdraw的工具,用于定位发生Overdraw的区域,只需在开发者模式的选项中点击“Debug GPU overdraw”,并选择“Show overdraw areas”即可。

图2

  此时回到之前的App,界面如图3。

图3

  不同的背景颜色代表了不同的Overdraw次数,如图4所示:

  • 原色:没有Overdraw
  • 蓝色:一次Overdraw
  • 绿色:两次Overdraw
  • 粉色:三次Overdraw
  • 红色:四次以上Overdraw
图4

图4

2. 移除不必要的填充背景

  从图3中背景的大片绿色可以猜测这里手动设置了一个纯色的背景,导致整个应用的背景发生Overdraw。在activity_chatum_latinum.xml可以看到,这里设置了一个白色被背景@android:color/white

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_chatum_latinum_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/white"
    tools:context=".MainActivity"
    tools:ignore="MergeRootFrame" />

  为了移除这里的背景,除了直接修改上面的android:background,还可以使用getWindow().setBackgroundDrawable(null)。修改ChatumLatinumActivity.java的onCreate()如下:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_chatum_latinum);

    getWindow().setBackgroundDrawable(null);

    if (savedInstanceState == null) {
        getSupportFragmentManager().beginTransaction()
                .add(R.id.activity_chatum_latinum_container, new ChatsFragment())
                .commit();
    }
}

  此时App的背景变成蓝色,Overdraw减少了一次,如图5。

图5

图5

  除了activity_chatum_latinum.xml,在chat_item.xml和fragment_chats.xml中还有4个不必要的背景,全部删除后,Overdraw大幅减少,如图6。

图6

图6

3. 根据实际需要添加背景

  注意图6中的头像大部分是绿的,说明头像发生了两次Overdraw;其中有一个空头像是蓝色的,说明头像为空时发生了一次Overdraw。在ChatAdapter.java的getView()中可以看到头像的相关逻辑。

@Override
public View getView(int position, View view, ViewGroup parent) {
    //...
    if (chat.getAuthor().getAvatarId() != 0) {
        Picasso.with(getContext()).load(chat.getAuthor().getAvatarId()).into(
                chat_author_avatar);
    }
    chat_author_avatar.setBackgroundColor(chat.getAuthor().getColor());

    return view;
}

这里无论是否有头像,都会使用setBackgroundColor()添加背景,当头像部位空时,就会发生Overdraw,修改这里的逻辑为只在头像为空时才添加背景:

@Override
public View getView(int position, View view, ViewGroup parent) {
    //...
    if (chat.getAuthor().getAvatarId() == 0) {
        Picasso.with(getContext()).load(android.R.color.transparent).into(chat_author_avatar);
        chat_author_avatar.setBackgroundColor(chat.getAuthor().getColor());
    } else {
        Picasso.with(getContext()).load(chat.getAuthor().getAvatarId()).into(chat_author_avatar);
        chat_author_avatar.setBackgroundColor(Color.TRANSPARENT);
    }

    return view;
}

  此时Overdraw进一步减少,如图7。

图7

图7