Home | History | Annotate | Download | only in threadsample
      1 /*
      2  * Copyright (C) 2012 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.example.android.threadsample;
     18 
     19 import android.content.Context;
     20 import android.content.res.TypedArray;
     21 import android.graphics.Bitmap;
     22 import android.graphics.Canvas;
     23 import android.graphics.drawable.Drawable;
     24 import android.net.Uri;
     25 import android.util.AttributeSet;
     26 import android.view.View;
     27 import android.widget.ImageView;
     28 
     29 
     30 import java.lang.ref.WeakReference;
     31 import java.net.URL;
     32 
     33 /**
     34  * This class extends the standard Android ImageView View class with some features
     35  * that are useful for downloading, decoding, and displaying Picasa images.
     36  *
     37  */
     38 public class PhotoView extends ImageView {
     39 
     40     // Indicates if caching should be used
     41     private boolean mCacheFlag;
     42 
     43     // Status flag that indicates if onDraw has completed
     44     private boolean mIsDrawn;
     45 
     46     /*
     47      * Creates a weak reference to the ImageView in this object. The weak
     48      * reference prevents memory leaks and crashes, because it automatically tracks the "state" of
     49      * the variable it backs. If the reference becomes invalid, the weak reference is garbage-
     50      * collected.
     51      * This technique is important for referring to objects that are part of a component lifecycle.
     52      * Using a hard reference may cause memory leaks as the value continues to change; even worse,
     53      * it can cause crashes if the underlying component is destroyed. Using a weak reference to
     54      * a View ensures that the reference is more transitory in nature.
     55      */
     56     private WeakReference<View> mThisView;
     57 
     58     // Contains the ID of the internal View
     59     private int mHideShowResId = -1;
     60 
     61     // The URL that points to the source of the image for this ImageView
     62     private URL mImageURL;
     63 
     64     // The Thread that will be used to download the image for this ImageView
     65     private PhotoTask mDownloadThread;
     66 
     67     /**
     68      * Creates an ImageDownloadView with no settings
     69      * @param context A context for the View
     70      */
     71     public PhotoView(Context context) {
     72         super(context);
     73     }
     74 
     75     /**
     76      * Creates an ImageDownloadView and gets attribute values
     77      * @param context A Context to use with the View
     78      * @param attributeSet The entire set of attributes for the View
     79      */
     80     public PhotoView(Context context, AttributeSet attributeSet) {
     81         super(context, attributeSet);
     82 
     83         // Gets attributes associated with the attribute set
     84         getAttributes(attributeSet);
     85     }
     86 
     87     /**
     88      * Creates an ImageDownloadView, gets attribute values, and applies a default style
     89      * @param context A context for the View
     90      * @param attributeSet The entire set of attributes for the View
     91      * @param defaultStyle The default style to use with the View
     92      */
     93     public PhotoView(Context context, AttributeSet attributeSet, int defaultStyle) {
     94         super(context, attributeSet, defaultStyle);
     95 
     96         // Gets attributes associated with the attribute set
     97         getAttributes(attributeSet);
     98     }
     99 
    100     /**
    101      * Gets the resource ID for the hideShowSibling resource
    102      * @param attributeSet The entire set of attributes for the View
    103      */
    104     private void getAttributes(AttributeSet attributeSet) {
    105 
    106         // Gets an array of attributes for the View
    107         TypedArray attributes =
    108                 getContext().obtainStyledAttributes(attributeSet, R.styleable.ImageDownloaderView);
    109 
    110         // Gets the resource Id of the View to hide or show
    111         mHideShowResId =
    112                 attributes.getResourceId(R.styleable.ImageDownloaderView_hideShowSibling, -1);
    113 
    114         // Returns the array for re-use
    115         attributes.recycle();
    116     }
    117 
    118     /**
    119      * Sets the visibility of the PhotoView
    120      * @param visState The visibility state (see View.setVisibility)
    121      */
    122     private void showView(int visState) {
    123         // If the View contains something
    124         if (mThisView != null) {
    125 
    126             // Gets a local hard reference to the View
    127             View localView = mThisView.get();
    128 
    129             // If the weak reference actually contains something, set the visibility
    130             if (localView != null)
    131                 localView.setVisibility(visState);
    132         }
    133     }
    134 
    135     /**
    136      * Sets the image in this ImageView to null, and makes the View visible
    137      */
    138     public void clearImage() {
    139         setImageDrawable(null);
    140         showView(View.VISIBLE);
    141     }
    142 
    143     /**
    144      * Returns the URL of the picture associated with this ImageView
    145      * @return a URL
    146      */
    147     final URL getLocation() {
    148         return mImageURL;
    149     }
    150 
    151     /*
    152      * This callback is invoked when the system attaches the ImageView to a Window. The callback
    153      * is invoked before onDraw(), but may be invoked after onMeasure()
    154      */
    155     @Override
    156     protected void onAttachedToWindow() {
    157         // Always call the supermethod first
    158         super.onAttachedToWindow();
    159 
    160         // If the sibling View is set and the parent of the ImageView is itself a View
    161         if ((this.mHideShowResId != -1) && ((getParent() instanceof View))) {
    162 
    163             // Gets a handle to the sibling View
    164             View localView = ((View) getParent()).findViewById(this.mHideShowResId);
    165 
    166             // If the sibling View contains something, make it the weak reference for this View
    167             if (localView != null) {
    168                 this.mThisView = new WeakReference<View>(localView);
    169             }
    170         }
    171     }
    172 
    173     /*
    174      * This callback is invoked when the ImageView is removed from a Window. It "unsets" variables
    175      * to prevent memory leaks.
    176      */
    177     @Override
    178     protected void onDetachedFromWindow() {
    179 
    180         // Clears out the image drawable, turns off the cache, disconnects the view from a URL
    181         setImageURL(null, false, null);
    182 
    183         // Gets the current Drawable, or null if no Drawable is attached
    184         Drawable localDrawable = getDrawable();
    185 
    186         // if the Drawable is null, unbind it from this VIew
    187         if (localDrawable != null)
    188             localDrawable.setCallback(null);
    189 
    190         // If this View still exists, clears the weak reference, then sets the reference to null
    191         if (mThisView != null) {
    192             mThisView.clear();
    193             mThisView = null;
    194         }
    195 
    196         // Sets the downloader thread to null
    197         this.mDownloadThread = null;
    198 
    199         // Always call the super method last
    200         super.onDetachedFromWindow();
    201     }
    202 
    203     /*
    204      * This callback is invoked when the system tells the View to draw itself. If the View isn't
    205      * already drawn, and its URL isn't null, it invokes a Thread to download the image. Otherwise,
    206      * it simply passes the existing Canvas to the super method
    207      */
    208     @Override
    209     protected void onDraw(Canvas canvas) {
    210         // If the image isn't already drawn, and the URL is set
    211         if ((!mIsDrawn) && (mImageURL != null)) {
    212 
    213             // Starts downloading this View, using the current cache setting
    214             mDownloadThread = PhotoManager.startDownload(this, mCacheFlag);
    215 
    216             // After successfully downloading the image, this marks that it's available.
    217             mIsDrawn = true;
    218         }
    219         // Always call the super method last
    220         super.onDraw(canvas);
    221     }
    222 
    223     /**
    224      * Sets the current View weak reference to be the incoming View. See the definition of
    225      * mThisView
    226      * @param view the View to use as the new WeakReference
    227      */
    228     public void setHideView(View view) {
    229         this.mThisView = new WeakReference<View>(view);
    230     }
    231 
    232     @Override
    233     public void setImageBitmap(Bitmap paramBitmap) {
    234         super.setImageBitmap(paramBitmap);
    235     }
    236 
    237     @Override
    238     public void setImageDrawable(Drawable drawable) {
    239         // The visibility of the View
    240         int viewState;
    241 
    242         /*
    243          * Sets the View state to visible if the method is called with a null argument (the
    244          * image is being cleared). Otherwise, sets the View state to invisible before refreshing
    245          * it.
    246          */
    247         if (drawable == null) {
    248 
    249             viewState = View.VISIBLE;
    250         } else {
    251 
    252             viewState = View.INVISIBLE;
    253         }
    254         // Either hides or shows the View, depending on the view state
    255         showView(viewState);
    256 
    257         // Invokes the supermethod with the provided drawable
    258         super.setImageDrawable(drawable);
    259     }
    260 
    261     /*
    262      * Displays a drawable in the View
    263      */
    264     @Override
    265     public void setImageResource(int resId) {
    266         super.setImageResource(resId);
    267     }
    268 
    269     /*
    270      * Sets the URI for the Image
    271      */
    272     @Override
    273     public void setImageURI(Uri uri) {
    274         super.setImageURI(uri);
    275     }
    276 
    277     /**
    278      * Attempts to set the picture URL for this ImageView and then download the picture.
    279      * <p>
    280      * If the picture URL for this view is already set, and the input URL is not the same as the
    281      * stored URL, then the picture has moved and any existing downloads are stopped.
    282      * <p>
    283      * If the input URL is the same as the stored URL, then nothing needs to be done.
    284      * <p>
    285      * If the stored URL is null, then this method starts a download and decode of the picture
    286      * @param pictureURL An incoming URL for a Picasa picture
    287      * @param cacheFlag Whether to use caching when doing downloading and decoding
    288      * @param imageDrawable The Drawable to use for this ImageView
    289      */
    290     public void setImageURL(URL pictureURL, boolean cacheFlag, Drawable imageDrawable) {
    291         // If the picture URL for this ImageView is already set
    292         if (mImageURL != null) {
    293 
    294             // If the stored URL doesn't match the incoming URL, then the picture has changed.
    295             if (!mImageURL.equals(pictureURL)) {
    296 
    297                 // Stops any ongoing downloads for this ImageView
    298                 PhotoManager.removeDownload(mDownloadThread, mImageURL);
    299             } else {
    300 
    301                 // The stored URL matches the incoming URL. Returns without doing any work.
    302                 return;
    303             }
    304         }
    305 
    306         // Sets the Drawable for this ImageView
    307         setImageDrawable(imageDrawable);
    308 
    309         // Stores the picture URL for this ImageView
    310         mImageURL = pictureURL;
    311 
    312         // If the draw operation for this ImageVIew has completed, and the picture URL isn't empty
    313         if ((mIsDrawn) && (pictureURL != null)) {
    314 
    315             // Sets the cache flag
    316             mCacheFlag = cacheFlag;
    317 
    318             /*
    319              * Starts a download of the picture file. Notice that if caching is on, the picture
    320              * file's contents may be taken from the cache.
    321              */
    322             mDownloadThread = PhotoManager.startDownload(this, cacheFlag);
    323         }
    324     }
    325 
    326     /**
    327      * Sets the Drawable for this ImageView
    328      * @param drawable A Drawable to use for the ImageView
    329      */
    330     public void setStatusDrawable(Drawable drawable) {
    331 
    332         // If the View is empty, sets a Drawable as its content
    333         if (mThisView == null) {
    334             setImageDrawable(drawable);
    335         }
    336     }
    337 
    338     /**
    339      * Sets the content of this ImageView to be a Drawable resource
    340      * @param resId
    341      */
    342     public void setStatusResource(int resId) {
    343 
    344         // If the View is empty, provides it with a Drawable resource as its content
    345         if (mThisView == null) {
    346             setImageResource(resId);
    347         }
    348     }
    349 }
    350