4

I've ran into a performance problem, which I think is caused by measuring of my views. My layout is built programmatically, so I cannot show the xml of my layout. But here are some views which might contribute to the problem:

  • A ScrollView for wrapping all the content (width: MATCH_PARENT, height: MATCH_PARENT)
  • A LinearLayout containing all the differnt content parts (width: MATCH_PARENT, height: MATCH_PARENT, a child of the previous view)
  • Two LinearListViews (LinearLayout) which are populated asynchronously (width: MATCH_PARENT, height: WRAP_CONTENT, children of the previous view)

The problem only seems to occur when I populate the LinearListView; adding views seems to trigger View.measure being called many times:

Method profiling result

Why is the View.measured called this many times? The LinearLayout having MATH_PARENT for its size means it doesn't have to measure it again when a child is added, right?

I'm not really sure how to prevent this from happening, or how to debug the problem. I've tried to set the visibility of the LinearListView to GONE untill all the items are added, but this didn't make a difference.

I hope I provided enough information for someone to point me in the right direction.

Edit #1 (07/01):

Here's some parts of the base class of the Adapters used for the LinearListViews:

public abstract class DataProviderAdapter<T> extends BaseAdapter
{
    ...

    @Override
    public View getView(int position, View convertView, ViewGroup parent)
    {
        View row = convertView;
        T item = getItem(position);

        Log.d(TAG, "getView(): Position: " + position + ", ConvertView: "
                + (convertView == null ? "null" : convertView) + ", TotalCount: " + getCount());

        if (row == null) {
            row = buildRow(position);
            markRowAsLoading(row);
        }

        if (item == null) {

            if (convertView != null) {
                markRowAsLoading(row);
            }

            int skip;
            if (itemsRequestParams != null) {
                int pageSize = itemsRequestParams.getTake();

                // determine how many items to skip
                skip = position - (position % pageSize);
            }
            else {
                skip = 0;
            }

            // if the page isn't already being fetched
            if (!pagesReceiving.contains(skip)) {
                pagesReceiving.add(skip);
                AsyncTaskUtil.executeMultiThreaded(new ReceiveItemsTask(), skip);
            }
        }
        else {
            Log.d(TAG, getClass().getSimpleName() + " - PrepareRow: " + getItemId(position)
                    + ", Pos: " + position);
            prepareRow(row, item, position);
        }
        return row;
    }

    protected void processDataResult(int skip, DataResult<T> result)
    {
        if (result instanceof GroupedDataResult<?>) {
            GroupedDataResult<T> groupedResult = (GroupedDataResult<T>)result;
            totalItemCount = groupedResult.getTotalGroupCount() + groupedResult.getTotalItemCount();
        }
        else {
            totalItemCount = result.getTotalItemCount();
        }

        for (int i = 0; i < result.getItems().size(); i++) {
            itemList.put(skip + i, result.getItems().get(i));
        }

        Log.d(TAG, "processDataResult(" + skip + ")");
        notifyDataSetChanged();
    }

    static protected class CellHolder
    {
        public ProgressBar ProgressSpinner;
        public List<android.widget.ImageView> Images;
        public List<TextView> Labels;
        private final HashMap<String, Object> _extras = new HashMap<String, Object>();

        public void putExtra(String key, Object value)
        {
            _extras.put(key, value);
        }

        @SuppressWarnings("unchecked")
        public <T> T getExtra(String key)
        {
            return _extras.containsKey(key) ? (T)_extras.get(key) : null;
        }
    }

    ...

    private class ReceiveItemsTask extends AsyncTask<Integer, Void, DataResult<T>>
    {
        private int skip;

        @Override
        protected DataResult<T> doInBackground(Integer... params)
        {
            skip = params[0];
            DataResult<T> items = getItems(skip);
            Log.d(TAG, "doInBackground(" + skip + ")");
            return items;
        }

        @Override
        protected void onPostExecute(DataResult<T> result)
        {
            Log.d(TAG,
                    "onPostExecute("
                            + (result == null ? "null" : result.getItems().size() + "/"
                                    + result.getTotalItemCount()) + ")");
            if (result == null) {
                return;
            }
            processDataResult(skip, result);
            Log.d(TAG, "onPostExecute end");
        }
    }
}

