From d8fb81f601830385a2343d08ad5dd171e4c7bfe0 Mon Sep 17 00:00:00 2001 From: Owen Lin Date: Thu, 29 Mar 2012 14:27:58 +0800 Subject: [PATCH] Upload textures in background. Change-Id: I365f7be9aaab793366884249cbb10e8b6f0ab0e6 --- .../gallery3d/app/AbstractGalleryActivity.java | 4 +- .../android/gallery3d/app/AlbumDataAdapter.java | 3 + .../android/gallery3d/app/AlbumSetDataAdapter.java | 3 + .../android/gallery3d/data/ImageCacheRequest.java | 3 +- src/com/android/gallery3d/data/MediaItem.java | 10 +- src/com/android/gallery3d/ui/AlbumLabelMaker.java | 54 ++++++++--- .../gallery3d/ui/AlbumSetSlidingWindow.java | 103 ++++++++++++++------- src/com/android/gallery3d/ui/AlbumSetView.java | 13 ++- .../android/gallery3d/ui/AlbumSlidingWindow.java | 46 ++++++--- src/com/android/gallery3d/ui/AlbumView.java | 9 +- src/com/android/gallery3d/ui/BitmapPool.java | 65 +++++-------- src/com/android/gallery3d/ui/GLRoot.java | 5 +- src/com/android/gallery3d/ui/GLRootView.java | 17 ++-- src/com/android/gallery3d/ui/SlotView.java | 16 ++-- src/com/android/gallery3d/ui/TextureUploader.java | 97 +++++++++++++++++++ src/com/android/gallery3d/ui/TileImageView.java | 3 +- 16 files changed, 325 insertions(+), 126 deletions(-) create mode 100644 src/com/android/gallery3d/ui/TextureUploader.java diff --git a/src/com/android/gallery3d/app/AbstractGalleryActivity.java b/src/com/android/gallery3d/app/AbstractGalleryActivity.java index 899e9bfa4..64c430d7f 100644 --- a/src/com/android/gallery3d/app/AbstractGalleryActivity.java +++ b/src/com/android/gallery3d/app/AbstractGalleryActivity.java @@ -33,7 +33,7 @@ import android.view.WindowManager; import com.android.gallery3d.R; import com.android.gallery3d.data.DataManager; -import com.android.gallery3d.ui.BitmapPool; +import com.android.gallery3d.data.MediaItem; import com.android.gallery3d.ui.GLRoot; import com.android.gallery3d.ui.GLRootView; import com.android.gallery3d.util.ThreadPool; @@ -177,7 +177,7 @@ public class AbstractGalleryActivity extends Activity implements GalleryActivity } finally { mGLRootView.unlockRenderThread(); } - BitmapPool.clear(); + MediaItem.getMicroThumbPool().clear(); } @Override diff --git a/src/com/android/gallery3d/app/AlbumDataAdapter.java b/src/com/android/gallery3d/app/AlbumDataAdapter.java index 1d10b89f4..ec46e50e5 100644 --- a/src/com/android/gallery3d/app/AlbumDataAdapter.java +++ b/src/com/android/gallery3d/app/AlbumDataAdapter.java @@ -18,6 +18,7 @@ package com.android.gallery3d.app; import android.os.Handler; import android.os.Message; +import android.os.Process; import com.android.gallery3d.common.Utils; import com.android.gallery3d.data.ContentListener; @@ -314,6 +315,8 @@ public class AlbumDataAdapter implements AlbumView.Model { @Override public void run() { + Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); + boolean updateComplete = false; while (mActive) { synchronized (this) { diff --git a/src/com/android/gallery3d/app/AlbumSetDataAdapter.java b/src/com/android/gallery3d/app/AlbumSetDataAdapter.java index e739a96fb..eea039d19 100644 --- a/src/com/android/gallery3d/app/AlbumSetDataAdapter.java +++ b/src/com/android/gallery3d/app/AlbumSetDataAdapter.java @@ -18,6 +18,7 @@ package com.android.gallery3d.app; import android.os.Handler; import android.os.Message; +import android.os.Process; import android.os.SystemClock; import com.android.gallery3d.common.Utils; @@ -307,6 +308,8 @@ public class AlbumSetDataAdapter implements AlbumSetView.Model { @Override public void run() { + Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); + boolean updateComplete = false; while (mActive) { synchronized (this) { diff --git a/src/com/android/gallery3d/data/ImageCacheRequest.java b/src/com/android/gallery3d/data/ImageCacheRequest.java index 64dfc9f45..d3d4f51e7 100644 --- a/src/com/android/gallery3d/data/ImageCacheRequest.java +++ b/src/com/android/gallery3d/data/ImageCacheRequest.java @@ -22,7 +22,6 @@ import android.graphics.BitmapFactory; import com.android.gallery3d.app.GalleryApp; import com.android.gallery3d.common.BitmapUtils; import com.android.gallery3d.data.ImageCacheService.ImageData; -import com.android.gallery3d.ui.BitmapPool; import com.android.gallery3d.util.ThreadPool.Job; import com.android.gallery3d.util.ThreadPool.JobContext; @@ -56,7 +55,7 @@ abstract class ImageCacheRequest implements Job { options.inPreferredConfig = Bitmap.Config.ARGB_8888; Bitmap bitmap; if (mType == MediaItem.TYPE_MICROTHUMBNAIL) { - bitmap = BitmapPool.decode(jc, BitmapPool.TYPE_MICRO_THUMB, + bitmap = MediaItem.getMicroThumbPool().decode(jc, data.mData, data.mOffset, data.mData.length - data.mOffset, options); } else { bitmap = DecodeUtils.decode(jc, diff --git a/src/com/android/gallery3d/data/MediaItem.java b/src/com/android/gallery3d/data/MediaItem.java index b2632f1cc..2b8f8a353 100644 --- a/src/com/android/gallery3d/data/MediaItem.java +++ b/src/com/android/gallery3d/data/MediaItem.java @@ -19,8 +19,9 @@ package com.android.gallery3d.data; import android.graphics.Bitmap; import android.graphics.BitmapRegionDecoder; -import com.android.gallery3d.util.ThreadPool.Job; +import com.android.gallery3d.ui.BitmapPool; import com.android.gallery3d.ui.ScreenNail; +import com.android.gallery3d.util.ThreadPool.Job; // MediaItem represents an image or a video item. public abstract class MediaItem extends MediaObject { @@ -39,6 +40,9 @@ public abstract class MediaItem extends MediaObject { public static final String MIME_TYPE_JPEG = "image/jpeg"; + private static final BitmapPool sMicroThumbPool = + new BitmapPool(MICROTHUMBNAIL_TARGET_SIZE, MICROTHUMBNAIL_TARGET_SIZE); + // TODO: fix default value for latlng and change this. public static final double INVALID_LATLNG = 0f; @@ -108,4 +112,8 @@ public abstract class MediaItem extends MediaObject { "should only request thumb/microthumb from cache"); } } + + public static BitmapPool getMicroThumbPool() { + return sMicroThumbPool; + } } diff --git a/src/com/android/gallery3d/ui/AlbumLabelMaker.java b/src/com/android/gallery3d/ui/AlbumLabelMaker.java index 22fe16d5f..68fa6958a 100644 --- a/src/com/android/gallery3d/ui/AlbumLabelMaker.java +++ b/src/com/android/gallery3d/ui/AlbumLabelMaker.java @@ -8,6 +8,7 @@ import android.graphics.Bitmap.Config; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; +import android.graphics.PorterDuff; import android.graphics.Typeface; import android.text.TextPaint; import android.text.TextUtils; @@ -27,6 +28,9 @@ public class AlbumLabelMaker { private final TextPaint mCountPaint; private final Context mContext; + private int mLabelWidth; + private BitmapPool mBitmapPool; + private final LazyLoadedBitmap mLocalSetIcon; private final LazyLoadedBitmap mPicasaIcon; private final LazyLoadedBitmap mCameraIcon; @@ -89,14 +93,20 @@ public class AlbumLabelMaker { } } + public synchronized void setLabelWidth(int width) { + if (mLabelWidth == width) return; + mLabelWidth = width; + mBitmapPool = new BitmapPool(mLabelWidth, mSpec.labelBackgroundHeight); + } + public ThreadPool.Job requestLabel( - String title, String count, int sourceType, int slotWidth) { - return new AlbumLabelJob(null, title, count, sourceType, slotWidth); + String title, String count, int sourceType) { + return new AlbumLabelJob(null, title, count, sourceType); } public ThreadPool.Job requestLabel( - MediaSet album, int sourceType, int slotWidth) { - return new AlbumLabelJob(album, null, null, sourceType, slotWidth); + MediaSet album, int sourceType) { + return new AlbumLabelJob(album, null, null, sourceType); } private static void drawText(Canvas canvas, @@ -114,15 +124,13 @@ public class AlbumLabelMaker { private final String mTitle; private final String mCount; private final int mSourceType; - private final int mSlotWidth; public AlbumLabelJob(MediaSet album, - String title, String count, int sourceType, int slotWidth) { + String title, String count, int sourceType) { mAlbum = album; mTitle = title; mCount = count; mSourceType = sourceType; - mSlotWidth = slotWidth; } @Override @@ -136,22 +144,36 @@ public class AlbumLabelMaker { ? Utils.ensureNotNull(mCount) : String.valueOf(album.getTotalMediaItemCount()); Bitmap icon = getOverlayAlbumIcon(mSourceType); - Bitmap bitmap = Bitmap.createBitmap(mSlotWidth, - s.labelBackgroundHeight, Config.ARGB_8888); - Canvas canvas = new Canvas(bitmap); + + Bitmap bitmap = null; + Canvas canvas; + int labelWidth; + + synchronized (this) { + labelWidth = mLabelWidth; + bitmap = mBitmapPool.getBitmap(); + } + if (bitmap == null) { + bitmap = Bitmap.createBitmap(labelWidth, + s.labelBackgroundHeight, Config.ARGB_8888); + canvas = new Canvas(bitmap); + } else { + canvas = new Canvas(bitmap); + canvas.drawColor(0, PorterDuff.Mode.SRC); + } // draw title if (jc.isCancelled()) return null; int x = s.leftMargin; int y = s.titleOffset; - drawText(canvas, x, y, title, mSlotWidth - s.leftMargin, mTitlePaint); + drawText(canvas, x, y, title, labelWidth - s.leftMargin, mTitlePaint); // draw the count if (jc.isCancelled()) return null; if (icon != null) x = s.iconSize; y += s.titleFontSize + s.countOffset; drawText(canvas, x, y, count, - mSlotWidth - s.leftMargin - s.iconSize, mCountPaint); + labelWidth - s.leftMargin - s.iconSize, mCountPaint); // draw the icon if (icon != null) { @@ -166,4 +188,12 @@ public class AlbumLabelMaker { return bitmap; } } + + public void reycleLabel(Bitmap label) { + mBitmapPool.recycle(label); + } + + public void clearRecycledLabels() { + mBitmapPool.clear(); + } } diff --git a/src/com/android/gallery3d/ui/AlbumSetSlidingWindow.java b/src/com/android/gallery3d/ui/AlbumSetSlidingWindow.java index 6eb391198..467b52233 100644 --- a/src/com/android/gallery3d/ui/AlbumSetSlidingWindow.java +++ b/src/com/android/gallery3d/ui/AlbumSetSlidingWindow.java @@ -57,6 +57,7 @@ public class AlbumSetSlidingWindow implements AlbumSetView.ModelListener { private final ThreadPool mThreadPool; private final AlbumLabelMaker mLabelMaker; private final String mLoadingText; + private final TextureUploader mTextureUploader; private int mActiveRequestCount = 0; private boolean mIsActive = false; @@ -94,6 +95,7 @@ public class AlbumSetSlidingWindow implements AlbumSetView.ModelListener { mLabelMaker = new AlbumLabelMaker(activity.getAndroidContext(), labelSpec); mLoadingText = activity.getAndroidContext().getString(R.string.loading); + mTextureUploader = new TextureUploader(activity.getGLRoot()); mHandler = new SynchronizedHandler(activity.getGLRoot()) { @Override @@ -168,7 +170,11 @@ public class AlbumSetSlidingWindow implements AlbumSetView.ModelListener { 0, Math.max(0, mSize - data.length)); int contentEnd = Math.min(contentStart + data.length, mSize); setContentWindow(contentStart, contentEnd); - if (mIsActive) updateAllImageRequests(); + + if (mIsActive) { + updateTextureUploadQueue(); + updateAllImageRequests(); + } } // We would like to request non active slots in the following order: @@ -236,8 +242,8 @@ public class AlbumSetSlidingWindow implements AlbumSetView.ModelListener { entry.label = null; } if (album != null) { - entry.labelLoader = new AlbumLabelLoader( - slotIndex, album, entry.sourceType, mSlotWidth); + entry.labelLoader = + new AlbumLabelLoader(slotIndex, album, entry.sourceType); } entry.coverItem = cover; @@ -271,22 +277,23 @@ public class AlbumSetSlidingWindow implements AlbumSetView.ModelListener { return loader.isRequestInProgress(); } - private void notifySlotChanged(int slotIndex) { - // If the updated content is not cached, ignore it - if (slotIndex < mContentStart || slotIndex >= mContentEnd) { - Log.w(TAG, String.format( - "invalid update: %s is outside (%s, %s)", - slotIndex, mContentStart, mContentEnd) ); - return; + private void queueTextureForUpload(boolean isActive, Texture texture) { + if ((texture == null) || !(texture instanceof BitmapTexture)) return; + if (isActive) { + mTextureUploader.addFgTexture((BitmapTexture) texture); + } else { + mTextureUploader.addBgTexture((BitmapTexture) texture); } + } - AlbumSetEntry entry = mData[slotIndex % mData.length]; - MediaSet set = mSource.getMediaSet(slotIndex); - MediaItem coverItem = mSource.getCoverItem(slotIndex); - updateAlbumSetEntry(entry, slotIndex, set, coverItem); - updateAllImageRequests(); - if (mListener != null && isActiveSlot(slotIndex)) { - mListener.onContentChanged(); + private void updateTextureUploadQueue() { + if (!mIsActive) return; + mTextureUploader.clear(); + for (int i = mContentStart, n = mContentEnd; i < n; ++i) { + AlbumSetEntry entry = mData[i % mData.length]; + boolean isActive = isActiveSlot(i); + queueTextureForUpload(isActive, entry.label); + queueTextureForUpload(isActive, entry.content); } } @@ -318,13 +325,30 @@ public class AlbumSetSlidingWindow implements AlbumSetView.ModelListener { // paused, ignore slot changed event return; } - notifySlotChanged(index); + + // If the updated content is not cached, ignore it + if (index < mContentStart || index >= mContentEnd) { + Log.w(TAG, String.format( + "invalid update: %s is outside (%s, %s)", + index, mContentStart, mContentEnd) ); + return; + } + + AlbumSetEntry entry = mData[index % mData.length]; + MediaSet set = mSource.getMediaSet(index); + MediaItem coverItem = mSource.getCoverItem(index); + updateAlbumSetEntry(entry, index, set, coverItem); + updateAllImageRequests(); + updateTextureUploadQueue(); + if (mListener != null && isActiveSlot(index)) { + mListener.onContentChanged(); + } } public BitmapTexture getLoadingTexture() { if (mLoadingLabel == null) { Bitmap bitmap = mLabelMaker.requestLabel(mLoadingText, null, - SelectionDrawer.DATASOURCE_TYPE_NOT_CATEGORIZED, mSlotWidth) + SelectionDrawer.DATASOURCE_TYPE_NOT_CATEGORIZED) .run(ThreadPool.JOB_CONTEXT_STUB); mLoadingLabel = new BitmapTexture(bitmap); mLoadingLabel.setOpaque(false); @@ -337,6 +361,7 @@ public class AlbumSetSlidingWindow implements AlbumSetView.ModelListener { for (int i = mContentStart, n = mContentEnd; i < n; ++i) { freeSlotContent(i); } + mLabelMaker.clearRecycledLabels(); } public void resume() { @@ -362,7 +387,7 @@ public class AlbumSetSlidingWindow implements AlbumSetView.ModelListener { @Override protected void recycleBitmap(Bitmap bitmap) { - BitmapPool.recycle(BitmapPool.TYPE_MICRO_THUMB, bitmap); + MediaItem.getMicroThumbPool().recycle(bitmap); } @Override @@ -379,16 +404,19 @@ public class AlbumSetSlidingWindow implements AlbumSetView.ModelListener { @Override public void updateEntry() { Bitmap bitmap = getBitmap(); - if (bitmap == null) return; + if (bitmap == null) return; // error or recycled AlbumSetEntry entry = mData[mSlotIndex % mData.length]; - BitmapTexture content = new BitmapTexture(bitmap); - entry.content = content; + BitmapTexture texture = new BitmapTexture(bitmap); + entry.content = texture; if (isActiveSlot(mSlotIndex)) { + mTextureUploader.addFgTexture(texture); --mActiveRequestCount; if (mActiveRequestCount == 0) requestNonactiveImages(); if (mListener != null) mListener.onContentChanged(); + } else { + mTextureUploader.addBgTexture(texture); } } } @@ -439,24 +467,23 @@ public class AlbumSetSlidingWindow implements AlbumSetView.ModelListener { private final MediaSet mMediaSet; private final int mSlotIndex; private final int mSourceType; - private final int mLabelWidth; - public AlbumLabelLoader(int slotIndex, - MediaSet mediaSet, int sourceType, int labelWidth) { + public AlbumLabelLoader( + int slotIndex, MediaSet mediaSet, int sourceType) { mSlotIndex = slotIndex; mMediaSet = mediaSet; mSourceType = sourceType; - mLabelWidth = labelWidth; } @Override protected Future submitBitmapTask(FutureListener l) { return mThreadPool.submit(mLabelMaker.requestLabel( - mMediaSet, mSourceType, mLabelWidth), l); + mMediaSet, mSourceType), l); } @Override protected void recycleBitmap(Bitmap bitmap) { + mLabelMaker.reycleLabel(bitmap); } @Override @@ -466,28 +493,33 @@ public class AlbumSetSlidingWindow implements AlbumSetView.ModelListener { @Override public void updateEntry() { - if (isRecycled()) return; - Bitmap bitmap = getBitmap(); - if (bitmap == null) return; + if (bitmap == null) return; // Error or recycled AlbumSetEntry entry = mData[mSlotIndex % mData.length]; - BitmapTexture content = new BitmapTexture(bitmap); - content.setOpaque(false); - entry.label = content; + BitmapTexture texture = new BitmapTexture(bitmap); + texture.setOpaque(false); + entry.label = texture; if (isActiveSlot(mSlotIndex)) { + mTextureUploader.addFgTexture(texture); --mActiveRequestCount; if (mActiveRequestCount == 0) requestNonactiveImages(); if (mListener != null) mListener.onContentChanged(); + } else { + mTextureUploader.addBgTexture(texture); } } } public void onSlotSizeChanged(int width, int height) { if (mSlotWidth == width) return; + mSlotWidth = width; mLoadingLabel = null; + mLabelMaker.setLabelWidth(mSlotWidth); + + if (!mIsActive) return; for (int i = mContentStart, n = mContentEnd; i < n; ++i) { AlbumSetEntry entry = mData[i % mData.length]; @@ -498,8 +530,9 @@ public class AlbumSetSlidingWindow implements AlbumSetView.ModelListener { } entry.labelLoader = (entry.album == null) ? null - : new AlbumLabelLoader(i, entry.album, entry.sourceType, width); + : new AlbumLabelLoader(i, entry.album, entry.sourceType); } updateAllImageRequests(); + updateTextureUploadQueue(); } } diff --git a/src/com/android/gallery3d/ui/AlbumSetView.java b/src/com/android/gallery3d/ui/AlbumSetView.java index 70ecbb49b..8a169b11a 100644 --- a/src/com/android/gallery3d/ui/AlbumSetView.java +++ b/src/com/android/gallery3d/ui/AlbumSetView.java @@ -98,6 +98,13 @@ public class AlbumSetView implements SlotView.SlotRenderer { } } + private static Texture checkTexture(GLCanvas canvas, Texture texture) { + return ((texture == null) || ((texture instanceof UploadedTexture) + && !((UploadedTexture) texture).isContentValid(canvas))) + ? null + : texture; + } + @Override public int renderSlot(GLCanvas canvas, int index, int pass, int width, int height) { AlbumSetEntry entry = mDataWindow.get(index); @@ -107,8 +114,7 @@ public class AlbumSetView implements SlotView.SlotRenderer { private int renderContent(GLCanvas canvas, int pass, AlbumSetEntry entry, int width, int height) { - // Fit the content into the box - Texture content = entry.content; + Texture content = checkTexture(canvas, entry.content); if (content == null) { content = mWaitLoadingTexture; @@ -120,6 +126,7 @@ public class AlbumSetView implements SlotView.SlotRenderer { content = entry.content; } + // Fit the content into the box int w = content.getWidth(); int h = content.getHeight(); @@ -150,7 +157,7 @@ public class AlbumSetView implements SlotView.SlotRenderer { // We show the loading message only when the album is still loading // (Not when we are still preparing the label) - Texture content = entry.label; + Texture content = checkTexture(canvas, entry.label); if (entry.album == null) { content = mDataWindow.getLoadingTexture(); } diff --git a/src/com/android/gallery3d/ui/AlbumSlidingWindow.java b/src/com/android/gallery3d/ui/AlbumSlidingWindow.java index c88ca7d97..af6dc460d 100644 --- a/src/com/android/gallery3d/ui/AlbumSlidingWindow.java +++ b/src/com/android/gallery3d/ui/AlbumSlidingWindow.java @@ -53,6 +53,11 @@ public class AlbumSlidingWindow implements AlbumView.ModelListener { } private final AlbumView.Model mSource; + private final AlbumEntry mData[]; + private final SynchronizedHandler mHandler; + private final JobLimiter mThreadPool; + private final TextureUploader mTextureUploader; + private int mSize; private int mContentStart = 0; @@ -63,11 +68,6 @@ public class AlbumSlidingWindow implements AlbumView.ModelListener { private Listener mListener; - private final AlbumEntry mData[]; - - private SynchronizedHandler mHandler; - private JobLimiter mThreadPool; - private int mActiveRequestCount = 0; private boolean mIsActive = false; @@ -87,6 +87,7 @@ public class AlbumSlidingWindow implements AlbumView.ModelListener { }; mThreadPool = new JobLimiter(activity.getThreadPool(), JOB_LIMIT); + mTextureUploader = new TextureUploader(activity.getGLRoot()); } public void setListener(Listener listener) { @@ -156,9 +157,29 @@ public class AlbumSlidingWindow implements AlbumView.ModelListener { 0, Math.max(0, mSize - data.length)); int contentEnd = Math.min(contentStart + data.length, mSize); setContentWindow(contentStart, contentEnd); + updateUploadedTextures(); if (mIsActive) updateAllImageRequests(); } + private void uploadTexture(boolean isActive, Texture texture) { + if ((texture == null) || !(texture instanceof BitmapTexture)) return; + if (isActive) { + mTextureUploader.addFgTexture((BitmapTexture) texture); + } else { + mTextureUploader.addBgTexture((BitmapTexture) texture); + } + } + + private void updateUploadedTextures() { + if (!mIsActive) return; + mTextureUploader.clear(); + for (int i = mContentStart, n = mContentEnd; i < n; ++i) { + AlbumEntry entry = mData[i % mData.length]; + boolean isActive = isActiveSlot(i); + uploadTexture(isActive, entry.content); + } + } + // We would like to request non active slots in the following order: // Order: 8 6 4 2 1 3 5 7 // |---------|---------------|---------| @@ -244,8 +265,8 @@ public class AlbumSlidingWindow implements AlbumView.ModelListener { } private class ThumbnailLoader extends BitmapLoader { - final int mSlotIndex; - final MediaItem mItem; + private final int mSlotIndex; + private final MediaItem mItem; public ThumbnailLoader(int slotIndex, MediaItem item) { mSlotIndex = slotIndex; @@ -254,7 +275,7 @@ public class AlbumSlidingWindow implements AlbumView.ModelListener { @Override protected void recycleBitmap(Bitmap bitmap) { - BitmapPool.recycle(BitmapPool.TYPE_MICRO_THUMB, bitmap); + MediaItem.getMicroThumbPool().recycle(bitmap); } @Override @@ -269,16 +290,19 @@ public class AlbumSlidingWindow implements AlbumView.ModelListener { } public void updateEntry() { - if (isRecycled()) return; + Bitmap bitmap = getBitmap(); + if (bitmap == null) return; // error or recycled AlbumEntry entry = mData[mSlotIndex % mData.length]; - Bitmap bitmap = entry.contentLoader.getBitmap(); - if (bitmap != null) entry.content = new BitmapTexture(bitmap); + entry.content = new BitmapTexture(bitmap); if (isActiveSlot(mSlotIndex)) { + mTextureUploader.addFgTexture((BitmapTexture) entry.content); --mActiveRequestCount; if (mActiveRequestCount == 0) requestNonactiveImages(); if (mListener != null) mListener.onContentChanged(); + } else { + mTextureUploader.addBgTexture((BitmapTexture) entry.content); } } } diff --git a/src/com/android/gallery3d/ui/AlbumView.java b/src/com/android/gallery3d/ui/AlbumView.java index 827d57dec..1ed3ded29 100644 --- a/src/com/android/gallery3d/ui/AlbumView.java +++ b/src/com/android/gallery3d/ui/AlbumView.java @@ -79,10 +79,17 @@ public class AlbumView implements SlotView.SlotRenderer { mSlotView.invalidate(); } + private static Texture checkTexture(GLCanvas canvas, Texture texture) { + return ((texture == null) || ((texture instanceof UploadedTexture) + && !((UploadedTexture) texture).isContentValid(canvas))) + ? null + : texture; + } + @Override public int renderSlot(GLCanvas canvas, int index, int pass, int width, int height) { AlbumSlidingWindow.AlbumEntry entry = mDataWindow.get(index); - Texture content = entry.content; + Texture content = checkTexture(canvas, entry.content); if (content == null) { content = mWaitLoadingTexture; diff --git a/src/com/android/gallery3d/ui/BitmapPool.java b/src/com/android/gallery3d/ui/BitmapPool.java index e910aece1..2cd3a4e51 100644 --- a/src/com/android/gallery3d/ui/BitmapPool.java +++ b/src/com/android/gallery3d/ui/BitmapPool.java @@ -7,7 +7,6 @@ import android.graphics.BitmapFactory; import android.graphics.BitmapFactory.Options; import com.android.gallery3d.data.DecodeUtils; -import com.android.gallery3d.data.MediaItem; import com.android.gallery3d.util.ThreadPool.JobContext; import java.io.FileDescriptor; @@ -16,63 +15,47 @@ import java.util.ArrayList; public class BitmapPool { private static final String TAG = "BitmapPool"; - public static final int TYPE_MICRO_THUMB = 0; - private static final int TYPE_COUNT = 1; private static final int POOL_SIZE = 16; - private static final int EXPECTED_WIDTH[] = {MediaItem.MICROTHUMBNAIL_TARGET_SIZE}; - private static final int EXPECTED_HEIGHT[] = {MediaItem.MICROTHUMBNAIL_TARGET_SIZE}; + private final ArrayList mPool = new ArrayList(POOL_SIZE); - @SuppressWarnings("unchecked") - private static final ArrayList sPools[] = new ArrayList[TYPE_COUNT]; - static { - for (int i = 0; i < TYPE_COUNT; ++i) { - sPools[i] = new ArrayList(); - } - } + private final int mWidth; + private final int mHeight; - private BitmapPool() { + public BitmapPool(int width, int height) { + mWidth = width; + mHeight = height; } - public static Bitmap getBitmap(int type) { - ArrayList list = sPools[type]; - synchronized (list) { - int size = list.size(); - return size > 0 ? list.remove(size - 1) : null; - } + public synchronized Bitmap getBitmap() { + int size = mPool.size(); + return size > 0 ? mPool.remove(size - 1) : null; } - public static void recycle(int type, Bitmap bitmap) { + public void recycle(Bitmap bitmap) { if (bitmap == null) return; - if ((bitmap.getWidth() != EXPECTED_WIDTH[type]) - || (bitmap.getHeight() != EXPECTED_HEIGHT[type])) { + if ((bitmap.getWidth() != mWidth) || (bitmap.getHeight() != mHeight)) { bitmap.recycle(); return; } - ArrayList list = sPools[type]; - synchronized (list) { - if (list.size() < POOL_SIZE) list.add(bitmap); + synchronized (this) { + if (mPool.size() < POOL_SIZE) mPool.add(bitmap); } } - public static void clear() { - for (int i = 0; i < TYPE_COUNT; ++i) { - ArrayList list = sPools[i]; - synchronized (list) { - list.clear(); - } - } + public synchronized void clear() { + mPool.clear(); } - public static Bitmap decode(JobContext jc, int type, + public Bitmap decode(JobContext jc, byte[] data, int offset, int length, BitmapFactory.Options options) { if (options == null) options = new BitmapFactory.Options(); if (options.inSampleSize < 1) options.inSampleSize = 1; options.inPreferredConfig = Bitmap.Config.ARGB_8888; - options.inBitmap = (options.inSampleSize == 1) ? getBitmap(type) : null; + options.inBitmap = (options.inSampleSize == 1) ? getBitmap() : null; try { Bitmap bitmap = DecodeUtils.decode(jc, data, offset, length, options); if (options.inBitmap != null && options.inBitmap != bitmap) { - recycle(type, bitmap); + recycle(options.inBitmap); options.inBitmap = null; } return bitmap; @@ -80,7 +63,7 @@ public class BitmapPool { if (options.inBitmap == null) throw e; Log.w(TAG, "decode fail with a given bitmap, try decode to a new bitmap"); - recycle(type, options.inBitmap); + recycle(options.inBitmap); options.inBitmap = null; return DecodeUtils.decode(jc, data, offset, length, options); } @@ -88,16 +71,16 @@ public class BitmapPool { // This is the same as the method above except the source data comes // from a file descriptor instead of a byte array. - public static Bitmap decode(int type, - JobContext jc, FileDescriptor fileDescriptor, Options options) { + public Bitmap decode(JobContext jc, + FileDescriptor fileDescriptor, Options options) { if (options == null) options = new BitmapFactory.Options(); if (options.inSampleSize < 1) options.inSampleSize = 1; options.inPreferredConfig = Bitmap.Config.ARGB_8888; - options.inBitmap = (options.inSampleSize == 1) ? getBitmap(type) : null; + options.inBitmap = (options.inSampleSize == 1) ? getBitmap() : null; try { Bitmap bitmap = DecodeUtils.decode(jc, fileDescriptor, options); if (options.inBitmap != null&& options.inBitmap != bitmap) { - recycle(type, bitmap); + recycle(options.inBitmap); options.inBitmap = null; } return bitmap; @@ -105,7 +88,7 @@ public class BitmapPool { if (options.inBitmap == null) throw e; Log.w(TAG, "decode fail with a given bitmap, try decode to a new bitmap"); - recycle(type, options.inBitmap); + recycle(options.inBitmap); options.inBitmap = null; return DecodeUtils.decode(jc, fileDescriptor, options); } diff --git a/src/com/android/gallery3d/ui/GLRoot.java b/src/com/android/gallery3d/ui/GLRoot.java index 834e85c91..fe040ba34 100644 --- a/src/com/android/gallery3d/ui/GLRoot.java +++ b/src/com/android/gallery3d/ui/GLRoot.java @@ -20,8 +20,11 @@ import com.android.gallery3d.anim.CanvasAnimation; public interface GLRoot { + // Listener will be called when GL is idle AND before each frame. + // Mainly used for uploading textures. public static interface OnGLIdleListener { - public boolean onGLIdle(GLRoot root, GLCanvas canvas); + public boolean onGLIdle( + GLCanvas canvas, boolean renderRequested); } public void addOnGLIdleListener(OnGLIdleListener listener); diff --git a/src/com/android/gallery3d/ui/GLRootView.java b/src/com/android/gallery3d/ui/GLRootView.java index 51853fffa..bed2908c4 100644 --- a/src/com/android/gallery3d/ui/GLRootView.java +++ b/src/com/android/gallery3d/ui/GLRootView.java @@ -30,8 +30,8 @@ import com.android.gallery3d.common.Utils; import com.android.gallery3d.util.GalleryUtils; import com.android.gallery3d.util.Profile; +import java.util.ArrayDeque; import java.util.ArrayList; -import java.util.LinkedList; import java.util.concurrent.locks.ReentrantLock; import javax.microedition.khronos.egl.EGLConfig; @@ -82,8 +82,8 @@ public class GLRootView extends GLSurfaceView private final ArrayList mAnimations = new ArrayList(); - private final LinkedList mIdleListeners = - new LinkedList(); + private final ArrayDeque mIdleListeners = + new ArrayDeque(); private final IdleRunner mIdleRunner = new IdleRunner(); @@ -267,6 +267,7 @@ public class GLRootView extends GLSurfaceView } finally { mRenderLock.unlock(); } + if (DEBUG_PROFILE_SLOW_ONLY) { long t = System.nanoTime(); long durationInMs = (t - mLastDrawFinishTime) / 1000000; @@ -305,7 +306,6 @@ public class GLRootView extends GLSurfaceView gl.glScissor(clip.left, clip.top, clip.width(), clip.height()); } - if (mContentView != null) { mContentView.render(mCanvas); } @@ -323,9 +323,7 @@ public class GLRootView extends GLSurfaceView } synchronized (mIdleListeners) { - if (!mRenderRequested && !mIdleListeners.isEmpty()) { - mIdleRunner.enable(); - } + if (!mIdleListeners.isEmpty()) mIdleRunner.enable(); } if (DEBUG_INVALIDATE) { @@ -370,19 +368,18 @@ public class GLRootView extends GLSurfaceView OnGLIdleListener listener; synchronized (mIdleListeners) { mActive = false; - if (mRenderRequested) return; if (mIdleListeners.isEmpty()) return; listener = mIdleListeners.removeFirst(); } mRenderLock.lock(); try { - if (!listener.onGLIdle(GLRootView.this, mCanvas)) return; + if (!listener.onGLIdle(mCanvas, mRenderRequested)) return; } finally { mRenderLock.unlock(); } synchronized (mIdleListeners) { mIdleListeners.addLast(listener); - enable(); + if (!mRenderRequested) enable(); } } diff --git a/src/com/android/gallery3d/ui/SlotView.java b/src/com/android/gallery3d/ui/SlotView.java index aef97a436..5e12c74b4 100644 --- a/src/com/android/gallery3d/ui/SlotView.java +++ b/src/com/android/gallery3d/ui/SlotView.java @@ -84,6 +84,9 @@ public class SlotView extends GLView { public static final int OVERSCROLL_SYSTEM = 1; public static final int OVERSCROLL_NONE = 2; + // to prevent allocating memory + private final Rect mTempRect = new Rect(); + public SlotView(Context context, Spec spec) { mGestureDetector = new GestureDetector(context, new MyGestureListener()); @@ -105,7 +108,7 @@ public class SlotView extends GLView { if (index < 0 || index >= slotCount) { return; } - Rect rect = mLayout.getSlotRect(index); + Rect rect = mLayout.getSlotRect(index, mTempRect); int position = WIDE ? (rect.left + rect.right - getWidth()) / 2 : (rect.top + rect.bottom - getHeight()) / 2; @@ -113,7 +116,7 @@ public class SlotView extends GLView { } public void makeSlotVisible(int index) { - Rect rect = mLayout.getSlotRect(index); + Rect rect = mLayout.getSlotRect(index, mTempRect); int visibleBegin = WIDE ? mScrollX : mScrollY; int visibleLength = WIDE ? getWidth() : getHeight(); int visibleEnd = visibleBegin + visibleLength; @@ -198,7 +201,7 @@ public class SlotView extends GLView { } public Rect getSlotRect(int slotIndex) { - return mLayout.getSlotRect(slotIndex); + return mLayout.getSlotRect(slotIndex, new Rect()); } @Override @@ -315,7 +318,7 @@ public class SlotView extends GLView { private int renderItem( GLCanvas canvas, int index, int pass, boolean paperActive) { canvas.save(GLCanvas.SAVE_FLAG_ALPHA | GLCanvas.SAVE_FLAG_MATRIX); - Rect rect = getSlotRect(index); + Rect rect = mLayout.getSlotRect(index, mTempRect); if (paperActive) { canvas.multiplyMatrix(mPaper.getTransform(rect, mScrollX), 0); } else { @@ -443,7 +446,7 @@ public class SlotView extends GLView { return vPadding != mVerticalPadding || hPadding != mHorizontalPadding; } - public Rect getSlotRect(int index) { + public Rect getSlotRect(int index, Rect rect) { int col, row; if (WIDE) { col = index / mUnitCount; @@ -455,7 +458,8 @@ public class SlotView extends GLView { int x = mHorizontalPadding + col * (mSlotWidth + mSlotGap); int y = mVerticalPadding + row * (mSlotHeight + mSlotGap); - return new Rect(x, y, x + mSlotWidth, y + mSlotHeight); + rect.set(x, y, x + mSlotWidth, y + mSlotHeight); + return rect; } public int getSlotWidth() { diff --git a/src/com/android/gallery3d/ui/TextureUploader.java b/src/com/android/gallery3d/ui/TextureUploader.java new file mode 100644 index 000000000..a372eab70 --- /dev/null +++ b/src/com/android/gallery3d/ui/TextureUploader.java @@ -0,0 +1,97 @@ +/* + * 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.gallery3d.ui; + +import com.android.gallery3d.ui.GLRoot.OnGLIdleListener; + +import java.util.ArrayDeque; + +public class TextureUploader implements OnGLIdleListener { + private static final int INIT_CAPACITY = 64; + private static final int QUOTA_PER_FRAME = 1; + + private final ArrayDeque mFgTextures = + new ArrayDeque(INIT_CAPACITY); + private final ArrayDeque mBgTextures = + new ArrayDeque(INIT_CAPACITY); + private final GLRoot mGLRoot; + private transient boolean mIsQueued = false; + + public TextureUploader(GLRoot root) { + mGLRoot = root; + } + + public synchronized void clear() { + mFgTextures.clear(); + mBgTextures.clear(); + } + + // caller should hold synchronized on "this" + private void queueSelfIfNeed() { + if (mIsQueued) return; + mIsQueued = true; + mGLRoot.addOnGLIdleListener(this); + } + + public synchronized void addBgTexture(UploadedTexture t) { + mBgTextures.addLast(t); + queueSelfIfNeed(); + } + + public synchronized void addFgTexture(UploadedTexture t) { + mFgTextures.addLast(t); + queueSelfIfNeed(); + } + + private int upload(GLCanvas canvas, ArrayDeque deque, + int uploadQuota, boolean isBackground) { + while (uploadQuota > 0) { + UploadedTexture t; + synchronized (this) { + if (deque.isEmpty()) break; + t = deque.removeFirst(); + } + if (!t.isContentValid(canvas)) { + t.updateContent(canvas); + + // It will took some more time for a texture to be drawn for + // the first time. + // Thus, when scrolling, if a new column appears on screen, + // it may cause a UI jank even these textures are uploaded. + if (isBackground) t.draw(canvas, 0, 0); + --uploadQuota; + } + } + return uploadQuota; + } + + @Override + public boolean onGLIdle(GLCanvas canvas, boolean renderRequested) { + int uploadQuota = QUOTA_PER_FRAME; + uploadQuota = upload(canvas, mFgTextures, uploadQuota, false); + if (uploadQuota < QUOTA_PER_FRAME) mGLRoot.requestRender(); + + // don't upload background texture if there is pending render request + if (!renderRequested) { + upload(canvas, mBgTextures, uploadQuota, true); + } + synchronized (this) { + mIsQueued = !mFgTextures.isEmpty() || !mBgTextures.isEmpty(); + return mIsQueued; + } + } +} diff --git a/src/com/android/gallery3d/ui/TileImageView.java b/src/com/android/gallery3d/ui/TileImageView.java index d2ce1677a..610a34688 100644 --- a/src/com/android/gallery3d/ui/TileImageView.java +++ b/src/com/android/gallery3d/ui/TileImageView.java @@ -548,7 +548,8 @@ public class TileImageView extends GLView { AtomicBoolean mActive = new AtomicBoolean(false); @Override - public boolean onGLIdle(GLRoot root, GLCanvas canvas) { + public boolean onGLIdle(GLCanvas canvas, boolean renderRequested) { + if (renderRequested) return false; int quota = UPLOAD_LIMIT; Tile tile; while (true) { -- 2.11.0