Fresco源码分析(二)

先看Imagepipeline:

Image pipeline 负责完成加载图像,变成Android设备可呈现的形式所要做的每个事情。
大致流程如下:

  • 检查内存缓存,如有,返回
  • 后台线程开始后续工作
  • 检查是否在未解码内存缓存中。如有,解码,变换,返回,然后缓存到内存缓存中。
  • 检查是否在磁盘缓存中,如果有,变换,返回。缓存到未解码缓存和内存缓存中。
  • 从网络或者本地加载。加载完成后,解码,变换,返回。存到各个缓存中。

总之你认为是个策略工厂

package com.facebook.imagepipeline.core;

import *

/**
 * The entry point for the image pipeline.
 */
@ThreadSafe
public class ImagePipeline {
  private static final CancellationException PREFETCH_EXCEPTION =
      new CancellationException("Prefetching is not enabled");

  private final ProducerSequenceFactory mProducerSequenceFactory;
  private final RequestListener mRequestListener;
  private final Supplier<Boolean> mIsPrefetchEnabledSupplier;
  private final MemoryCache<CacheKey, CloseableImage> mBitmapMemoryCache;
  private final MemoryCache<CacheKey, PooledByteBuffer> mEncodedMemoryCache;
  private final BufferedDiskCache mMainBufferedDiskCache;
  private final BufferedDiskCache mSmallImageBufferedDiskCache;
  private final CacheKeyFactory mCacheKeyFactory;
  private final ThreadHandoffProducerQueue mThreadHandoffProducerQueue;
  private AtomicLong mIdCounter;

  public ImagePipeline(
      ProducerSequenceFactory producerSequenceFactory, // 消费和生产者模式,这个会创建网络请求片段序列
      Set<RequestListener> requestListeners, // 这货就是最后,下载完图片,通过消费者,最后回掉用的接口
      Supplier<Boolean> isPrefetchEnabledSupplier,
      MemoryCache<CacheKey, CloseableImage> bitmapMemoryCache, // 缓存1级
      MemoryCache<CacheKey, PooledByteBuffer> encodedMemoryCache, // 缓存2级
      BufferedDiskCache mainBufferedDiskCache, // Frescon 支持大图和小图区别对待,分开buff和toDisk,默认都是这个
      BufferedDiskCache smallImageBufferedDiskCache,// 小图
      CacheKeyFactory cacheKeyFactory, // 默认的key-value工厂
      ThreadHandoffProducerQueue threadHandoffProducerQueue) { // 生产者队列,用于在pause关闭资源
    mIdCounter = new AtomicLong(); // 为这个ImagePiple打一个独特内存id
    mProducerSequenceFactory = producerSequenceFactory;
    mRequestListener = new ForwardingRequestListener(requestListeners);// 这个是回掉接口包装
    mIsPrefetchEnabledSupplier = isPrefetchEnabledSupplier;
    mBitmapMemoryCache = bitmapMemoryCache;
    mEncodedMemoryCache = encodedMemoryCache;
    mMainBufferedDiskCache = mainBufferedDiskCache;
    mSmallImageBufferedDiskCache = smallImageBufferedDiskCache;
    mCacheKeyFactory = cacheKeyFactory;
    mThreadHandoffProducerQueue = threadHandoffProducerQueue;
  }

  private String generateUniqueFutureId() {
    return String.valueOf(mIdCounter.getAndIncrement());
  }

  /**
   * Returns a DataSource supplier that will on get submit the request for execution and return a
   * DataSource representing the pending results of the task.
   * 完成request,带bitmapCacheOnly这个flag策略,加载图片来源
   */
  public Supplier<DataSource<CloseableReference<CloseableImage>>> getDataSourceSupplier(
      final ImageRequest imageRequest,
      final Object callerContext,
      final boolean bitmapCacheOnly) {
    return new Supplier<DataSource<CloseableReference<CloseableImage>>>() {
      @Override
      public DataSource<CloseableReference<CloseableImage>> get() {
        if (bitmapCacheOnly) {
          return fetchImageFromBitmapCache(imageRequest, callerContext);
        } else {
          return fetchDecodedImage(imageRequest, callerContext);
        }
      }
    };
  }

  /**
   * Returns a DataSource supplier that will on get submit the request for execution and return a
   * DataSource representing the pending results of the task.
   * 完成request,加载图片来源,磁盘或者网络
   */
  public Supplier<DataSource<CloseableReference<PooledByteBuffer>>>
      getEncodedImageDataSourceSupplier(
          final ImageRequest imageRequest,
          final Object callerContext) {
    return new Supplier<DataSource<CloseableReference<PooledByteBuffer>>>() {
      @Override
      public DataSource<CloseableReference<PooledByteBuffer>> get() {
        return fetchEncodedImage(imageRequest, callerContext);
      }
    };
  }

  /**
   * Submits a request for bitmap cache lookup.
   * 完成request,加载图片来源 图片缓存
   */
  public DataSource<CloseableReference<CloseableImage>> fetchImageFromBitmapCache(
      ImageRequest imageRequest,
      Object callerContext) {
    try {
      Producer<CloseableReference<CloseableImage>> producerSequence =
          mProducerSequenceFactory.getDecodedImageProducerSequence(imageRequest);
      return submitFetchRequest(
          producerSequence,
          imageRequest,
          ImageRequest.RequestLevel.BITMAP_MEMORY_CACHE, //     BITMAP_MEMORY_CACHE(4); 这个等级是Bitmap caching
          callerContext);
    } catch (Exception exception) {
      return DataSources.immediateFailedDataSource(exception);
    }
  }

  /**
   * Submits a request for execution and returns a DataSource representing the pending decoded
   * image(s). 完成request,加载图片来源网络或者磁盘
   * <p>The returned DataSource must be closed once the client has finished with it.
   */
  public DataSource<CloseableReference<CloseableImage>> fetchDecodedImage(
      ImageRequest imageRequest,
      Object callerContext) {
    try {
      Producer<CloseableReference<CloseableImage>> producerSequence =
          mProducerSequenceFactory.getDecodedImageProducerSequence(imageRequest);
      return submitFetchRequest(
          producerSequence,
          imageRequest,
          ImageRequest.RequestLevel.FULL_FETCH, // 这个等级 Fetch (from the network or local storage) 
          callerContext);
    } catch (Exception exception) {
      return DataSources.immediateFailedDataSource(exception);
    }
  }