Edit #2 (07/01):

I subclassed RelativeLayout and overridden onMeasure so I could log which views were being measured, and in which order. These are the results:

07-01 15:37:37.434: V/DefaultLayout(8172): DefaultLayout.onMeasure
07-01 15:37:37.434: V/DefaultLayout(8172): TabView.onMeasure
07-01 15:37:37.434: V/DefaultLayout(8172): ItemDetailsContainerView.onMeasure
07-01 15:37:37.438: V/DefaultLayout(8172): HeaderView.onMeasure
07-01 15:37:37.438: V/DefaultLayout(8172): ItemDetailsInfoFieldsLayout.onMeasure
07-01 15:37:37.438: V/DefaultLayout(8172): ItemReviewTableView.onMeasure
07-01 15:37:37.438: V/DefaultLayout(8172): InfoFieldsLayout.onMeasure
07-01 15:37:37.438: V/DefaultLayout(8172): InfoFieldsLayout.onMeasure
07-01 15:37:37.442: V/DefaultLayout(8172): InfoFieldsLayout.onMeasure
07-01 15:37:37.442: V/DefaultLayout(8172): InfoFieldsLayout.onMeasure
07-01 15:37:37.442: V/DefaultLayout(8172): InfoFieldsLayout.onMeasure
07-01 15:37:37.442: V/DefaultLayout(8172): InfoFieldsLayout.onMeasure
07-01 15:37:37.442: V/DefaultLayout(8172): InfoFieldsLayout.onMeasure
07-01 15:37:37.442: V/DefaultLayout(8172): InfoFieldsLayout.onMeasure
07-01 15:37:37.446: V/DefaultLayout(8172): ItemReviewTableView.onMeasure
07-01 15:37:37.446: V/DefaultLayout(8172): InfoFieldsLayout.onMeasure
07-01 15:37:37.446: V/DefaultLayout(8172): InfoFieldsLayout.onMeasure
07-01 15:37:37.446: V/DefaultLayout(8172): InfoFieldsLayout.onMeasure
07-01 15:37:37.446: V/DefaultLayout(8172): InfoFieldsLayout.onMeasure
07-01 15:37:37.450: V/DefaultLayout(8172): InfoFieldsLayout.onMeasure
07-01 15:37:37.450: V/DefaultLayout(8172): InfoFieldsLayout.onMeasure
07-01 15:37:37.450: V/DefaultLayout(8172): InfoFieldsLayout.onMeasure
07-01 15:37:37.450: V/DefaultLayout(8172): InfoFieldsLayout.onMeasure
07-01 15:37:37.450: V/DefaultLayout(8172): RelatedItemsTableView.onMeasure
07-01 15:37:37.454: V/DefaultLayout(8172): RatingView.onMeasure
07-01 15:37:37.458: V/DefaultLayout(8172): RatingView.onMeasure
07-01 15:37:37.462: V/DefaultLayout(8172): RatingView.onMeasure
07-01 15:37:37.466: V/DefaultLayout(8172): RatingView.onMeasure
07-01 15:37:37.470: V/DefaultLayout(8172): RatingView.onMeasure
07-01 15:37:37.474: V/DefaultLayout(8172): RatingView.onMeasure
07-01 15:37:37.478: V/DefaultLayout(8172): RatingView.onMeasure
07-01 15:37:37.478: V/DefaultLayout(8172): RatingView.onMeasure
07-01 15:37:37.482: V/DefaultLayout(8172): RatingView.onMeasure
07-01 15:37:37.482: V/DefaultLayout(8172): RatingView.onMeasure
07-01 15:37:37.486: V/DefaultLayout(8172): RatingView.onMeasure
07-01 15:37:37.486: V/DefaultLayout(8172): RatingView.onMeasure
07-01 15:37:37.490: V/DefaultLayout(8172): RatingView.onMeasure
07-01 15:37:37.490: V/DefaultLayout(8172): RatingView.onMeasure
07-01 15:37:37.494: V/DefaultLayout(8172): RatingView.onMeasure
07-01 15:37:37.494: V/DefaultLayout(8172): RatingView.onMeasure
07-01 15:37:37.498: V/DefaultLayout(8172): RelatedItemsTableView.onMeasure
07-01 15:37:37.498: V/DefaultLayout(8172): RatingView.onMeasure
07-01 15:37:37.498: V/DefaultLayout(8172): RatingView.onMeasure
07-01 15:37:37.502: V/DefaultLayout(8172): RatingView.onMeasure
07-01 15:37:37.502: V/DefaultLayout(8172): RatingView.onMeasure
07-01 15:37:37.506: V/DefaultLayout(8172): RatingView.onMeasure
07-01 15:37:37.506: V/DefaultLayout(8172): RatingView.onMeasure
07-01 15:37:37.510: V/DefaultLayout(8172): RatingView.onMeasure
07-01 15:37:37.510: V/DefaultLayout(8172): RatingView.onMeasure
07-01 15:37:37.514: V/DefaultLayout(8172): RatingView.onMeasure
07-01 15:37:37.514: V/DefaultLayout(8172): RatingView.onMeasure
07-01 15:37:37.518: V/DefaultLayout(8172): RatingView.onMeasure
07-01 15:37:37.518: V/DefaultLayout(8172): RatingView.onMeasure
07-01 15:37:37.522: V/DefaultLayout(8172): RatingView.onMeasure
07-01 15:37:37.522: V/DefaultLayout(8172): RatingView.onMeasure
07-01 15:37:37.526: V/DefaultLayout(8172): RatingView.onMeasure
07-01 15:37:37.526: V/DefaultLayout(8172): RatingView.onMeasure
...

