facebook/fresco

SimpleDraweeView black filckering on replacement image

Closed this issue · 13 comments

I am trying to replace mutiples images (from local storage) using the same SimpleDraweeView. How avoid the black flickering?

After a while the black flickering disappear and works well.

See the video: http://sendvid.com/q92ry52l

I am only doing: mSimpleDraweeView.setImageURI("file://" + imagesList.get(index));

I am using 'com.facebook.fresco:fresco:0.13.0'

Thanks

Yeah, unfortunately this is a known issue. If you set a new URI, you'll see the placeholder image until the new image is ready. This is especially noticeable when you have large images and you switch them a lot (like in your example).

There is a PR that solves this, #1328, but we did not yet include it in Fresco. You could try and see if this would solve the issue.

Thanks, I need to fork de repo and apply the pull request locally to test right?

You don't need to fork. The DraweeController already supports setting a DataSourceSupplier , so you should be able to just copy paste the code from RetainingDataSourceSupplier in your project that uses Fresco and then use it like shown in MainActivity.java of the pull request.

Which also means that if this works for you, you can just use it until we've officially included it in Fresco :)

Yes, I tried that, but when I write this

 mSimpleDraweeView.setController(
                    Fresco.newDraweeControllerBuilder()
                            .setDataSourceSupplier(retainingSupplier)
                            .build());

The method .build() does not exists

Yes, if you take a look at the PR, setDataSourceSupplier does not return the builder.

You have to do

PipelineDraweeControllerBuilder builder = Fresco.newDraweeControllerBuilder()...
builder.setDataSourceSupplier();
setController(builder.build());

Works like a charm! Many thanks!

A new video with the fix: http://sendvid.com/wpv678su

All the best

Perfect, good to know that it works! Let us know if you experience some issues with this. We'll also work on including this directly into Fresco.

Anyone can share some sample code based on setDataSourceSupplier...

Take a look at #1328 @karun2189

import com.facebook.common.executors.CallerThreadExecutor;
import com.facebook.common.internal.Supplier;
import com.facebook.datasource.AbstractDataSource;
import com.facebook.datasource.DataSource;
import com.facebook.datasource.DataSubscriber;

import java.util.Collections;
import java.util.Set;
import java.util.WeakHashMap;

import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.NotThreadSafe;
import javax.annotation.concurrent.ThreadSafe;

@NotThreadSafe
public class RetainingDataSourceSupplier<T> implements Supplier<DataSource<T>> {

    private final Set<RetainingDataSource> mDataSources =
            Collections.newSetFromMap(new WeakHashMap<RetainingDataSource, Boolean>());

    private Supplier<DataSource<T>> mCurrentDataSourceSupplier = null;

    @Override
    public DataSource<T> get() {
        RetainingDataSource dataSource = new RetainingDataSource();
        dataSource.setSupplier(mCurrentDataSourceSupplier);
        mDataSources.add(dataSource);
        return dataSource;
    }

    public void setSupplier(Supplier<DataSource<T>> supplier) {
        mCurrentDataSourceSupplier = supplier;
        for (RetainingDataSource dataSource: mDataSources) {
            dataSource.setSupplier(supplier);
        }
    }

    @ThreadSafe
    private class RetainingDataSource extends AbstractDataSource<T> {
        @GuardedBy("RetainingDataSource.this")
        @Nullable
        private DataSource<T> mDataSource = null;

        public void setSupplier(@Nullable Supplier<DataSource<T>> supplier) {
            // early return without calling {@code supplier.get()} in case we are closed
            if (isClosed()) {
                return;
            }
            DataSource<T> oldDataSource;
            DataSource<T> newDataSource = (supplier != null) ? supplier.get() : null;
            synchronized (RetainingDataSource.this) {
                if (isClosed()) {
                    oldDataSource = newDataSource;
                    newDataSource = null;
                } else {
                    oldDataSource = mDataSource;
                    mDataSource = newDataSource;
                }
            }
            if (newDataSource != null) {
                newDataSource.subscribe(new InternalDataSubscriber(), CallerThreadExecutor.getInstance());
            }
            closeSafely(oldDataSource);
        }

        @Override
        @Nullable
        public synchronized T getResult() {
            return (mDataSource != null) ? mDataSource.getResult() : null;
        }

        @Override
        public synchronized boolean hasResult() {
            return (mDataSource != null) && mDataSource.hasResult();
        }

        @Override
        public boolean close() {
            DataSource<T> dataSource;
            synchronized (RetainingDataSource.this) {
                // it's fine to call {@code super.close()} within a synchronized block because we don't
                // implement {@link #closeResult()}, but perform result closing ourselves.
                if (!super.close()) {
                    return false;
                }
                dataSource = mDataSource;
                mDataSource = null;
            }
            closeSafely(dataSource);
            return true;
        }

        private void onDataSourceNewResult(DataSource<T> dataSource) {
            if (dataSource == mDataSource) {
                setResult(null, false);
            }
        }

        private void onDataSourceFailed(DataSource<T> dataSource) {
            // do not propagate failure
        }

        private void onDatasourceProgress(DataSource<T> dataSource) {
            if (dataSource == mDataSource) {
                setProgress(dataSource.getProgress());
            }
        }

        private void closeSafely(DataSource<T> dataSource) {
            if (dataSource != null) {
                dataSource.close();
            }
        }

        private class InternalDataSubscriber implements DataSubscriber<T> {
            @Override
            public void onNewResult(DataSource<T> dataSource) {
                if (dataSource.hasResult()) {
                    RetainingDataSource.this.onDataSourceNewResult(dataSource);
                } else if (dataSource.isFinished()) {
                    RetainingDataSource.this.onDataSourceFailed(dataSource);
                }
            }

            @Override
            public void onFailure(DataSource<T> dataSource) {
                RetainingDataSource.this.onDataSourceFailed(dataSource);
            }

            @Override
            public void onCancellation(DataSource<T> dataSource) {
            }

            @Override
            public void onProgressUpdate(DataSource<T> dataSource) {
                RetainingDataSource.this.onDatasourceProgress(dataSource);
            }
        }
    }
}

Use:

SimpleDraweeView mImageView = //findById; 
RetainingDataSourceSupplier<CloseableReference<CloseableImage>> retainingSupplier = new RetainingDataSourceSupplier<>();
            PipelineDraweeControllerBuilder builder = Fresco.newDraweeControllerBuilder();
            builder.setDataSourceSupplier(retainingSupplier);
            mImageView.setController(builder.build());
retainingSupplier.setSupplier(Fresco.getImagePipeline().getDataSourceSupplier(ImageRequest.fromUri("your cool uri"), null, false));

hi, @oprisnik i used @agustinsivoplas's code,unfortunately,it's not working,when i replace url,it still shows in the following order:first image-->placeholder-->second image,It really is not a good experience

The sample code in #1328 is working for me locally. Just do the exact same things as in the example given there.