  /**
   * Submits a request for execution and returns a DataSource representing the pending encoded
   * image(s).
   * 最终要调这个Submits
   */
  public DataSource<CloseableReference<PooledByteBuffer>> fetchEncodedImage(
      ImageRequest imageRequest,
      Object callerContext) {
    Preconditions.checkNotNull(imageRequest.getSourceUri());
    try {
      Producer<CloseableReference<PooledByteBuffer>> producerSequence =
          mProducerSequenceFactory.getEncodedImageProducerSequence(imageRequest); // 这个很重要了,NetWork请求client在这个里面准备好
      // The resize options are used to determine whether images are going to be downsampled during
      // decode or not. For the case where the image has to be downsampled and it's a local image it
      // will be kept as a FileInputStream until decoding instead of reading it in memory. Since
      // this method returns an encoded image, it should always be read into memory. Therefore, the
      // resize options are ignored to avoid treating the image as if it was to be downsampled
      // during decode.
      if (imageRequest.getResizeOptions() != null) {
        imageRequest = ImageRequestBuilder.fromRequest(imageRequest)
            .setResizeOptions(null)
            .build();
      }
      return submitFetchRequest(
          producerSequence,
          imageRequest,
          ImageRequest.RequestLevel.FULL_FETCH,
          callerContext); // 执行生产者消费者 从这里开始了
    } catch (Exception exception) {
      return DataSources.immediateFailedDataSource(exception);
    }
  }

  public DataSource<Void> prefetchToBitmapCache(
      ImageRequest imageRequest,
      Object callerContext) {
    if (!mIsPrefetchEnabledSupplier.get()) {
      return DataSources.immediateFailedDataSource(PREFETCH_EXCEPTION);
    }
    try {
      Producer<Void> producerSequence =
          mProducerSequenceFactory.getDecodedImagePrefetchProducerSequence(imageRequest);
      return submitPrefetchRequest(
          producerSequence,
          imageRequest,
          ImageRequest.RequestLevel.FULL_FETCH,
          callerContext,
          Priority.MEDIUM);
    } catch (Exception exception) {
      return DataSources.immediateFailedDataSource(exception);
    }
  }

  public DataSource<Void> prefetchToDiskCache(
      ImageRequest imageRequest,
      Object callerContext,
      Priority priority) {
    if (!mIsPrefetchEnabledSupplier.get()) {
      return DataSources.immediateFailedDataSource(PREFETCH_EXCEPTION);
    }
    try {
      Producer<Void> producerSequence =
          mProducerSequenceFactory.getEncodedImagePrefetchProducerSequence(imageRequest);
      return submitPrefetchRequest(
          producerSequence,
          imageRequest,
          ImageRequest.RequestLevel.FULL_FETCH,
          callerContext,
          priority);
    } catch (Exception exception) {
      return DataSources.immediateFailedDataSource(exception);
    }
  }

     .........


  private <T> DataSource<CloseableReference<T>> submitFetchRequest(
      Producer<CloseableReference<T>> producerSequence,
      ImageRequest imageRequest,
      ImageRequest.RequestLevel lowestPermittedRequestLevelOnSubmit,
      Object callerContext) {
    try {
      ImageRequest.RequestLevel lowestPermittedRequestLevel =
          ImageRequest.RequestLevel.getMax(
              imageRequest.getLowestPermittedRequestLevel(),
              lowestPermittedRequestLevelOnSubmit);
      SettableProducerContext settableProducerContext = new SettableProducerContext(// 创建一个上下文对象,如同Android:Context
          imageRequest,
          generateUniqueFutureId(),
          mRequestListener,
          callerContext,
          lowestPermittedRequestLevel,
        /* isPrefetch */ false,
          imageRequest.getProgressiveRenderingEnabled() ||
              !UriUtil.isNetworkUri(imageRequest.getSourceUri()),
          imageRequest.getPriority());
      return CloseableProducerToDataSourceAdapter.create( //生产者数据源开始
          producerSequence,
          settableProducerContext,
          mRequestListener);
    } catch (Exception exception) {
      return DataSources.immediateFailedDataSource(exception);
    }
  }

  private DataSource<Void> submitPrefetchRequest( // 这个就是从内存拿策略和上面差不多,介绍上面那个,不同就是生产者不同
      Producer<Void> producerSequence,
      ImageRequest imageRequest,
      ImageRequest.RequestLevel lowestPermittedRequestLevelOnSubmit,
      Object callerContext,
      Priority priority) {
    try {
      ImageRequest.RequestLevel lowestPermittedRequestLevel =
          ImageRequest.RequestLevel.getMax(
              imageRequest.getLowestPermittedRequestLevel(),
              lowestPermittedRequestLevelOnSubmit);
      SettableProducerContext settableProducerContext = new SettableProducerContext(
          imageRequest,
          generateUniqueFutureId(),
          mRequestListener,
          callerContext,
          lowestPermittedRequestLevel,
        /* isPrefetch */ true,
        /* isIntermediateResultExpected */ false,
          priority);
      return ProducerToDataSourceAdapter.create(
          producerSequence,
          settableProducerContext,
          mRequestListener);
    } catch (Exception exception) {
      return DataSources.immediateFailedDataSource(exception);
    }
  }

   ........

  public void pause() {
    mThreadHandoffProducerQueue.startQueueing();
  }

  public void resume() {
    mThreadHandoffProducerQueue.stopQueuing();
  }

  public boolean isPaused() {
    return mThreadHandoffProducerQueue.isQueueing();
  }

  /**
   * @return The CacheKeyFactory implementation used by ImagePipeline
   */
  public CacheKeyFactory getCacheKeyFactory() {
    return mCacheKeyFactory;
  }
}

在构造函数中我们看到了mRequestListener = new ForwardingRequestListener(requestListeners);这货就是提交给ImagePipleDraweeController中Listener,基本无用,默认requestListeners为null
流程:
AbstractDraweeController:sumbit() ===>PipelineDraweeController:getDataSource() ===> PipelineDraweeControllerBuilderSupplier === > ImagePipelineFactory === > ImagePipeline ===> ImagePipeline:getDataSourceSupplier() === >fetchDecodedImage() ===>ProducerSequenceFactory:getDecodedImageProducerSequence(imageRequest)处理,然后创建生产者和消费者CloseableProducerToDataSourceAdapter:create() ===>线程等级为ProducerSequenceFactory.BackGroudTask执行request
在介绍生产者之前,先把ProducerSequenceFactory这个看了

  public Producer<CloseableReference<CloseableImage>> getDecodedImageProducerSequence(
      ImageRequest imageRequest) {
    Producer<CloseableReference<CloseableImage>> pipelineSequence =
        getBasicDecodedImageSequence(imageRequest);// 这里开始装配
    if (imageRequest.getPostprocessor() != null) {// 判断是否有后后处理器,默认是没有
      return getPostprocessorSequence(pipelineSequence);
    } else {
      return pipelineSequence;
    }
  }

