Updates fragment dejunk
- Reuse stream item views. Simplified the view layout for this.
(In this CL we still inflate views, rather than creating them in code.
Even without doing that performance now seems good enough.)
- Decode HTML into CharSequence in ContactLoader
- Removed ContactTileImageContainer and created
LayoutSuppressingImageView and LayoutSuppressingQuickContactBadge
Bug 5982899
Change-Id: I5cbd816a290a50fca9a964b921d934061915aee1
diff --git a/src/com/android/contacts/ContactLoader.java b/src/com/android/contacts/ContactLoader.java
index b7cc87d..195eb78 100644
--- a/src/com/android/contacts/ContactLoader.java
+++ b/src/com/android/contacts/ContactLoader.java
@@ -74,6 +74,8 @@
public class ContactLoader extends Loader<ContactLoader.Result> {
private static final String TAG = "ContactLoader";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
private final Uri mRequestedUri;
private Uri mLookupUri;
private boolean mLoadGroupMetaData;
@@ -1076,6 +1078,17 @@
cursor.close();
}
+ // Pre-decode all HTMLs
+ final long start = System.currentTimeMillis();
+ for (StreamItemEntry streamItem : streamItems) {
+ streamItem.decodeHtml(getContext());
+ }
+ final long end = System.currentTimeMillis();
+ if (DEBUG) {
+ Log.d(TAG, "Decoded HTML for " + streamItems.size() + " items, took "
+ + (end - start) + " ms");
+ }
+
// Now retrieve any photo records associated with the stream items.
if (!streamItems.isEmpty()) {
if (result.isUserProfile()) {
diff --git a/src/com/android/contacts/detail/ContactDetailDisplayUtils.java b/src/com/android/contacts/detail/ContactDetailDisplayUtils.java
index 08e8bfe..eabd9ec 100644
--- a/src/com/android/contacts/detail/ContactDetailDisplayUtils.java
+++ b/src/com/android/contacts/detail/ContactDetailDisplayUtils.java
@@ -354,61 +354,76 @@
/** Creates the view that represents a stream item. */
public static View createStreamItemView(LayoutInflater inflater, Context context,
- StreamItemEntry streamItem, LinearLayout parent,
- View.OnClickListener photoClickListener) {
- View container = inflater.inflate(R.layout.stream_item_container, parent, false);
- ViewGroup contentTable = (ViewGroup) container.findViewById(R.id.stream_item_content);
+ View convertView, StreamItemEntry streamItem, View.OnClickListener photoClickListener) {
- ContactPhotoManager contactPhotoManager = ContactPhotoManager.getInstance(context);
- List<StreamItemPhotoEntry> photos = streamItem.getPhotos();
+ // Try to recycle existing views.
+ final View container;
+ if (convertView != null) {
+ container = convertView;
+ } else {
+ container = inflater.inflate(R.layout.stream_item_container, null, false);
+ }
+
+ final ContactPhotoManager contactPhotoManager = ContactPhotoManager.getInstance(context);
+ final List<StreamItemPhotoEntry> photos = streamItem.getPhotos();
final int photoCount = photos.size();
- // This stream item only has text.
+ // Add the text part.
+ addStreamItemText(context, streamItem, container);
+
+ // Add images.
+ final ViewGroup imageRows = (ViewGroup) container.findViewById(R.id.stream_item_image_rows);
+
if (photoCount == 0) {
- View textOnlyContainer = inflater.inflate(R.layout.stream_item_row_text, contentTable,
- false);
- addStreamItemText(context, streamItem, textOnlyContainer);
- contentTable.addView(textOnlyContainer);
+ // This stream item only has text.
+ imageRows.setVisibility(View.GONE);
} else {
- // This stream item has text and photos. Process the photos, two at a time.
- for (int index = 0; index < photoCount; index += 2) {
- final StreamItemPhotoEntry firstPhoto = photos.get(index);
- if (index + 1 < photoCount) {
- // Put in two photos, side by side.
- final StreamItemPhotoEntry secondPhoto = photos.get(index + 1);
- View photoContainer = inflater.inflate(R.layout.stream_item_row_two_images,
- contentTable, false);
- loadPhoto(contactPhotoManager, streamItem, firstPhoto, photoContainer,
- R.id.stream_item_first_image, photoClickListener);
- loadPhoto(contactPhotoManager, streamItem, secondPhoto, photoContainer,
- R.id.stream_item_second_image, photoClickListener);
- contentTable.addView(photoContainer);
- } else {
- // Put in a single photo
- View photoContainer = inflater.inflate(
- R.layout.stream_item_row_one_image, contentTable, false);
- loadPhoto(contactPhotoManager, streamItem, firstPhoto, photoContainer,
- R.id.stream_item_first_image, photoClickListener);
- contentTable.addView(photoContainer);
+ // This stream item has text and photos.
+ imageRows.setVisibility(View.VISIBLE);
+
+ // Number of image rows needed, which is cailing(photoCount / 2)
+ final int numImageRows = (photoCount + 1) / 2;
+
+ // Actual image rows.
+ final int numOldImageRows = imageRows.getChildCount();
+
+ // Make sure we have enough stream_item_row_images.
+ if (numOldImageRows == numImageRows) {
+ // Great, we have the just enough number of rows...
+
+ } else if (numOldImageRows < numImageRows) {
+ // Need to add more image rows.
+ for (int i = numOldImageRows; i < numImageRows; i++) {
+ View imageRow = inflater.inflate(R.layout.stream_item_row_images, imageRows,
+ true);
+ }
+ } else {
+ // We have exceeding image rows. Hide them.
+ for (int i = numImageRows; i < numOldImageRows; i++) {
+ imageRows.getChildAt(i).setVisibility(View.GONE);
}
}
- // Add text, comments, and attribution if applicable
- View textContainer = inflater.inflate(R.layout.stream_item_row_text, contentTable,
- false);
- // Add extra padding between the text and the images
- int extraVerticalPadding = context.getResources().getDimensionPixelSize(
- R.dimen.detail_update_section_between_items_vertical_padding);
- textContainer.setPadding(textContainer.getPaddingLeft(),
- textContainer.getPaddingTop() + extraVerticalPadding,
- textContainer.getPaddingRight(),
- textContainer.getPaddingBottom());
- addStreamItemText(context, streamItem, textContainer);
- contentTable.addView(textContainer);
- }
+ // Put images, two by two.
+ for (int i = 0; i < photoCount; i += 2) {
+ final View imageRow = imageRows.getChildAt(i / 2);
+ // Reused image rows may not visible, so make sure they're shown.
+ imageRow.setVisibility(View.VISIBLE);
- if (parent != null) {
- parent.addView(container);
+ // Show first image.
+ loadPhoto(contactPhotoManager, streamItem, photos.get(i), imageRow,
+ R.id.stream_item_first_image, photoClickListener);
+ final View secondContainer = imageRow.findViewById(R.id.second_image_container);
+ if (i + 1 < photoCount) {
+ // Show the second image too.
+ loadPhoto(contactPhotoManager, streamItem, photos.get(i + 1), imageRow,
+ R.id.stream_item_second_image, photoClickListener);
+ secondContainer.setVisibility(View.VISIBLE);
+ } else {
+ // Hide the second image, but it still has to occupy the space.
+ secondContainer.setVisibility(View.INVISIBLE);
+ }
+ }
}
return container;
@@ -447,14 +462,12 @@
ImageGetter imageGetter = new DefaultImageGetter(context.getPackageManager());
// Stream item text
- setDataOrHideIfNone(HtmlUtils.fromHtml(context, streamItem.getText(), imageGetter, null),
- htmlView);
+ setDataOrHideIfNone(streamItem.getDecodedText(), htmlView);
// Attribution
setDataOrHideIfNone(ContactBadgeUtil.getSocialDate(streamItem, context),
attributionView);
// Comments
- setDataOrHideIfNone(HtmlUtils.fromHtml(context, streamItem.getComments(), imageGetter,
- null), commentsView);
+ setDataOrHideIfNone(streamItem.getDecodedComments(), commentsView);
return rootView;
}
@@ -516,6 +529,15 @@
}
}
+ private static Html.ImageGetter sImageGetter;
+
+ public static Html.ImageGetter getImageGetter(Context context) {
+ if (sImageGetter == null) {
+ sImageGetter = new DefaultImageGetter(context.getPackageManager());
+ }
+ return sImageGetter;
+ }
+
/** Fetcher for images from resources to be included in HTML text. */
private static class DefaultImageGetter implements Html.ImageGetter {
/** The scheme used to load resources. */
diff --git a/src/com/android/contacts/detail/StreamItemAdapter.java b/src/com/android/contacts/detail/StreamItemAdapter.java
index 2d870b4..089cb07 100644
--- a/src/com/android/contacts/detail/StreamItemAdapter.java
+++ b/src/com/android/contacts/detail/StreamItemAdapter.java
@@ -104,7 +104,7 @@
manager.getAccountType(streamItem.getAccountType(), streamItem.getDataSet());
final View view = ContactDetailDisplayUtils.createStreamItemView(
- mInflater, mContext, streamItem, null,
+ mInflater, mContext, convertView, streamItem,
// Only pass the photo click listener if the account type has the photo
// view activity.
(accountType.getViewStreamItemPhotoActivity() == null) ? null : mPhotoClickListener
@@ -130,6 +130,12 @@
}
@Override
+ public int getViewTypeCount() {
+ // ITEM_VIEW_TYPE_HEADER and ITEM_VIEW_TYPE_STREAM_ITEM
+ return 2;
+ }
+
+ @Override
public int getItemViewType(int position) {
if (position == 0) {
return ITEM_VIEW_TYPE_HEADER;
diff --git a/src/com/android/contacts/list/ContactTileImageContainer.java b/src/com/android/contacts/list/ContactTileImageContainer.java
deleted file mode 100644
index e1a791b..0000000
--- a/src/com/android/contacts/list/ContactTileImageContainer.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.contacts.list;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.widget.FrameLayout;
-
-/**
- * Custom container for ImageView or ContactBadge inside {@link ContactTileView}.
- *
- * This improves the performance of favorite tabs by not passing the layout request to the parent
- * views, assuming that once measured this will not need to resize itself.
- */
-public class ContactTileImageContainer extends FrameLayout {
-
- public ContactTileImageContainer(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- public void requestLayout() {
- forceLayout();
- }
-}
\ No newline at end of file
diff --git a/src/com/android/contacts/util/StreamItemEntry.java b/src/com/android/contacts/util/StreamItemEntry.java
index 5959c46..6c8210f 100644
--- a/src/com/android/contacts/util/StreamItemEntry.java
+++ b/src/com/android/contacts/util/StreamItemEntry.java
@@ -16,10 +16,13 @@
package com.android.contacts.util;
+import com.android.contacts.detail.ContactDetailDisplayUtils;
import com.android.contacts.test.NeededForTesting;
+import android.content.Context;
import android.database.Cursor;
import android.provider.ContactsContract.StreamItems;
+import android.text.Html;
import java.util.ArrayList;
import java.util.Collections;
@@ -36,6 +39,8 @@
private final long mId;
private final String mText;
private final String mComments;
+ private CharSequence mDecodedText;
+ private CharSequence mDecodedComments;
private final long mTimestamp;
private final String mAccountType;
private final String mAccountName;
@@ -136,6 +141,24 @@
return mPhotos;
}
+ public void decodeHtml(Context context) {
+ final Html.ImageGetter imageGetter = ContactDetailDisplayUtils.getImageGetter(context);
+ if (mText != null) {
+ mDecodedText = HtmlUtils.fromHtml(context, mText, imageGetter, null);
+ }
+ if (mComments != null) {
+ mDecodedComments = HtmlUtils.fromHtml(context, mComments, imageGetter, null);
+ }
+ }
+
+ public CharSequence getDecodedText() {
+ return mDecodedText;
+ }
+
+ public CharSequence getDecodedComments() {
+ return mDecodedComments;
+ }
+
private static String getString(Cursor cursor, String columnName) {
return cursor.getString(cursor.getColumnIndex(columnName));
}
diff --git a/src/com/android/contacts/widget/LayoutSuppressingImageView.java b/src/com/android/contacts/widget/LayoutSuppressingImageView.java
new file mode 100644
index 0000000..d80aeea
--- /dev/null
+++ b/src/com/android/contacts/widget/LayoutSuppressingImageView.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.contacts.widget;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+
+/**
+ * Custom {@link ImageView} that improves layouting performance.
+ *
+ * This improves the performance by not passing requestLayout() to its parent, taking advantage
+ * of knowing that image size won't change once set.
+ */
+public class LayoutSuppressingImageView extends ImageView {
+
+ public LayoutSuppressingImageView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ public void requestLayout() {
+ forceLayout();
+ }
+}
diff --git a/src/com/android/contacts/widget/LayoutSuppressingQuickContactBadge.java b/src/com/android/contacts/widget/LayoutSuppressingQuickContactBadge.java
new file mode 100644
index 0000000..3413e53
--- /dev/null
+++ b/src/com/android/contacts/widget/LayoutSuppressingQuickContactBadge.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.contacts.widget;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.QuickContactBadge;
+
+/**
+ * Custom {@link QuickContactBadge} that improves layouting performance.
+ *
+ * This improves the performance by not passing requestLayout() to its parent, taking advantage
+ * of knowing that image size won't change once set.
+ */
+public class LayoutSuppressingQuickContactBadge extends QuickContactBadge {
+
+ public LayoutSuppressingQuickContactBadge(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ public void requestLayout() {
+ forceLayout();
+ }
+}