我遇到了一个性能问题,我认为这是由衡量我的观点引起的。我的布局是以编程方式构建的,因此我无法显示布局的xml。但以下是一些可能导致问题的观点:
问题似乎只在我填充LinearListView时出现;添加视图似乎会触发多次调用View.measure:
为什么View.measured被多次调用?LinearLayout使用MATH_PARENT作为它的大小意味着当添加一个孩子时,它不必再次测量它,对吗?
我不知道如何防止这种情况发生,或者如何调试问题。我试图将LinearListView的可见性设置为GONE,直到添加所有项目,但这并没有什么不同。
我希望我提供了足够的信息,让人们给我指明正确的方向。
编辑#1 (07/01):
下面是用于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");
}
}
}
编辑#2(07/01):
我子类化了RelativeLayout并覆盖了onMeasure,这样我就可以记录哪些视图被测量,以及测量的顺序。这些是结果:
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
...
这个清单持续了一段时间。但对我来说似乎很奇怪的是,它从测量顶视图(DefaultLayout.onMeasure)开始,然后测量所有子视图(所以所有视图)。它多次这样做!
什么会导致在顶视图上调用onMeasure?
编辑#3(07/02):
可能是 notifyDataSetChanged()
触发了顶视图的测量吗?
编辑#4(07/03):
是的,看起来好像是< code > notifyDataSetChanged()触发了要测量的顶级视图(我能够在一个新的测试项目中重现这一点)。由于我的适配器的工作方式,< code > notifyDataSetChanged()被多次调用。
当我调用 notifyDataSetChanged()
时测量所有视图的原因是什么?有没有办法防止这种情况发生?
你没有说相对布局在哪里,这是列表的包装吗?
根据Romain Guy的说法,相对布局总是进行两次测量,因此这可能会导致一些减速。你有没有可能有嵌套的相对布局?显然,这可能具有指数级的时间复杂度。
请参阅此答案以供参考(以及指向他实际说的Google io视频的链接):
https://stackoverflow.com/a/17496262/1281144
在您的描述中,有一件事让我印象深刻,那就是您的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()来删除所有视图并重新绘制它们。
在您的进程数据结果()方法中,您可以执行以下操作:
for (int i = 0; i < result.getItems().size(); i++) {
itemList.put(skip + i, result.getItems().get(i));
}
因此,在添加每个项目后,列表正在重绘(当然是在 UI 线程上)。
我会自己修改库以删除对onChanged()中setup儿童()的调用,如下所示:
private DataSetObserver mDataObserver = new DataSetObserver() {
@Override
public void onChanged() {
//setupChildren(); do nothing here
}
@Override
public void onInvalidated() {
setupChildren();
}
};
我假设当您调用notifyDataSetChanged()时,会调用onInvalidated()