关于后处理器: 完成特殊定制,就是你在下载完成后,这个图片需要特殊要求和处理

Producer<CloseableReference<CloseableImage>> pipelineSequence = getBasicDecodedImageSequence(imageRequest);
 private Producer<CloseableReference<CloseableImage>> getBasicDecodedImageSequence(
      ImageRequest imageRequest) {
    Preconditions.checkNotNull(imageRequest);

    Uri uri = imageRequest.getSourceUri(); // setUrl时候构造
    Preconditions.checkNotNull(uri, "Uri is null.");
    if (UriUtil.isNetworkUri(uri)) {
      return getNetworkFetchSequence(); // 网络请求走这个
    } else if (UriUtil.isLocalFileUri(uri)) {
      if (MediaUtils.isVideo(MediaUtils.extractMime(uri.getPath()))) {
        return getLocalVideoFileFetchSequence();
      } else {
        return getLocalImageFileFetchSequence();
      }
    } else if (UriUtil.isLocalContentUri(uri)) {
      return getLocalContentUriFetchSequence();
    } else if (UriUtil.isLocalAssetUri(uri)) {
      return getLocalAssetFetchSequence();
    } else if (UriUtil.isLocalResourceUri(uri)) {
      return getLocalResourceFetchSequence();
    } else if (UriUtil.isDataUri(uri)) {
      return getDataFetchSequence();
    } else {
      String uriString = uri.toString();
      if (uriString.length() > 30) {
        uriString = uriString.substring(0, 30) + "...";
      }
      throw new RuntimeException("Unsupported uri scheme! Uri is: " + uriString);
    }
  }

其中大部分我们不关心,先找到核心请求,其它只是策略
getNetworkFetchSequence(); 不用看,就这货了。

/**
   * swallow result if prefetch -> bitmap cache get ->
   * background thread hand-off -> multiplex -> bitmap cache -> decode -> multiplex ->
   * encoded cache -> disk cache -> (webp transcode) -> network fetch.
   * 大致就是说,没有缓存你就走disk,再没有就network, multiplex是指多个request load 一起来,一样的流程,因为是有multiplex,所有同步方法
   */
  private synchronized Producer<CloseableReference<CloseableImage>> getNetworkFetchSequence() {
    if (mNetworkFetchSequence == null) {
      mNetworkFetchSequence =
          newBitmapCacheGetToDecodeSequence(getCommonNetworkFetchToEncodedMemorySequence());
    }
    return mNetworkFetchSequence;
  }

getCommonNetworkFetchToEncodedMemorySequence() 名字已经暴露你了,什么XXX网络片段请求

 /**
   * multiplex -> encoded cache -> disk cache -> (webp transcode) -> network fetch.
   */
  private synchronized Producer<EncodedImage> getCommonNetworkFetchToEncodedMemorySequence() {
    if (mCommonNetworkFetchToEncodedMemorySequence == null) {
      Producer<EncodedImage> inputProducer =
          newEncodedCacheMultiplexToTranscodeSequence(
              mProducerFactory.newNetworkFetchProducer(mNetworkFetcher));
      mCommonNetworkFetchToEncodedMemorySequence =
          ProducerFactory.newAddImageTransformMetaDataProducer(inputProducer);

      if (mResizeAndRotateEnabledForNetwork && !mDownsampleEnabled) {
        mCommonNetworkFetchToEncodedMemorySequence =
            mProducerFactory.newResizeAndRotateProducer(
                mCommonNetworkFetchToEncodedMemorySequence);
      }
    }
    return mCommonNetworkFetchToEncodedMemorySequence;
  }

看重点,其它乱78遭,旋转改变大小又重新干一个请求,这个不看。

Paste_Image.png

mNetworkFetcher这货是真正底层网络代码了,默认使用HttpUrlConnectionNetworkFetcher,HttpUrlConnection的bug已经在4.3以后修复了,如果要定制okhttp,像作者一样OkHttpNetworkFetcher,需要在Frescon初始化的时候注入:com.facebook.drawee.backends.pipeline.Fresco.initialize(this, OkHttpImagePipelineConfigFactory.newBuilder(this, new okhttp3.OkHttpClient()).build());

ProducerFactory.newAddImageTransformMetaDataProducer(inputProducer); 这货就是消费者添加图片

 public static AddImageTransformMetaDataProducer newAddImageTransformMetaDataProducer(
      Producer<EncodedImage> inputProducer) {
    return new AddImageTransformMetaDataProducer(inputProducer);
  }

/**
 * Add image transform meta data producer
 *
 * <p>Extracts meta data from the results passed down from the next producer, and adds it to the
 * result that it returns to the consumer.
 */
public class AddImageTransformMetaDataProducer implements Producer<EncodedImage> {
  private final Producer<EncodedImage> mInputProducer;

  public AddImageTransformMetaDataProducer(Producer<EncodedImage> inputProducer) {
    mInputProducer = inputProducer;
  }

  @Override
  public void produceResults(Consumer<EncodedImage> consumer, ProducerContext context) {
    mInputProducer.produceResults(new AddImageTransformMetaDataConsumer(consumer), context);
  }

  private static class AddImageTransformMetaDataConsumer extends DelegatingConsumer<
      EncodedImage, EncodedImage> {

    private AddImageTransformMetaDataConsumer(Consumer<EncodedImage> consumer) {
      super(consumer);
    }

    @Override
    protected void onNewResultImpl(EncodedImage newResult, boolean isLast) {
      if (newResult == null) {
        getConsumer().onNewResult(null, isLast);
        return;
      }
      if (!EncodedImage.isMetaDataAvailable(newResult)) {
        newResult.parseMetaData();
      }
      getConsumer().onNewResult(newResult, isLast);
    }
  }
}

基本下载类

// 基本下载类,没有什么特点
/**
 * Network fetcher that uses the simplest Android stack.
 *
 * <p> Apps requiring more sophisticated networking should implement their own
 * {@link NetworkFetcher}.
 */
