Skip to content

Instantly share code, notes, and snippets.

@saantiaguilera
Created September 9, 2016 22:07
Show Gist options
  • Save saantiaguilera/22ab8d376c43ffafa9742415d0b561f7 to your computer and use it in GitHub Desktop.
Save saantiaguilera/22ab8d376c43ffafa9742415d0b561f7 to your computer and use it in GitHub Desktop.
Fresco picasso mode

Juanma tutorial for using Fresco as "Picasso"

La diferencia mas grande entre picasso y fresco es que (por si te preguntan):

Android <4.0 usa como vm Dalvik. De 4.0 a 4.4 esta Dalvik y ART (Beta). >5.0 ART.

Las mayor diferencia entre Dalvik y ART, es que dalvik tiene un solo heap uniforme y que, cada vez que corra el garbage collector, haltea TODOS los procesos para limpiar la memoria. Entonces en los celulares de dalvik, si empezas a usar mucha memoria y tiene que recolectarla, se te "freezea" el celular por unos ms.

Esto se nota demasiado en las listviews/gridviews/recyclerviews, ya que uno levanta muchisima memoria y se recolecta al toque, pero en dalvik esto haltea ;).

Asi que sacaron ART, ART tiene muchas mas ventajas, pero esas son las mas importantes para porque Fresco>Picasso. ART para ponerlo facil "hace el gc en otro thread aparte".

Si queres chusmearlo mas pone Dalvik vs ART o busca como funciona la memoria en android :)

La diferencia mas grande entre Fresco / Picasso es que Picasso corre todo sobre el heap, como las imagenes son bloques de memoria grandes por lo general, en Picasso en celulares con dalvik forzas al GC a correr muchisimo.

Fresco en cambio crea algo que se llama ASHMEM (anonymous shared memory) que es memoria nativa (en C++). Entonces tenes que hacer vos el malloc/realloc/free (lo hace fresco jaja). Esto se saltea al GC por estar en nativa haciendo que en Dalvik no estes GCeando como loco. En versiones ART (no dalvik) fresco funciona identico a picasso a bajo nivel.

Ahora a lo que queres:

1: Agrega fresco como dependencia

  compile "com.facebook.fresco:fresco:0.13.0"
  //Una buena practica es usar $frescoVersion y en gradle.properties agregar "frescoVersion = 0.13.0" asi no hay tanto hardcodeo ni numero magico

2: Crea una clase que herede de Application. Desde aca podes ver los lifecycles de las activities de tu aplicacion / los lifecycles de tu aplicacion / etc

Necesitamos inicializar fresco (a diferencia de picasso) ya que tiene que inicializar su pool nativa de memoria.

//Puedo tener errores de ortografia asi que esto "es masomenos asi" jajaj
public class CustomApplicaton extends Application {
  @Override
  public void onCreate() {
    super.onCreate();
    
    Fresco.initialize(this);
  }
}