This list goes on for a while. But what seems weird to me, is that it starts with measuring the top view (DefaultLayout.onMeasure), and then also measures ALL the children (so all the views). It does this multiple times!

What could be causing onMeasure to be called on the top view?

Edit #3 (07/02):

Could it be that notifyDataSetChanged() triggers the measuring of the top view?

Edit #4 (07/03):

Yes, it does seem like notifyDataSetChanged() triggers the top-level view to be measured (I was able to reproduce this in a new test project). And since the way my adapter works, notifyDataSetChanged() is called multiple times.

What is the reason all views are measured when i call notifyDataSetChanged()? Is there a way I can prevent this from happening?

4

2 回答 2

4

您没有说相对布局的来源,这是列表的包装器吗?

根据 Romain Guy 的说法,相对布局总是执行两次测量传递,因此这可能会导致一些减速。你甚至有嵌套的相对布局吗?显然,这可能具有指数时间复杂度。

请参阅此答案以供参考(以及他实际所说的 Google io 视频的链接):

https://stackoverflow.com/a/17496262/1281144

于 2013-07-09T05:37:07.033 回答
0

在您的描述中立即让我印象深刻的一件事是您的 LinearLayout,它是您的 ScrollView 的直接(也是唯一)子级设置为具有 MATCH_PARENT 的高度 - 这是错误的 - 它应该具有 WRAP_CONTENT 的高度。ScrollView 的子级不能与 ScrollView 本身高度相同 - 否则无法滚动。

除此之外,查看 LinearListView 的代码,我看到了这一点:

private DataSetObserver mDataObserver = new DataSetObserver() {

    @Override
    public void onChanged() {
        setupChildren();
    }

    @Override
    public void onInvalidated() {
        setupChildren();
    }

};

因此,每当底层数据集发生更改时,就会调用 setupChildren() 来删除所有视图并重新绘制它们。

在您的 processDataResult() 方法中,您可以这样做:

 for (int i = 0; i < result.getItems().size(); i++) {
        itemList.put(skip + i, result.getItems().get(i));
    }

因此,在添加每个项目后,列表正在重新绘制(当然是在 UI 线程上)。

我会自己修改库以删除 onChanged() 中对 setupChildren() 的调用,如下所示:

private DataSetObserver mDataObserver = new DataSetObserver() {

    @Override
    public void onChanged() {
        //setupChildren(); do nothing here
    }

    @Override
    public void onInvalidated() {
        setupChildren();
    }

};

我认为当你调用 notifyDataSetChanged() 时会调用 onInvalidated()

于 2013-07-01T12:06:31.570 回答