public class HttpUrlConnectionNetworkFetcher extends BaseNetworkFetcher<FetchState> {

  private final ExecutorService mExecutorService;

  public HttpUrlConnectionNetworkFetcher() {
    this(Executors.newFixedThreadPool(NUM_NETWORK_THREADS));
  }

  @VisibleForTesting
  HttpUrlConnectionNetworkFetcher(ExecutorService executorService) {
    mExecutorService = executorService;
  }

  @Override
  public FetchState createFetchState(Consumer<EncodedImage> consumer, ProducerContext context) {
    return new FetchState(consumer, context);
  }

  @Override
  public void fetch(final FetchState fetchState, final Callback callback) {
    final Future<?> future = mExecutorService.submit(
        new Runnable() {
          @Override
          public void run() {
            fetchSync(fetchState, callback);
          }
        });
    fetchState.getContext().addCallbacks(
        new BaseProducerContextCallbacks() {
          @Override
          public void onCancellationRequested() {
            if (future.cancel(false)) {
              callback.onCancellation();
            }
          }
        });
  }

  @VisibleForTesting
  void fetchSync(FetchState fetchState, Callback callback) {
    HttpURLConnection connection = null;

    try {
      connection = downloadFrom(fetchState.getUri(), MAX_REDIRECTS);

      if (connection != null) {
        callback.onResponse(connection.getInputStream(), -1);
      }
    } catch (IOException e) {
      callback.onFailure(e);
    } finally {
      if (connection != null) {
        connection.disconnect();
      }
    }

  }

  private HttpURLConnection downloadFrom(Uri uri, int maxRedirects) throws IOException {
    HttpURLConnection connection = openConnectionTo(uri);
    int responseCode = connection.getResponseCode();

    if (isHttpSuccess(responseCode)) {
        return connection;

    } else if (isHttpRedirect(responseCode)) {
        String nextUriString = connection.getHeaderField("Location");
        connection.disconnect();

        Uri nextUri = (nextUriString == null) ? null : Uri.parse(nextUriString);
        String originalScheme = uri.getScheme();

        if (maxRedirects > 0 && nextUri != null && !nextUri.getScheme().equals(originalScheme)) {
          return downloadFrom(nextUri, maxRedirects - 1);
        } else {
          String message = maxRedirects == 0
              ? error("URL %s follows too many redirects", uri.toString())
              : error("URL %s returned %d without a valid redirect", uri.toString(), responseCode);
          throw new IOException(message);
        }

    } else {
        connection.disconnect();
        throw new IOException(String
            .format("Image URL %s returned HTTP code %d", uri.toString(), responseCode));
    }
  }

  @VisibleForTesting
  static HttpURLConnection openConnectionTo(Uri uri) throws IOException {
    URL url = new URL(uri.toString());
    return (HttpURLConnection) url.openConnection();
  }
 ......

OkHttp下载介绍

// 下面是OkHttp,使用了异步请求call.enqueue()...不知道童鞋,可以去百度下OkHttp,不难,也是建造者模式
public class OkHttpNetworkFetcher extends
    BaseNetworkFetcher<OkHttpNetworkFetcher.OkHttpNetworkFetchState> {

  public static class OkHttpNetworkFetchState extends FetchState {

    public OkHttpNetworkFetchState(
        Consumer<EncodedImage> consumer,
        ProducerContext producerContext) {
      super(consumer, producerContext);
    }
  }

  private final OkHttpClient mOkHttpClient;

  private Executor mCancellationExecutor;

  /**
   * @param okHttpClient client to use
   */
  public OkHttpNetworkFetcher(OkHttpClient okHttpClient) {
    mOkHttpClient = okHttpClient;
    mCancellationExecutor = okHttpClient.dispatcher().executorService();
  }

  @Override
  public OkHttpNetworkFetchState createFetchState(
      Consumer<EncodedImage> consumer,
      ProducerContext context) {
    return new OkHttpNetworkFetchState(consumer, context);
  }

  @Override
  public void fetch(final OkHttpNetworkFetchState fetchState, final Callback callback) {
    fetchState.submitTime = SystemClock.elapsedRealtime();
    final Uri uri = fetchState.getUri();
    final Request request = new Request.Builder()
        .cacheControl(new CacheControl.Builder().noStore().build())
        .url(uri.toString())
        .get()
        .build();
    final Call call = mOkHttpClient.newCall(request);

    fetchState.getContext().addCallbacks(
        new BaseProducerContextCallbacks() {
          @Override
          public void onCancellationRequested() {
            if (Looper.myLooper() != Looper.getMainLooper()) {
              call.cancel();
            } else {
              mCancellationExecutor.execute(new Runnable() {
                @Override public void run() {
                  call.cancel();
                }
              });
            }
          }
        });

    call.enqueue(
        new okhttp3.Callback() {
          @Override
          public void onResponse(Call call, Response response) throws IOException {
            fetchState.responseTime = SystemClock.elapsedRealtime();
            final ResponseBody body = response.body();
            try {
              if (!response.isSuccessful()) {
                handleException(
                        call,
                        new IOException("Unexpected HTTP code " + response),
                        callback);
                return;
              }

              long contentLength = body.contentLength();
              if (contentLength < 0) {
                contentLength = 0;
              }
              callback.onResponse(body.byteStream(), (int) contentLength);
            } catch (Exception e) {
              handleException(call, e, callback);
            } finally {
              try {
                body.close();
              } catch (Exception e) {
                FLog.w(TAG, "Exception when closing response body", e);
              }
            }
          }

          @Override
          public void onFailure(Call call, IOException e) {
            handleException(call, e, callback);
          }
        });
  }
  }
}

newBitmapCacheGetToDecodeSequence这货干缓存,前面干网络请求。

  /**
   * Same as {@code newBitmapCacheGetToBitmapCacheSequence} but with an extra DecodeProducer.
   * @param inputProducer producer providing the input to the decode
   * @return bitmap cache get to decode sequence
   */
  private Producer<CloseableReference<CloseableImage>> newBitmapCacheGetToDecodeSequence(
      Producer<EncodedImage> inputProducer) {
    DecodeProducer decodeProducer = mProducerFactory.newDecodeProducer(inputProducer);
    return newBitmapCacheGetToBitmapCacheSequence(decodeProducer);
  }

DecodeProducer这货images的包装器,build模式