(podes ver si queres como customizarlo, tipo para que en vez de usar el cliente de network de java use OkHttp). O leete por adentro la clase de Fresco (tenes un Fresco.initialize(this, configs) o podes buscar en la documentacion http://frescolib.org/docs/getting-started.html)

3: Teniendo ya Fresco inicializado, te paso una clase que hice yo para "Picassear" a Fresco

import android.content.ContentResolver;
import android.content.Context;
import android.graphics.drawable.Animatable;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.os.OperationCanceledException;

import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.drawee.controller.BaseControllerListener;
import com.facebook.drawee.interfaces.DraweeController;
import com.facebook.drawee.view.DraweeView;
import com.facebook.imagepipeline.common.ImageDecodeOptions;
import com.facebook.imagepipeline.common.ResizeOptions;
import com.facebook.imagepipeline.image.ImageInfo;
import com.facebook.imagepipeline.request.ImageRequestBuilder;
import com.facebook.imagepipeline.request.Postprocessor;

import java.io.File;
import java.lang.ref.WeakReference;

/**
 * Immutable utility class to load an image with a desired callback.
 *
 * Simple usage example;

 FrescoImageController.create(someContext)
 .load(uriToLoadImageFrom); //Supports uri/url/resId/file
 .listener(new FrescoImageController.Callback() {
 //Override the onSuccess and the onFailure and do what you want
 })
 //There are a lot more stuff, check it out
 .into(theView);

 *
 * Created by saguilera on 8/1/16.
 */
public class FrescoImageController {

    private WeakReference<Context> contextRef;

    private @NonNull WeakReference<? extends DraweeView> view;
    private final @NonNull FrescoControllerListener frescoCallback;
    private @NonNull Uri uri;

    private @Nullable ResizeOptions resizeOptions;
    private @Nullable ImageDecodeOptions decodeOptions;
    private @Nullable Postprocessor postprocessor;

    private boolean rotate;
    private boolean tapToRetry;
    private boolean progressiveRendering;
    private boolean localThumbnailPreview;

    private boolean noCache;
    private boolean noDiskCache;
    private boolean noMemoryCache;

    /**
     * Static method to create an empty builder. The same can be achieved by doing
     * new FrescoImageController.Builder();
     *
     * @return empty builder
     */
    public static Builder create(Context context) {
        return new Builder(context);
    }

    /**
     * Private constructor. Since its an immutable object, use builders.
     */
    private FrescoImageController(@NonNull WeakReference<Context> contextRef,
                                @NonNull final Uri uri, @NonNull DraweeView view,
                                @Nullable Callback callback,
                                @Nullable ResizeOptions resizeOpt, @Nullable ImageDecodeOptions decodeOpt,
                                @Nullable Postprocessor postprocessor,
                                boolean rotate, boolean ttr, boolean pr, boolean ltp,
                                boolean noCache, boolean noDiskCache, boolean noMemoryCache) {
        this.contextRef = contextRef;

        this.view = new WeakReference<>(view);
        this.uri = uri;

        this.resizeOptions = resizeOpt;
        this.decodeOptions = decodeOpt;
        this.postprocessor = postprocessor;

        this.rotate = rotate;
        this.tapToRetry = ttr;
        this.progressiveRendering = pr;
        this.localThumbnailPreview = ltp;

        this.noCache = noCache;
        this.noDiskCache = noDiskCache;
        this.noMemoryCache = noMemoryCache;

        frescoCallback = new FrescoControllerListener(callback);

        ImageRequestBuilder request = ImageRequestBuilder.newBuilderWithSource(uri)
            .setAutoRotateEnabled(rotate)
            .setLocalThumbnailPreviewsEnabled(localThumbnailPreview)
            .setProgressiveRenderingEnabled(progressiveRendering);

        if (noCache || noDiskCache) {
            request.disableDiskCache();
        }

        if (postprocessor != null) {
            request.setPostprocessor(postprocessor);
        }

        if (decodeOptions != null) {
            request.setImageDecodeOptions(decodeOptions);
        }

        if (resizeOptions != null) {
            request.setResizeOptions(resizeOptions);
        }

        DraweeController controller = Fresco.newDraweeControllerBuilder()
            .setUri(uri)
            .setImageRequest(request.build())
            .setTapToRetryEnabled(tapToRetry)
            .setOldController(view.getController())
            .setControllerListener(frescoCallback)
            .build();

        view.setController(controller);
    }

    /**
     * gets the attached view
     *
     * @return attached view or null if its already gced by the os
     */
    public @Nullable DraweeView getView() {
        return view.get();
    }

    /**
     * Getter
     */
    @Nullable
    public ResizeOptions getResizeOptions() {
        return resizeOptions;
    }

    /**
     * Getter
     */
    @Nullable
    public Postprocessor getPostprocessor() {
        return postprocessor;
    }

    /**
     * Getter
     */
    @Nullable
    public ImageDecodeOptions getDecodeOptions() {
        return decodeOptions;
    }

    /**
     * Getter
     */
    @NonNull
    public Uri getUri() {
        return uri;
    }

    /**
     * Getter
     */
    public boolean isCacheEnabled() {
        return !noCache;
    }

    /**
     * Getter
     */
    public boolean isMemoryCacheEnabled() {
        return !noMemoryCache;
    }

    /**
     * Getter
     */
    public boolean isDiskCacheEnabled() {
        return !noDiskCache;
    }

    /**
     * Getter
     */
    public boolean isLocalThumbnailPreviewEnabled() {
        return localThumbnailPreview;
    }

    /**
     * Getter
     */
    public boolean isProgressiveRenderingEnabled() {
        return progressiveRendering;
    }

    /**
     * Getter
     */
    public boolean isAutoRotateEnabled() {
        return rotate;
    }

    /**
     * Getter
     */
    public boolean isTapToRetryEnabled() {
        return tapToRetry;
    }

    /**
     * Perform an explicit success callback
     */
    public void success() {
        frescoCallback.success(null);
    }

    /**
     * Perform an explicit failure callback
     */
    public void failure() {
        frescoCallback.failure(new OperationCanceledException("Called failure explicitly from " + getClass().getSimpleName()));
    }

    /**
     * Create builder from state.
     *
     * Note this wont set the current view.
     *
     * @return new builder with current state
     */
    public @NonNull Builder newBuilder() {
        Builder builder = new Builder(contextRef.get())
            .load(getUri())
            .autoRotate(isAutoRotateEnabled())
            .tapToRetry(isTapToRetryEnabled())
            .progressiveRendering(isProgressiveRenderingEnabled())
            .localThumbnailPreview(isLocalThumbnailPreviewEnabled());

        if (!isCacheEnabled()) {
            builder.noCache();
        }

        if (!isDiskCacheEnabled()) {
            builder.noDiskCache();
        }

        if (!isMemoryCacheEnabled()) {
            builder.noMemoryCache();
        }

        if (getDecodeOptions() != null) {
            builder.decodeOptions(getDecodeOptions());
        }

        if (getResizeOptions() != null) {
            builder.resize(getResizeOptions().width, getResizeOptions().height);
        }

        if (getPostprocessor() != null) {
            builder.postprocessor(getPostprocessor());
        }

        if (frescoCallback.getCallback() != null) {
            builder.listener(frescoCallback.getCallback());
        }

        return builder;
    }

    /**
     * Builder class to create an immutable FrescoController
     */
    public static class Builder {

        private @NonNull WeakReference<Context> contextRef;

        private @Nullable Uri mUri = null;
        private @Nullable Callback listener = null;
        private @Nullable ResizeOptions resizeOptions = null;
        private boolean rotate = false;
        private boolean tapToRetry = false;
        private boolean progressiveRendering = false;
        private boolean localThumbnailPreview = false;
        private boolean noCache = false;
        private boolean noDiskCache = false;
        private boolean noMemoryCache = false;
        private @Nullable ImageDecodeOptions decodeOptions = null;
        private @Nullable Postprocessor postprocessor = null;

        /**
         * Constructor
         */
        public Builder(@NonNull Context context) {
            this.contextRef = new WeakReference<>(context);
        }

        /**
         * Set a resId from where the image will be loaded
         * @param resId with the id of the drawable to load
         * @return Builder
         */
        public Builder load(int resId) {
            this.mUri = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" +
                contextRef.get().getResources().getResourcePackageName(resId) + '/' +
                contextRef.get().getResources().getResourceTypeName(resId) + '/' +
                contextRef.get().getResources().getResourceEntryName(resId));
            return this;
        }

        /**
         * Set Uri from where the image will be loaded
         * @param uri with the address to download the image from
         * @return Builder
         */
        public Builder load(@NonNull Uri uri) {
            this.mUri = uri;
            return this;
        }

        /**
         * Set Url from where the image will be loaded
         * @param url with the address to download the image from
         * @return Builder
         */
        public Builder load(@NonNull String url) {
            this.mUri = Uri.parse(url);
            return this;
        }

        /**
         * Set file from where the image will be loaded
         * @param file with the image
         * @return Builder
         */
        public Builder load(@NonNull File file) {
            this.mUri = Uri.fromFile(file);
            return this;
        }

        /**
         * Set if you want to receive callbacks from the loading.
         *
         * @param listener from where callbacks will be observed
         * @return Builder
         */
        public Builder listener(@NonNull Callback listener) {
            this.listener = listener;
            return this;
        }

        /**
         * Resize the image before showing it.
         * Of course resize != scale.
         * For scaling just use the layout_width / layout_height of the view.
         *
         * Note: Currently fresco doesnt support png resizing. So take into account that if you will
         * implement this in a place with possible .png images (like local user files) you wont have
         * all images resized. This can lead to problems, eg if you have a jpg and a png image of
         * 16:9 and you resize to 1:1, you will only resize the squared one.
         * A possible solution for this, but please use it carefully is to use a postprocessor that
         * resizes the bitmap if its Uri is a .png, but be careful it can make the cache useless
         *
         * @param width dest to resize
         * @param height dest to resize
         * @return Builder
         */
        public Builder resize(int width, int height) {
            this.resizeOptions = new ResizeOptions(width, height);
            return this;
        }

        /**
         * Helper that autorotates the image depending on the exif value
         * Default: False
         *
         * @param should auto rotate
         * @return Builder
         */
        public Builder autoRotate(boolean should) {
            this.rotate = should;
            return this;
        }

        /**
         * Dont cache the image.
         * By default all images are cached
         *
         * @return Builder
         */
        public Builder noCache() {
            this.noCache = true;
            return this;
        }

        /**
         * Dont cache the image in disk.
         * By default all images are cached
         *
         * @return Builder
         */
        public Builder noDiskCache() {
            this.noDiskCache = true;
            return this;
        }

        /**
         * Dont cache the image in memory
         * By default all images are cached
         *
         * @return Builder
         */
        public Builder noMemoryCache() {
            this.noMemoryCache = true;
            return this;
        }

        /**
         * Tap on the image to retry loading it
         * Default: False
         *
         * @param should enable tap to retry
         * @return Builder
         */
        public Builder tapToRetry(boolean should) {
            this.tapToRetry = should;
            return this;
        }

        /**
         * Load the image while its rendering, this is useful if you want to show previews while its
         * rendering
         * Default: false
         *
         * @param should be enabled
         * @return Builder
         */
        public Builder progressiveRendering(boolean should) {
            this.progressiveRendering = should;
            return this;
        }

        /**
         * Show local thumbnail if present in the exif data
         * Default: false
         *
         * Fresco limitation:
         * This option is supported only for local URIs, and only for images in the JPEG format.
         *
         * @param should show it
         * @return builder
         */
        public Builder localThumbnailPreview(boolean should) {
            this.localThumbnailPreview = should;
            return this;
        }

        /**
         * Use a custom decode options. Create it with ImageDecodeOptionsBuilder.
         * Beware since this handles internal state information. Use at your own risk if needed.
         *
         * @param options for image decoding
         * @return Builder
         */
        public Builder decodeOptions(@NonNull ImageDecodeOptions options) {
            this.decodeOptions = options;
            return this;
        }

        /**
         * Since there are more than one postprocessor and processing methods (see
         * BasePostprocessor and BaseRepeatedPostprocessor) and there are three different
         * processing methods, you should feed the builder with the postprocessor instance already created
         * (instead of us defining a particular method and class for you to process the data)
         *
         * Note: DO NOT override more than one of the bitmap processing methods, this WILL lead to
         * undesired behaviours and is prone to errors
         *
         * Note: Fresco may (in a future, but currently it doesnt) support postprocessing
         * on animations.
         *
         * @param postprocessor instance for images
         * @return Builder
         */
        public Builder postprocessor(@NonNull Postprocessor postprocessor) {
            this.postprocessor = postprocessor;
            return this;
        }

        /**
         * Attach to a view.
         *
         * Note this will handle the loading of the uri. There MUST be (Mandatory) an existent Uri
         * from where to load the image.
         *
         * You can save the returned instance to retreive the data you have used or to explicitly
         * call the callbacks
         *
         * @param view to attach the desired args
         * @return Controller
         */
        public @NonNull FrescoImageController into(@NonNull DraweeView view) {
            if (mUri == null)
                throw new IllegalStateException("Creating controller for drawee with no address to retrieve image from. Forgot to call setUri/setUrl ??");

            return new FrescoImageController(contextRef,
                mUri, view,
                listener,
                resizeOptions, decodeOptions,
                postprocessor,
                rotate, tapToRetry, progressiveRendering, localThumbnailPreview,
                noCache, noDiskCache, noMemoryCache);
        }

    }

    /**
     * Interface from where callbacks will be dispatched
     */
    public interface Callback {
        void onSuccess(@Nullable ImageInfo imageInfo);
        void onFailure(@NonNull Throwable t);
    }

    private class FrescoControllerListener extends BaseControllerListener<ImageInfo> {

        private @Nullable Callback callback;

        public FrescoControllerListener(@Nullable Callback callback) {
            this.callback = callback;
        }

        @Nullable
        public Callback getCallback() {
            return callback;
        }

        public void success(@Nullable ImageInfo imageInfo) {
            if (callback != null) {
                callback.onSuccess(imageInfo);
            }

            if (!isCacheEnabled() || !isMemoryCacheEnabled()) {
                Fresco.getImagePipeline().evictFromCache(getUri());
            }
        }

        public void failure(Throwable throwable) {
            if (callback != null) {
                callback.onFailure(throwable);
            }
        }

        @Override
        public void onFinalImageSet(String id, ImageInfo imageInfo, Animatable animatable) {
            super.onFinalImageSet(id, imageInfo, animatable);
            success(imageInfo);
        }

        @Override
        public void onFailure(String id, Throwable throwable) {
            super.onFailure(id, throwable);
            failure(throwable);
        }
    }

}

