/* * Copyright (C) 2016 eschao <esc.chao@gmail.com> * * 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.eschao.android.widget.sample.pageflip; import android.content.Context; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Matrix; import android.util.Log; import java.util.LinkedList; import java.util.Random; /** * A singleton thread task to load bitmap * <p>Attempt to load bitmap in separate thread to get better performance.</p> * * @author eschao */ public final class LoadBitmapTask implements Runnable { private final static String TAG = "LoadBitmapTask"; private static LoadBitmapTask __object; final static int SMALL_BG = 0; final static int MEDIUM_BG = 1; final static int LARGE_BG = 2; final static int BG_COUNT = 10; int mBGSizeIndex; int mQueueMaxSize; int mPreRandomNo; boolean mIsLandscape; boolean mStop; Random mBGRandom; Resources mResources; Thread mThread; LinkedList<Bitmap> mQueue; int[][] mPortraitBGs; /** * Get an unique task object * * @param context Android context * @return unique task object */ public static LoadBitmapTask get(Context context) { if (__object == null) { __object = new LoadBitmapTask(context); } return __object; } /** * Constructor * * @param context Android context */ private LoadBitmapTask(Context context) { mResources = context.getResources(); mBGRandom = new Random(); mBGSizeIndex = SMALL_BG; mStop = false; mThread = null; mPreRandomNo = 0; mIsLandscape = false; mQueueMaxSize = 1; mQueue = new LinkedList<Bitmap>(); // init all available bitmaps mPortraitBGs = new int[][] { new int[] {R.drawable.p1_480, R.drawable.p2_480, R.drawable.p3_480, R.drawable.p4_480, R.drawable.p5_480, R.drawable.p6_480, R.drawable.p7_480, R.drawable.p8_480, R.drawable.p9_480, R.drawable.p10_480}, new int[] {R.drawable.p1_720, R.drawable.p2_720, R.drawable.p3_720, R.drawable.p4_720, R.drawable.p5_720, R.drawable.p6_720, R.drawable.p7_720, R.drawable.p8_720, R.drawable.p9_720, R.drawable.p10_720}, new int[] {R.drawable.p1_1080, R.drawable.p2_1080, R.drawable.p3_1080, R.drawable.p4_1080, R.drawable.p5_1080, R.drawable.p6_1080, R.drawable.p7_1080, R.drawable.p8_1080, R.drawable.p9_1080, R.drawable.p10_1080} }; } /** * Acquire a bitmap to show * <p>If there is no cached bitmap, it will load one immediately</p> * * @return bitmap */ public Bitmap getBitmap() { Bitmap b = null; synchronized (this) { if (mQueue.size() > 0) { b = mQueue.pop(); } notify(); } if (b == null) { Log.d(TAG, "Load bitmap instantly!"); b = getRandomBitmap(); } return b; } /** * Is task running? * * @return true if task is running */ public boolean isRunning() { return mThread != null && mThread.isAlive(); } /** * Start task */ public synchronized void start() { if (mThread == null || !mThread.isAlive()) { mStop = false; mThread = new Thread(this); mThread.start(); } } /** * Stop task * <p>Set mStop flag with true and notify task thread, at last, it will * check if task is alive every 500ms with 3 times to make sure the thread * stop</p> */ public void stop() { synchronized (this) { mStop = true; notify(); } // wait for thread stopping for (int i = 0; i < 3 && mThread.isAlive(); ++i) { Log.d(TAG, "Waiting thread to stop ..."); try { Thread.sleep(500); } catch (InterruptedException e) { } } if (mThread.isAlive()) { Log.d(TAG, "Thread is still alive after waited 1.5s!"); } } /** * Set bitmap width , height and maximum size of cache queue * * @param w width of bitmap * @param h height of bitmap * @param maxCached maximum size of cache queue */ public void set(int w, int h, int maxCached) { int newIndex = LARGE_BG; if ((w <= 480 && h <= 854) || (w <= 854 && h <= 480)) { mBGSizeIndex = SMALL_BG; } else if ((w <= 800 && h <= 1280) || (h <= 800 && w <= 1280)) { mBGSizeIndex = MEDIUM_BG; } mIsLandscape = w > h; if (maxCached != mQueueMaxSize) { mQueueMaxSize = maxCached; } if (newIndex != mBGSizeIndex) { mBGSizeIndex = newIndex; synchronized (this) { cleanQueue(); notify(); } } } /** * Load bitmap from resources randomly * * @return bitmap object */ private Bitmap getRandomBitmap() { int newNo = mPreRandomNo; while (newNo == mPreRandomNo) { newNo = mBGRandom.nextInt(BG_COUNT); } mPreRandomNo = newNo; int resId = mPortraitBGs[mBGSizeIndex][newNo]; Bitmap b = BitmapFactory.decodeResource(mResources, resId); if (mIsLandscape) { Matrix matrix = new Matrix(); matrix.postRotate(90); Bitmap lb = Bitmap.createBitmap(b, 0, 0, b.getWidth(), b.getHeight(), matrix, true); b.recycle(); return lb; } return b; } /** * Clear cache queue */ private void cleanQueue() { for (int i = 0; i < mQueue.size(); ++i) { mQueue.get(i).recycle(); } mQueue.clear(); } public void run() { while (true) { synchronized (this) { // check if ask thread stopping if (mStop) { cleanQueue(); break; } // load bitmap only when no cached bitmap in queue int size = mQueue.size(); if (size < 1) { for (int i = 0; i < mQueueMaxSize; ++i) { Log.d(TAG, "Load Queue:" + i + " in background!"); mQueue.push(getRandomBitmap()); } } // wait to be awaken try { wait(); } catch (InterruptedException e) { } } } } }