/**
 * Decodes images.
 *
 * <p/> Progressive JPEGs are decoded progressively as new data arrives.
 */
public class DecodeProducer implements Producer<CloseableReference<CloseableImage>> {

  public static final String PRODUCER_NAME = "DecodeProducer";

  // keys for extra map
  private static final String BITMAP_SIZE_KEY = "bitmapSize";
  private static final String HAS_GOOD_QUALITY_KEY = "hasGoodQuality";
  private static final String IMAGE_TYPE_KEY = "imageType";
  private static final String IS_FINAL_KEY = "isFinal";

  private final ByteArrayPool mByteArrayPool;
  private final Executor mExecutor;
  private final ImageDecoder mImageDecoder;
  private final ProgressiveJpegConfig mProgressiveJpegConfig;
  private final Producer<EncodedImage> mInputProducer;
  private final boolean mDownsampleEnabled;
  private final boolean mDownsampleEnabledForNetwork;
  
  ........
}

里面有执行器和byte数据池,Decoder
回归主线:
newBitmapCacheGetToBitmapCacheSequence(decodeProducer);

/**
   * Bitmap cache get -> thread hand off -> multiplex -> bitmap cache
   * @param inputProducer producer providing the input to the bitmap cache
   * @return bitmap cache get to bitmap cache sequence
   */
  private Producer<CloseableReference<CloseableImage>> newBitmapCacheGetToBitmapCacheSequence(
      Producer<CloseableReference<CloseableImage>> inputProducer) {
    BitmapMemoryCacheProducer bitmapMemoryCacheProducer =
        mProducerFactory.newBitmapMemoryCacheProducer(inputProducer);
    BitmapMemoryCacheKeyMultiplexProducer bitmapKeyMultiplexProducer =
        mProducerFactory.newBitmapMemoryCacheKeyMultiplexProducer(bitmapMemoryCacheProducer);
    ThreadHandoffProducer<CloseableReference<CloseableImage>> threadHandoffProducer =
        mProducerFactory.newBackgroundThreadHandoffProducer(
            bitmapKeyMultiplexProducer,
            mThreadHandoffProducerQueue);
    return mProducerFactory.newBitmapMemoryCacheGetProducer(threadHandoffProducer);
  }

mProducerFactory.newBackgroundThreadHandoffProducer(bitmapKeyMultiplexProducer,mThreadHandoffProducerQueue);还记得前面有说ImagePipeline:mThreadHandoffProducerQueue么,这货维护请求资源,pause开打进行释放,resume反之
mProducerFactory.newBitmapMemoryCacheGetProducer(threadHandoffProducer);

  public BitmapMemoryCacheGetProducer newBitmapMemoryCacheGetProducer(
      Producer<CloseableReference<CloseableImage>> inputProducer) {
    return new BitmapMemoryCacheGetProducer(mBitmapMemoryCache, mCacheKeyFactory, inputProducer);
  }

最后塞到Cache中处理

/**
 * Bitmap memory cache producer that is read-only.
 */
public class BitmapMemoryCacheGetProducer extends BitmapMemoryCacheProducer {

  @VisibleForTesting static final String PRODUCER_NAME = "BitmapMemoryCacheGetProducer";

  public BitmapMemoryCacheGetProducer(
      MemoryCache<CacheKey, CloseableImage> memoryCache,
      CacheKeyFactory cacheKeyFactory,
      Producer<CloseableReference<CloseableImage>> inputProducer) {
    super(memoryCache, cacheKeyFactory, inputProducer);
  }

  @Override
  protected Consumer<CloseableReference<CloseableImage>> wrapConsumer(
      final Consumer<CloseableReference<CloseableImage>> consumer,
      final CacheKey cacheKey) {
    // since this cache is read-only, we can pass our consumer directly to the next producer
    return consumer;
  }

  @Override
  protected String getProducerName() {
    return PRODUCER_NAME;
  }

消费者回掉在基类

/**
 * Memory cache producer for the bitmap memory cache.
 */
public class BitmapMemoryCacheProducer implements Producer<CloseableReference<CloseableImage>> {

  @VisibleForTesting static final String PRODUCER_NAME = "BitmapMemoryCacheProducer";
  @VisibleForTesting static final String VALUE_FOUND = "cached_value_found";

  private final MemoryCache<CacheKey, CloseableImage> mMemoryCache;
  private final CacheKeyFactory mCacheKeyFactory;
  private final Producer<CloseableReference<CloseableImage>> mInputProducer;

  public BitmapMemoryCacheProducer(
      MemoryCache<CacheKey, CloseableImage> memoryCache,
      CacheKeyFactory cacheKeyFactory,
      Producer<CloseableReference<CloseableImage>> inputProducer) {
    mMemoryCache = memoryCache;
    mCacheKeyFactory = cacheKeyFactory;
    mInputProducer = inputProducer;
  }

  @Override
  public void produceResults(
      final Consumer<CloseableReference<CloseableImage>> consumer,
      final ProducerContext producerContext) {

    final ProducerListener listener = producerContext.getListener();
    final String requestId = producerContext.getId();
    listener.onProducerStart(requestId, getProducerName());
    final ImageRequest imageRequest = producerContext.getImageRequest();
    final Object callerContext = producerContext.getCallerContext();
    final CacheKey cacheKey = mCacheKeyFactory.getBitmapCacheKey(imageRequest, callerContext);

    CloseableReference<CloseableImage> cachedReference = mMemoryCache.get(cacheKey);

    if (cachedReference != null) {
      boolean isFinal = cachedReference.get().getQualityInfo().isOfFullQuality();
      if (isFinal) {
        listener.onProducerFinishWithSuccess(
            requestId,
            getProducerName(),
            listener.requiresExtraMap(requestId) ? ImmutableMap.of(VALUE_FOUND, "true") : null);
        consumer.onProgressUpdate(1f);
      }
      consumer.onNewResult(cachedReference, isFinal);
      cachedReference.close();
      if (isFinal) {
        return;
      }
    }

    if (producerContext.getLowestPermittedRequestLevel().getValue() >=
        ImageRequest.RequestLevel.BITMAP_MEMORY_CACHE.getValue()) {
      listener.onProducerFinishWithSuccess(
          requestId,
          getProducerName(),
          listener.requiresExtraMap(requestId) ? ImmutableMap.of(VALUE_FOUND, "false") : null);
      consumer.onNewResult(null, true);
      return;
    }

    Consumer<CloseableReference<CloseableImage>> wrappedConsumer = wrapConsumer(consumer, cacheKey);
    listener.onProducerFinishWithSuccess(
        requestId,
        getProducerName(),
        listener.requiresExtraMap(requestId) ? ImmutableMap.of(VALUE_FOUND, "false") : null);
    mInputProducer.produceResults(wrappedConsumer, producerContext);
  }