Creo que es bastante entendible jajaj, sino un ejemplo para usarla seria:

FrescoImageController.create(context)
  .load(url)
  .listener(callback)
  .resize(200, 200)
  .into(imageView);

Fijate en la docu de fresco, pero a diferencia de picasso no vas a tener que usar ImageView sino que SimpleDraweeView (una clase de la libreria). Si queres que la imagen tenga ponele una imagen de placeholder antes, o que la imagen este centrada totalmente y todo eso, todo eso lo haces desde el xml o via codigo con: (van todos los posibles)

<com.facebook.drawee.view.SimpleDraweeView
  fresco:fadeDuration="300"
  fresco:actualImageScaleType="focusCrop"
  fresco:placeholderImage="@color/wait_color"
  fresco:placeholderImageScaleType="fitCenter"
  fresco:failureImage="@drawable/error"
  fresco:failureImageScaleType="centerInside"
  fresco:retryImage="@drawable/retrying"
  fresco:retryImageScaleType="centerCrop"
  fresco:progressBarImage="@drawable/progress_bar"
  fresco:progressBarImageScaleType="centerInside"
  fresco:progressBarAutoRotateInterval="1000"
  fresco:backgroundImage="@color/blue"
  fresco:overlayImage="@drawable/watermark"
  fresco:pressedStateOverlayImage="@color/red"
  fresco:roundAsCircle="false"
  fresco:roundedCornerRadius="1dp"
  fresco:roundTopLeft="true"
  fresco:roundTopRight="false"
  fresco:roundBottomLeft="false"
  fresco:roundBottomRight="true"
  fresco:roundWithOverlayColor="@color/corner_color"
  fresco:roundingBorderWidth="2dp"
  fresco:roundingBorderColor="@color/border_color" />

Te va a decir que no conoce el attrs fresco:, por si todavia no laburaste con styles, solo agregale al root de tu xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:fresco="http://schemas.android.com/apk/res-auto"
    ... />

Y creo que con eso estas (?)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment