/* * @copyright 2012 Philip Warner * @license GNU General Public License * * This file is part of Book Catalogue. * * Book Catalogue is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Book Catalogue is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Book Catalogue. If not, see <http://www.gnu.org/licenses/>. */ package com.eleybourn.bookcatalogue.utils; import android.util.SparseArray; import android.view.View; /** * Using View.setTag(int, Object) causes a memory leak if the tag refers, by a strong reference chain, * to the view itself (ie. it uses the 'Holder' pattern). This bug is documented here: * * http://code.google.com/p/android/issues/detail?id=18273 * * It seems that an 'interesting' design choice was made to use the view itself as a weak key to the into * another collection, which then causes the views to never be GC'd. * * The work-around is to *not* use strong refs, or use setTag(Object). But we use multiple tags. * * So this class implements setTag(int, Object) in a non-leaky fashion and is designed to be stored * in the tag of a view. * * @author Philip Warner */ public class ViewTagger { /** Stores the basic tag referred to without an ID */ public Object mBareTag = null; public SparseArray<Object> mTags = null; /** * Internal static method to get (and optionally create) a ViewTagger object * on tha passed view. * * @param v View with tag * @param autoCreate Indicates if tagger should be created if not present * * @return ViewTagger object */ private static ViewTagger getTagger(View v, boolean autoCreate) { // See if we have one already Object o = v.getTag(); ViewTagger tagger = null; if (o == null) { // Create if requested if (autoCreate) { tagger = new ViewTagger(); v.setTag(tagger); } } else { // Make sure it's a valid object type if ( ! (o instanceof ViewTagger) ) throw new RuntimeException("View already has a tag that is not a ViewTagger"); tagger = (ViewTagger) o; } return tagger; } /** * Static method to get the bare tag from the view. * * @param v View from which to retrieve tag * @return */ public static Object getTag(View v) { ViewTagger tagger = getTagger(v, false); if (tagger == null) return null; return tagger.get(); } /** * Static method to get the tag matching the ID from the view * * @param v View from which to retrieve tag * @param key Key of required tag * * @return Object with specified tag */ @SuppressWarnings("unchecked") public static<T> T getTag(View v, int key) { ViewTagger tagger = getTagger(v, false); if (tagger == null) return null; return (T)tagger.get(key); } /** * Static method to set the bare tag on the view * * @param v View from which to retrieve tag * @param value Object to store at specified tag */ public static void setTag(View v, Object value) { getTagger(v, true).set(value); } /** * Static method to set the tag matching the ID on the view * * @param v View from which to retrieve tag * @param key Key of tag to store * @param value Object to store at specified tag */ public static void setTag(View v, int key, Object value) { getTagger(v, true).set(key, value); } /** * Instance method to set the bare tag * * @param value Value of id-less tag */ public void set(Object value) { mBareTag = value; } /** * Instance method to set the specified tag value * * @param key Key of new tag * @param value Object to store at specified tag */ public void set(int key, Object value) { synchronized(this) { if (mTags == null) mTags = new SparseArray<Object>(); mTags.put(key, value); } } /** * Instance method to get the bare tag * * @return The bare tag object */ public Object get() { return mBareTag; } /** * Instance method to get the specified tag * * @param key Key of object to retrieve * * @return Object at specified key */ public Object get(int key) { synchronized(this) { if (mTags == null) return null; return mTags.get(key); } } }