  protected Consumer<CloseableReference<CloseableImage>> wrapConsumer(
      final Consumer<CloseableReference<CloseableImage>> consumer,
      final CacheKey cacheKey) {
    return new DelegatingConsumer<
        CloseableReference<CloseableImage>,
        CloseableReference<CloseableImage>>(consumer) {
      @Override
      public void onNewResultImpl(CloseableReference<CloseableImage> newResult, boolean isLast) {
        // ignore invalid intermediate results and forward the null result if last
        if (newResult == null) {
          if (isLast) {
            getConsumer().onNewResult(null, true);
          }
          return;
        }
        // stateful results cannot be cached and are just forwarded
        if (newResult.get().isStateful()) {
          getConsumer().onNewResult(newResult, isLast);
          return;
        }
        // if the intermediate result is not of a better quality than the cached result,
        // forward the already cached result and don't cache the new result.
        if (!isLast) {
          CloseableReference<CloseableImage> currentCachedResult = mMemoryCache.get(cacheKey);
          if (currentCachedResult != null) {
            try {
              QualityInfo newInfo = newResult.get().getQualityInfo();
              QualityInfo cachedInfo = currentCachedResult.get().getQualityInfo();
              if (cachedInfo.isOfFullQuality() || cachedInfo.getQuality() >= newInfo.getQuality()) {
                getConsumer().onNewResult(currentCachedResult, false);
                return;
              }
            } finally {
              CloseableReference.closeSafely(currentCachedResult);
            }
          }
        }
        // cache and forward the new result
        CloseableReference<CloseableImage> newCachedResult =
            mMemoryCache.cache(cacheKey, newResult);
        try {
          if (isLast) {
            getConsumer().onProgressUpdate(1f);
          }
          getConsumer().onNewResult(
              (newCachedResult != null) ? newCachedResult : newResult, isLast);
        } finally {
          CloseableReference.closeSafely(newCachedResult);
        }
      }
    };
  }

  protected String getProducerName() {
    return PRODUCER_NAME;
  }
}

produceResults()来自AbstractProducerToDataSourceAdapter(),往上追踪这个方法来自ImagePipleine:submitFetchRequest():CloseableProducerToDataSourceAdapter:creat()进行回掉
然后函数结尾的时候走mInputProducer.produceResults(wrappedConsumer, producerContext);
mInputProducer来自ImagePiple:fetchDecodedImage(): Producer<CloseableReference<CloseableImage>> producerSequence =
mProducerSequenceFactory.getDecodedImageProducerSequence(imageRequest);
生成:ThreadHandoffProducer

/**
 * Uses ExecutorService to move further computation to different thread
 */
public class ThreadHandoffProducer<T> implements Producer<T> {

  @VisibleForTesting
  protected static final String PRODUCER_NAME = "BackgroundThreadHandoffProducer";

  private final Producer<T> mInputProducer;
  private final ThreadHandoffProducerQueue mThreadHandoffProducerQueue;

  public ThreadHandoffProducer(final Producer<T> inputProducer,
                               final  ThreadHandoffProducerQueue inputThreadHandoffProducerQueue) {
    mInputProducer = Preconditions.checkNotNull(inputProducer);
    mThreadHandoffProducerQueue = inputThreadHandoffProducerQueue;
  }

  @Override
  public void produceResults(final Consumer<T> consumer, final ProducerContext context) {
    final ProducerListener producerListener = context.getListener();
    final String requestId = context.getId();
    final StatefulProducerRunnable<T> statefulRunnable = new StatefulProducerRunnable<T>(
        consumer,
        producerListener,
        PRODUCER_NAME,
        requestId) {
      @Override
      protected void onSuccess(T ignored) {
        producerListener.onProducerFinishWithSuccess(requestId, PRODUCER_NAME, null);
        mInputProducer.produceResults(consumer, context);
      }

      @Override
      protected void disposeResult(T ignored) {}

      @Override
      protected T getResult() throws Exception {
        return null;
      }
    };
    context.addCallbacks(
        new BaseProducerContextCallbacks() {
          @Override
          public void onCancellationRequested() {
            statefulRunnable.cancel();
            mThreadHandoffProducerQueue.remove(statefulRunnable);
          }
        });
    mThreadHandoffProducerQueue.addToQueueOrExecute(statefulRunnable);
  }
}

produceResults()进行队列任务

/**
 * Abstraction for computation.
 *
 * <p> Computation expressed as StatefulRunnable can be cancelled, but only if it has not
 * started yet.
 *
 * <p> For better decoupling of the code computing the result and the code that handles it, 4
 * separate methods are provided: getResult, onSuccess, onFailure and onCancellation.
 *
 * <p> This runnable can be run only once. Subsequent calls to run method won't have any effect.
 */
abstract public class StatefulRunnable<T> implements Runnable {

  @Override
  public final void run() {
    if (!mState.compareAndSet(STATE_CREATED, STATE_STARTED)) {
      return;
    }
    T result;
    try {
      result = getResult();
    } catch (Exception e) {
      mState.set(STATE_FAILED);
      onFailure(e);
      return;
    }

    mState.set(STATE_FINISHED);
    try {
      onSuccess(result);
    } finally {
      disposeResult(result);
    }
  }
}

完成回掉onSuccess,上面ThreadHandoffProducer:produceResults()&StatefulProducerRunnable:onSuccess():mInputProducer.produceResults(consumer, context);而这个mInputProducer是MultiplexProducer,看下描述:

/**
 * Producer for combining multiple identical requests into a single request.
 *
 * <p>Requests using the same key will be combined into a single request. This request is only
 * cancelled when all underlying requests are cancelled, and returns values to all underlying
 * consumers. If the request has already return one or more results but has not finished, then
 * any requests with the same key will have the most recent result returned to them immediately.
 *  也就是说这个类,把同样key合并成一个request,并且这个request如果被取消,那么下面所有的consumer将接收,
 *  还有就是如果这个request提前返回,但没有完成,那么其它的request同样key将会立即拿到返回的。
 */
@ThreadSafe
public abstract class MultiplexProducer<K, T extends Closeable> implements Producer<T> {}

然后接着继续 mInputProducer.produceResults(forwardingConsumer,multiplexProducerContext); 当前这个在MultiplexProducer的mInputProducer是BitmapMemoryCacheProducer,然后接收produceResults()进行包装

/**
 * Memory cache producer for the bitmap memory cache.
 */
public class BitmapMemoryCacheProducer implements Producer<CloseableReference<CloseableImage>> {

