A DataSource is, like a Java Future, the result of an asynchronous computation. The different is that, unlike a Future, a DataSource can return you a whole series of results from a single command, not just one.

After submitting an image request, the image pipeline returns a data source. To get a result out if it, you need to use a DataSubscriber.

Executors

When subscribing to a data source, an executor must be provided. The purpose of executors is to execute runnables (in our case the subscriber callback methods) on a specific thread and with specific policy. Fresco provides several executors and one should carefully choose which one to be used:

  • If you need to do any UI stuff from your callback (accessing views, drawables, etc.), you must use UiThreadImmediateExecutorService.getInstance(). Android view system is not thread safe and is only to be accessed from the main thread (the UI thread).
  • If the callback is lightweight, and does not do any UI related stuff, you can simply use CallerThreadExecutor.getInstance(). This executor executes runnables on the caller’s thread. Depending on what is the calling thread, callback may be executed either on the UI or a background thread. There are no guarantees which thread it is going to be and because of that this executor should be used with great caution. And again, only for lightweight non-UI related stuff.
  • If you need to do some expensive non-UI related work (database access, disk read/write, or any other slow operation), this should NOT be done either with CallerThreadExecutor nor with the UiThreadExecutorService, but with one of the background thread executors. See DefaultExecutorSupplier.forBackgroundTasks for an example implementation.

Getting result from a data source

This is a generic example of how to get a result from a data source of CloseableReference<T> for arbitrary type T. The result is valid only in the scope of the onNewResultImpl callback. As soon as the callback gets executed, the result is no longer valid. See the next example if the result needs to be kept around.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
    DataSource<CloseableReference<T>> dataSource = ...;

    DataSubscriber<CloseableReference<T>> dataSubscriber =
        new BaseDataSubscriber<CloseableReference<T>>() {
          @Override
          protected void onNewResultImpl(
              DataSource<CloseableReference<T>> dataSource) {
            if (!dataSource.isFinished()) {
              // if we are not interested in the intermediate images,
              // we can just return here.
              return;
            }
            CloseableReference<T> ref = dataSource.getResult();
            if (ref != null) {
              try {
                // do something with the result
                T result = ref.get();
                ...
              } finally {
                CloseableReference.closeSafely(ref);
              }
            }
          }

          @Override
          protected void onFailureImpl(DataSource<CloseableReference<T>> dataSource) {
            Throwable t = dataSource.getFailureCause();
            // handle failure
          }
        };

    dataSource.subscribe(dataSubscriber, executor);

Keeping result from a data source

The above example closes the reference as soon as the callback gets executed. If the result needs to be kept around, you must keep the corresponding CloseableReference for as long as the result is needed. This can be done as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
    DataSource<CloseableReference<T>> dataSource = ...;

    DataSubscriber<CloseableReference<T>> dataSubscriber =
        new BaseDataSubscriber<CloseableReference<T>>() {
          @Override
          protected void onNewResultImpl(
              DataSource<CloseableReference<T>> dataSource) {
            if (!dataSource.isFinished()) {
              // if we are not interested in the intermediate images,
              // we can just return here.
              return;
            }
            // keep the closeable reference
            mRef = dataSource.getResult();
            // do something with the result
            T result = mRef.get();
            ...
          }

          @Override
          protected void onFailureImpl(DataSource<CloseableReference<T>> dataSource) {
            Throwable t = dataSource.getFailureCause();
            // handle failure
          }
        };

    dataSource.subscribe(dataSubscriber, executor);

IMPORTANT: once you don’t need the result anymore, you must close the reference. Not doing so may cause memory leaks. See closeable references for more details.

1
2
    CloseableReference.closeSafely(mRef);
    mRef = null;

However, if you are using BaseDataSubscriber you do not have to manually close the dataSource (closing mRef is enough). BaseDataSubscriber automatically closes the dataSource for you right after onNewResultImpl is called. If you are not using BaseDataSubscriber (e.g. if you’re calling dataSource.getResult()), make sure to close the dataSource as well.

To get encoded image…

1
2
    DataSource<CloseableReference<PooledByteBuffer>> dataSource =
        mImagePipeline.fetchEncodedImage(imageRequest, CALLER_CONTEXT);

Image pipeline uses PooledByteBuffer for encoded images. This is our T in the above examples. Here is an example of creating an InputStream out of PooledByteBuffer so that we can read the image bytes:

1
2
3
4
5
6
7
8
9
10
11
      InputStream is = new PooledByteBufferInputStream(result);
      try {
        // Example: get the image format
        ImageFormat imageFormat = ImageFormatChecker.getImageFormat(is);
        // Example: write input stream to a file
        Files.copy(is, path);
      } catch (...) {
        ...
      } finally {
        Closeables.closeQuietly(is);
      }

To get decoded image…

1
2
DataSource<CloseableReference<CloseableImage>>
    dataSource = imagePipeline.fetchDecodedImage(imageRequest, callerContext);

Image pipeline uses CloseableImage for decoded images. This is our T in the above examples. Here is an example of getting a Bitmap out of CloseableImage:

1
2
3
4
5
6
    CloseableImage image = ref.get();
    if (image instanceof CloseableBitmap) {
      // do something with the bitmap
      Bitmap bitmap = (CloseableBitmap image).getUnderlyingBitmap();
      ...
    }

I just want a bitmap…

If your request to the pipeline is for a single Bitmap, you can take advantage of our easier-to-use BaseBitmapDataSubscriber:

1
2
3
4
5
6
7
8
9
10
11
12
13
dataSource.subscribe(new BaseBitmapDataSubscriber() {
    @Override
    public void onNewResultImpl(@Nullable Bitmap bitmap) {
      // You can use the bitmap here, but in limited ways.
      // No need to do any cleanup.
    }

    @Override
    public void onFailureImpl(DataSource dataSource) {
      // No cleanup required here.
    }
  },
  executor);

A snap to use, right? There are caveats.

This subscriber doesn’t work for animated images as those can not be represented as a single bitmap.

You can not assign the bitmap to any variable not in the scope of the onNewResultImpl method. The reason is, as already explained in the above examples that, after the subscriber has finished executing, the image pipeline will recycle the bitmap and free its memory. If you try to draw the bitmap after that, your app will crash with an IllegalStateException.

You can still safely pass the Bitmap to an Android notification or remote view. If Android needs your Bitmap in order to pass it to a system process, it makes a copy of the Bitmap data in ashmem - the same heap used by Fresco. So Fresco’s automatic cleanup will work without issue.

If those requirements prevent you from using BaseBitmapDataSubscriber, you can go with a more generic approach as explained above.