 public void produceResults(
      final Consumer<CloseableReference<CloseableImage>> consumer,
      final ProducerContext producerContext) {

    final ProducerListener listener = producerContext.getListener();
    final String requestId = producerContext.getId();
    listener.onProducerStart(requestId, getProducerName());
    final ImageRequest imageRequest = producerContext.getImageRequest();
    final Object callerContext = producerContext.getCallerContext();
    final CacheKey cacheKey = mCacheKeyFactory.getBitmapCacheKey(imageRequest, callerContext);

    CloseableReference<CloseableImage> cachedReference = mMemoryCache.get(cacheKey);

    if (cachedReference != null) {
      boolean isFinal = cachedReference.get().getQualityInfo().isOfFullQuality();
      if (isFinal) {
        listener.onProducerFinishWithSuccess(
            requestId,
            getProducerName(),
            listener.requiresExtraMap(requestId) ? ImmutableMap.of(VALUE_FOUND, "true") : null);
        consumer.onProgressUpdate(1f);
      }
      consumer.onNewResult(cachedReference, isFinal);
      cachedReference.close();
      if (isFinal) {
        return;
      }
    }

    if (producerContext.getLowestPermittedRequestLevel().getValue() >=
        ImageRequest.RequestLevel.BITMAP_MEMORY_CACHE.getValue()) {
      listener.onProducerFinishWithSuccess(
          requestId,
          getProducerName(),
          listener.requiresExtraMap(requestId) ? ImmutableMap.of(VALUE_FOUND, "false") : null);
      consumer.onNewResult(null, true);
      return;
    }

    Consumer<CloseableReference<CloseableImage>> wrappedConsumer = wrapConsumer(consumer, cacheKey);
    listener.onProducerFinishWithSuccess(
        requestId,
        getProducerName(),
        listener.requiresExtraMap(requestId) ? ImmutableMap.of(VALUE_FOUND, "false") : null);
    mInputProducer.produceResults(wrappedConsumer, producerContext);
  }
}

最后又走 mInputProducer.produceResults(wrappedConsumer, producerContext);这个不同了,变成了DecodeProducer去进行图片修饰,前面有说

/**
 * Decodes images.
 *
 * <p/> Progressive JPEGs are decoded progressively as new data arrives.
 */
public class DecodeProducer implements Producer<CloseableReference<CloseableImage>> {

 @Override
  public void produceResults(
      final Consumer<CloseableReference<CloseableImage>> consumer,
      final ProducerContext producerContext) {
    final ImageRequest imageRequest = producerContext.getImageRequest();
    ProgressiveDecoder progressiveDecoder;
    if (!UriUtil.isNetworkUri(imageRequest.getSourceUri())) {
      progressiveDecoder = new LocalImagesProgressiveDecoder(consumer, producerContext);
    } else {
      ProgressiveJpegParser jpegParser = new ProgressiveJpegParser(mByteArrayPool);
      progressiveDecoder = new NetworkImagesProgressiveDecoder(
          consumer,
          producerContext,
          jpegParser,
          mProgressiveJpegConfig);
    }
    mInputProducer.produceResults(progressiveDecoder, producerContext);
  }
}

mInputProducer这货不看了,这个是ResizeAndRotateProducer,旋转有关的,NetworkImagesProgressiveDecoder里面有jobSclude

/**
 * Decodes images.
 *
 * <p/> Progressive JPEGs are decoded progressively as new data arrives.
 */
public class DecodeProducer implements Producer<CloseableReference<CloseableImage>> {

    public ProgressiveDecoder(
        final Consumer<CloseableReference<CloseableImage>> consumer,
        final ProducerContext producerContext) {
      super(consumer);
      mProducerContext = producerContext;
      mProducerListener = producerContext.getListener();
      mImageDecodeOptions = producerContext.getImageRequest().getImageDecodeOptions();
      mIsFinished = false;
      JobRunnable job = new JobRunnable() {
        @Override
        public void run(EncodedImage encodedImage, boolean isLast) {
          if (encodedImage != null) { // 这里开始接收到finsh
            if (mDownsampleEnabled) {
              ImageRequest request = producerContext.getImageRequest();
              if (mDownsampleEnabledForNetwork ||
                  !UriUtil.isNetworkUri(request.getSourceUri())) {
                encodedImage.setSampleSize(DownsampleUtil.determineSampleSize(
                    request, encodedImage));
              }
            }
            doDecode(encodedImage, isLast);
          }
        }
      };
      mJobScheduler = new JobScheduler(mExecutor, job, mImageDecodeOptions.minDecodeIntervalMs);
      mProducerContext.addCallbacks(
          new BaseProducerContextCallbacks() {
            @Override
            public void onIsIntermediateResultExpectedChanged() {
              if (mProducerContext.isIntermediateResultExpected()) {
                mJobScheduler.scheduleJob();
              }
            }
          });
    }
}

然后job做完,会拿到回掉,走doDecode(encodedImage, isLast);

/**
 * Decodes images.
 *
 * <p/> Progressive JPEGs are decoded progressively as new data arrives.
 */
public class DecodeProducer implements Producer<CloseableReference<CloseableImage>> {

/** Performs the decode synchronously. */
    private void doDecode(EncodedImage encodedImage, boolean isLast) {
      if (isFinished() || !EncodedImage.isValid(encodedImage)) { // 校验
        return;
      }

      try {
        long queueTime = mJobScheduler.getQueuedTime();// 校验
        int length = isLast ?// 校验
            encodedImage.getSize() : getIntermediateImageEndOffset(encodedImage);
        QualityInfo quality = isLast ? ImmutableQualityInfo.FULL_QUALITY : getQualityInfo();// 校验

        // 这个还是ForwardingRequestListener,基本无用,因为里面包装的requstListener是空
        mProducerListener.onProducerStart(mProducerContext.getId(), PRODUCER_NAME); 
        CloseableImage image = null;
        try {
          image = mImageDecoder.decodeImage(encodedImage, length, quality, mImageDecodeOptions); // 按图片类型加载
        } catch (Exception e) {
          Map<String, String> extraMap = getExtraMap(image, queueTime, quality, isLast);
          mProducerListener.
              onProducerFinishWithFailure(mProducerContext.getId(), PRODUCER_NAME, e, extraMap);
          handleError(e);
          return;
        }
        Map<String, String> extraMap = getExtraMap(image, queueTime, quality, isLast);
        mProducerListener.
            onProducerFinishWithSuccess(mProducerContext.getId(), PRODUCER_NAME, extraMap);
        handleResult(image, isLast);
      } finally {
        EncodedImage.closeSafely(encodedImage);
      }
    }
}

mImageDecoder.decodeImage()将会调用ImageDecoder,这个类有点流弊,有谁知道'pinned'这个技术么,希望告诉我详细,关于内存的,以前bitmap保存在Ashmen后来sdk变高以后,bitmap挂载在java heap去维护了

Fresco 的 Image Pipeline 负责图片的获取和管理。

  • 图片可以来自远程服务器,本地文件,或者Content Provider,本地资源。压缩后的文件缓存在本地存储中,Bitmap数据缓存在内存中。
  • 在5.0系统之后,Image Pipeline 使用`pinned purgeables*将Bitmap数据存在native 内存中。这要求图片不使用时,要显示地释放内存。
  • SimpleDraweeView 自动处理了这个释放过程,所以没有特殊情况,尽量使用SimpleDraweeView,在特殊的场合,如果有需要,也可以直接控制Image Pipeline。
Pinned purgeables behave as specified in {@link android.graphics.BitmapFactory.Options#inPurgeable}
public class BitmapFactory {

     public static class Options {
        /**
         * @deprecated As of {@link android.os.Build.VERSION_CODES#LOLLIPOP}, this is
         * ignored.
         *
         * In {@link android.os.Build.VERSION_CODES#KITKAT} and below, if this
         * is set to true, then the resulting bitmap will allocate its
         * pixels such that they can be purged if the system needs to reclaim
         * memory. In that instance, when the pixels need to be accessed again
         * (e.g. the bitmap is drawn, getPixels() is called), they will be
         * automatically re-decoded.
         *
         * <p>For the re-decode to happen, the bitmap must have access to the
         * encoded data, either by sharing a reference to the input
         * or by making a copy of it. This distinction is controlled by
         * inInputShareable. If this is true, then the bitmap may keep a shallow
         * reference to the input. If this is false, then the bitmap will
         * explicitly make a copy of the input data, and keep that. Even if
         * sharing is allowed, the implementation may still decide to make a
         * deep copy of the input data.</p>
         *
         * <p>While inPurgeable can help avoid big Dalvik heap allocations (from
         * API level 11 onward), it sacrifices performance predictability since any
         * image that the view system tries to draw may incur a decode delay which
         * can lead to dropped frames. Therefore, most apps should avoid using
         * inPurgeable to allow for a fast and fluid UI. To minimize Dalvik heap
         * allocations use the {@link #inBitmap} flag instead.</p>
         *
         * <p class="note"><strong>Note:</strong> This flag is ignored when used
         * with {@link #decodeResource(Resources, int,
         * android.graphics.BitmapFactory.Options)} or {@link #decodeFile(String,
         * android.graphics.BitmapFactory.Options)}.</p>
         */
        @Deprecated
        public boolean inPurgeable;
      }
}

基本是说,inPurgeable,造成bitmap被系统回收通过pixels,因此,在fast和fluid ui(快速流畅)避免使用,GC时间长,为了使用小heap资源,sdk给inBitmap标志代替

关于Bitmap的回收机制

系统的Bitmap内存管理机制随着Android系统的演进可以分为三个阶段,关于这部分可以参考官网文章Managing Bitmap Memory

  • 2.3.3及以下:Bitmap的像素数据存储在native内存中,但依旧会计算在一个进程的内存上限之中。
  • 3.0~4.4:Bitmap的像素数据存储在Java堆内存中,解码Bitmap时可以通过Options#inBitmap复用不再使用的Bitmap,从而减少系统gc。但要求被复用的Bitmap和新Bitmap的像素数据一样大。
  • 5.0及以上:对于复用Bitmap的限制不再严格要求一样大,只要别复用的Bitmap的像素数据不小于新Bitmap即可。
/**
 * Decodes images.
 *
 * <p> ImageDecoder implements image type recognition and passes decode requests to
 * specialized methods implemented by subclasses.
 *
 * On dalvik, it produces 'pinned' purgeable bitmaps.
 *
 * <p> Pinned purgeables behave as specified in
 * {@link android.graphics.BitmapFactory.Options#inPurgeable} with one modification. The bitmap is
 * 'pinned' so is never purged.
 *
 * <p> For API 21 and higher, this class produces standard Bitmaps, as purgeability is not supported
 * on the most recent versions of Android.
 */
public class ImageDecoder {

/**
   * Decodes image.
   *
   * @param encodedImage input image (encoded bytes plus meta data)
   * @param length if image type supports decoding incomplete image then determines where
   *   the image data should be cut for decoding.
   * @param qualityInfo quality information for the image
   * @param options options that cange decode behavior
   */
  public CloseableImage decodeImage(
      final EncodedImage encodedImage,
      final int length,
      final QualityInfo qualityInfo,
      final ImageDecodeOptions options) {
    ImageFormat imageFormat = encodedImage.getImageFormat();
    if (imageFormat == null || imageFormat == ImageFormat.UNKNOWN) {
      imageFormat = ImageFormatChecker.getImageFormat_WrapIOException(
          encodedImage.getInputStream());
    }

    switch (imageFormat) {
      case UNKNOWN:
        throw new IllegalArgumentException("unknown image format");

      case JPEG:
        return decodeJpeg(encodedImage, length, qualityInfo);

      case GIF:
        return decodeGif(encodedImage, options);

      case WEBP_ANIMATED:
        return decodeAnimatedWebp(encodedImage, options);

      default:
        return decodeStaticImage(encodedImage);
    }
  }
}

最后这个bitmap的Cahe会传到Multiplexer&ForwardingConsumer:consumer然后找到对应订阅Pair<Consumer<T>, ProducerContext> 然后就回到了最开始AbstractDraweeController:submitRequest()的datasource订阅者了,完成成功,失败,数据持续更新一系列主线程刷新。
好了,差不多介绍完主要的,细节我会持续补充的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容