Android-Glide源码解析(三)图片处理

Android-Glide源码解析(二)线程管理 Glide源码解析-线程管理 阅读详情

在介绍源码调用流程这篇文章时,对图片处理这块没有详细讲。只是说明了Glide将网络请求回来的数据解码成了相应的Resource。下面我们跟进源码看下解码的详细实现。

解码是发生在DecodeJob#decodeFromRetrievedData方法中。

  private void decodeFromRetrievedData() {
    if (Log.isLoggable(TAG, Log.VERBOSE)) {
      logWithTimeAndKey("Retrieved data", startFetchTime,
          "data: " + currentData
              + ", cache key: " + currentSourceKey
              + ", fetcher: " + currentFetcher);
    }
    Resource<R> resource = null;
    try {
      resource = decodeFromData(currentFetcher, currentData, currentDataSource);
    } catch (GlideException e) {
      e.setLoggingDetails(currentAttemptingKey, currentDataSource);
      throwables.add(e);
    }
    if (resource != null) {
      notifyEncodeAndRelease(resource, currentDataSource);
    } else {
      runGenerators();
    }
  }

接下来到了decodeFromData。

  private <Data> Resource<R> decodeFromData(DataFetcher<?> fetcher, Data data,
      DataSource dataSource) throws GlideException {
    try {
      if (data == null) {
        return null;
      }
      long startTime = LogTime.getLogTime();
      Resource<R> result = decodeFromFetcher(data, dataSource);
      if (Log.isLoggable(TAG, Log.VERBOSE)) {
        logWithTimeAndKey("Decoded result " + result, startTime);
      }
      return result;
    } finally {
      fetcher.cleanup();
    }
  }

decodeFromData里面又调用了decodeFromFetcher。

  private <Data> Resource<R> decodeFromFetcher(Data data, DataSource dataSource)
      throws GlideException {
    LoadPath<Data, ?, R> path = decodeHelper.getLoadPath((Class<Data>) data.getClass());
    return runLoadPath(data, dataSource, path);
  }

decodeFromFetcher方法里面,获取到了加载的路径。然后调用了runLoadPath

  private <Data, ResourceType> Resource<R> runLoadPath(Data data, DataSource dataSource,
      LoadPath<Data, ResourceType, R> path) throws GlideException {
    Options options = getOptionsWithHardwareConfig(dataSource);
    DataRewinder<Data> rewinder = glideContext.getRegistry().getRewinder(data);
    try {
      // ResourceType in DecodeCallback below is required for compilation to work with gradle.
      return path.load(
          rewinder, options, width, height, new DecodeCallback<ResourceType>(dataSource));
    } finally {
      rewinder.cleanup();
    }
  }

DataRewinder。接下来执行到LoadPath.load方法

  public Resource<Transcode> load(DataRewinder<Data> rewinder, @NonNull Options options, int width,
      int height, DecodePath.DecodeCallback<ResourceType> decodeCallback) throws GlideException {
    List<Throwable> throwables = Preconditions.checkNotNull(listPool.acquire());
    try {
      return loadWithExceptionList(rewinder, options, width, height, decodeCallback, throwables);
    } finally {
      listPool.release(throwables);
    }
  }

上面方法的throwables是一个List集合,作为参数传到loadWithExceptionList中,用于存储解码的异常。下面看下LoadPath.loadWithExceptionList方法。

  private Resource<Transcode> loadWithExceptionList(DataRewinder<Data> rewinder,
      @NonNull Options options,
      int width, int height, DecodePath.DecodeCallback<ResourceType> decodeCallback,
      List<Throwable> exceptions) throws GlideException {
    Resource<Transcode> result = null;
    //noinspection ForLoopReplaceableByForEach to improve perf
    for (int i = 0, size = decodePaths.size(); i < size; i++) {//遍历所有可能的DecodePath。有可能是
      DecodePath<Data, ResourceType, Transcode> path = decodePaths.get(i);
      try {
        result = path.decode(rewinder, width, height, options, decodeCallback);
      } catch (GlideException e) {
        exceptions.add(e);
      }
      if (result != null) {
        break;
      }
    }

    if (result == null) {
      throw new GlideException(failureMessage, new ArrayList<>(exceptions));
    }

    return result;
  }

接下来执行DecodePath.decode方法

  public Resource<Transcode> decode(DataRewinder<DataType> rewinder, int width, int height,
      @NonNull Options options, DecodeCallback<ResourceType> callback) throws GlideException {
    Resource<ResourceType> decoded = decodeResource(rewinder, width, height, options);//解码出原始资源
    Resource<ResourceType> transformed = callback.onResourceDecoded(decoded);//新建变换的资源
    return transcoder.transcode(transformed, options);//根据设置,变换成目标资源
  }

先看decodeResource方法。

  @NonNull
  private Resource<ResourceType> decodeResource(DataRewinder<DataType> rewinder, int width,
      int height, @NonNull Options options) throws GlideException {
    List<Throwable> exceptions = Preconditions.checkNotNull(listPool.acquire());//
    try {
      return decodeResourceWithList(rewinder, width, height, options, exceptions);
    } finally {
      listPool.release(exceptions);
    }
  }

直接看decodeResourceWithList方法

  @NonNull
  private Resource<ResourceType> decodeResourceWithList(DataRewinder<DataType> rewinder, int width,
      int height, @NonNull Options options, List<Throwable> exceptions) throws GlideException {
    Resource<ResourceType> result = null;
    //noinspection ForLoopReplaceableByForEach to improve perf
    for (int i = 0, size = decoders.size(); i < size; i++) {
      ResourceDecoder<DataType, ResourceType> decoder = decoders.get(i);//拿到资源解码器
      try {
        DataType data = rewinder.rewindAndGet();//获取原始数据类型
        if (decoder.handles(data, options)) {//判断该解码器能否解码该数据类型的数据。
          data = rewinder.rewindAndGet();//如果可以解码
          result = decoder.decode(data, width, height, options);
        }
        // Some decoders throw unexpectedly. If they do, we shouldn't fail the entire load path, but
        // instead log and continue. See #2406 for an example.
      } catch (IOException | RuntimeException | OutOfMemoryError e) {
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
          Log.v(TAG, "Failed to decode data for " + decoder, e);
        }
        exceptions.add(e);
      }

      if (result != null) {
        break;
      }
    }

    if (result == null) {//如果为空,抛出异常,由上级捕获。然后遍历下一个DecodePath
      throw new GlideException(failureMessage, new ArrayList<>(exceptions));
    }
    return result;
  }

我的不是Gif图,对应的解码器为ByteBufferBitmapDecoder。我们来看下

  @Override
  public Resource<Bitmap> decode(@NonNull ByteBuffer source, int width, int height,
      @NonNull Options options)
      throws IOException {
    InputStream is = ByteBufferUtil.toStream(source);//将ByteBuffer转为InputStream
    return downsampler.decode(is, width, height, options);
  }

Downsampler这个类主要完成采样压缩、解码、旋转的功能。下面执行到了它的decode方法。

  public Resource<Bitmap> decode(InputStream is, int outWidth, int outHeight,
      Options options) throws IOException {
    return decode(is, outWidth, outHeight, options, EMPTY_CALLBACKS);
  }
  public Resource<Bitmap> decode(InputStream is, int requestedWidth, int requestedHeight,
      Options options, DecodeCallbacks callbacks) throws IOException {
    Preconditions.checkArgument(is.markSupported(), "You must provide an InputStream that supports"
        + " mark()");

    byte[] bytesForOptions = byteArrayPool.get(ArrayPool.STANDARD_BUFFER_SIZE_BYTES, byte[].class);//从byteArrayPool取出一个大小为65536数组,用于本次解码。byteArrayPool默认大小为4M。
    BitmapFactory.Options bitmapFactoryOptions = getDefaultOptions();//获取默认的decode bitmap参数。inSampleZize=1,表示1:1采样。
    bitmapFactoryOptions.inTempStorage = bytesForOptions;//将缓存数组关联到解码Option配置里。

    DecodeFormat decodeFormat = options.get(DECODE_FORMAT);//默认解码格式为PREFER_ARGB_8888
    DownsampleStrategy downsampleStrategy = options.get(DownsampleStrategy.OPTION);//图片的缩放策略,fit_center、fit_xy等等。
    boolean fixBitmapToRequestedDimensions = options.get(FIX_BITMAP_SIZE_TO_REQUESTED_DIMENSIONS);
    // 查询是否支持Bitmap.Config#HARDWARE。硬件位图是Android 8.0新推出的一种位图。详细介绍见文章最后。
    boolean isHardwareConfigAllowed =
      options.get(ALLOW_HARDWARE_CONFIG) != null && options.get(ALLOW_HARDWARE_CONFIG);

    try {
      Bitmap result = decodeFromWrappedStreams(is, bitmapFactoryOptions,
          downsampleStrategy, decodeFormat, isHardwareConfigAllowed, requestedWidth,
          requestedHeight, fixBitmapToRequestedDimensions, callbacks);
      return BitmapResource.obtain(result, bitmapPool);
    } finally {
      releaseOptions(bitmapFactoryOptions);
      byteArrayPool.put(bytesForOptions);//解码完成后,将分配的临时数组放回到byte数组池中。
    }
  }

真正解码操作在decodeFromWrappedStreams这个方法里。

  private Bitmap decodeFromWrappedStreams(InputStream is,
      BitmapFactory.Options options, DownsampleStrategy downsampleStrategy,
      DecodeFormat decodeFormat, boolean isHardwareConfigAllowed, int requestedWidth,
      int requestedHeight, boolean fixBitmapToRequestedDimensions,
      DecodeCallbacks callbacks) throws IOException {
    long startTime = LogTime.getLogTime();

    int[] sourceDimensions = getDimensions(is, options, callbacks, bitmapPool);//获取原始图片的大小。
    int sourceWidth = sourceDimensions[0];
    int sourceHeight = sourceDimensions[1];
    String sourceMimeType = options.outMimeType;//资源类型

    // If we failed to obtain the image dimensions, we may end up with an incorrectly sized Bitmap,
    // so we want to use a mutable Bitmap type. One way this can happen is if the image header is so
    // large (10mb+) that our attempt to use inJustDecodeBounds fails and we're forced to decode the
    // full size image.
    if (sourceWidth == -1 || sourceHeight == -1) {
      isHardwareConfigAllowed = false;
    }

    int orientation = ImageHeaderParserUtils.getOrientation(parsers, is, byteArrayPool);//获取原始图片的方向。根据解析图片的EXIF(可交换图像文件格式)信息来获得。
    int degreesToRotate = TransformationUtils.getExifOrientationDegrees(orientation);//获取旋转角度
    boolean isExifOrientationRequired = TransformationUtils.isExifOrientationRequired(orientation);//原始图片是否有旋转

    int targetWidth = requestedWidth == Target.SIZE_ORIGINAL ? sourceWidth : requestedWidth;
    int targetHeight = requestedHeight == Target.SIZE_ORIGINAL ? sourceHeight : requestedHeight;

    ImageType imageType = ImageHeaderParserUtils.getType(parsers, is, byteArrayPool);//获取原始图片类型。

//计算缩放值
    calculateScaling(
        imageType,
        is,
        callbacks,
        bitmapPool,
        downsampleStrategy,
        degreesToRotate,
        sourceWidth,
        sourceHeight,
        targetWidth,
        targetHeight,
        options);
        //计算配置。包括inPreferredConfig和inDither
    calculateConfig(
        is,
        decodeFormat,
        isHardwareConfigAllowed,
        isExifOrientationRequired,
        options,
        targetWidth,
        targetHeight);

    boolean isKitKatOrGreater = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
    // Prior to KitKat, the inBitmap size must exactly match the size of the bitmap we're decoding.
    if ((options.inSampleSize == 1 || isKitKatOrGreater) && shouldUsePool(imageType)) {//如果Android版本大于4.4或者不进行采样率压缩
      int expectedWidth;
      int expectedHeight;
      if (sourceWidth >= 0 && sourceHeight >= 0
          && fixBitmapToRequestedDimensions && isKitKatOrGreater) {
        expectedWidth = targetWidth;
        expectedHeight = targetHeight;
      } else {
        float densityMultiplier = isScaling(options)
            ? (float) options.inTargetDensity / options.inDensity : 1f;
        int sampleSize = options.inSampleSize;
        int downsampledWidth = (int) Math.ceil(sourceWidth / (float) sampleSize);
        int downsampledHeight = (int) Math.ceil(sourceHeight / (float) sampleSize);
        expectedWidth = Math.round(downsampledWidth * densityMultiplier);
        expectedHeight = Math.round(downsampledHeight * densityMultiplier);
		**//公式:输出图片的宽高= 原图片的宽高 / inSampleSize * (inTargetDensity / inDensity)**
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
          Log.v(TAG, "Calculated target [" + expectedWidth + "x" + expectedHeight + "] for source"
              + " [" + sourceWidth + "x" + sourceHeight + "]"
              + ", sampleSize: " + sampleSize
              + ", targetDensity: " + options.inTargetDensity
              + ", density: " + options.inDensity
              + ", density multiplier: " + densityMultiplier);
        }
      }
      // If this isn't an image, or BitmapFactory was unable to parse the size, width and height
      // will be -1 here.
      if (expectedWidth > 0 && expectedHeight > 0) {
        setInBitmap(options, bitmapPool, expectedWidth, expectedHeight);
      }
    }
    Bitmap downsampled = decodeStream(is, options, callbacks, bitmapPool);//根据options配置,解码最终的图片。
    callbacks.onDecodeComplete(bitmapPool, downsampled);

    if (Log.isLoggable(TAG, Log.VERBOSE)) {
      logDecode(sourceWidth, sourceHeight, sourceMimeType, options, downsampled,
          requestedWidth, requestedHeight, startTime);
    }

    Bitmap rotated = null;
    if (downsampled != null) {
      // If we scaled, the Bitmap density will be our inTargetDensity. Here we correct it back to
      // the expected density dpi.
      downsampled.setDensity(displayMetrics.densityDpi);

      rotated = TransformationUtils.rotateImageExif(bitmapPool, downsampled, orientation);//如果需要旋转,则对图片进行旋转
      if (!downsampled.equals(rotated)) {
        bitmapPool.put(downsampled);//如果采样的之后的Bitmap和旋转之后的不是同一个,说明有进行旋转操作。将采样的这个Bitmap丢到bitmap池子中。
      }
    }

    return rotated;
  }

我们来看一下获取原始图片的实现

  private static int[] getDimensions(InputStream is, BitmapFactory.Options options,
      DecodeCallbacks decodeCallbacks, BitmapPool bitmapPool) throws IOException {
    options.inJustDecodeBounds = true;//设置为true,表示解码的时候不给bitmap分配内存,只返回bitmap的边界信息。
    decodeStream(is, options, decodeCallbacks, bitmapPool);//将inputstream解码。
    options.inJustDecodeBounds = false;//解码得到宽高信息后,将inJustDecodeBounds恢复为false。
    return new int[] { options.outWidth, options.outHeight };
  }
  private static Bitmap decodeStream(InputStream is, BitmapFactory.Options options,
      DecodeCallbacks callbacks, BitmapPool bitmapPool) throws IOException {
    if (options.inJustDecodeBounds) {
      is.mark(MARK_POSITION);
    } else {
      // Once we've read the image header, we no longer need to allow the buffer to expand in
      // size. To avoid unnecessary allocations reading image data, we fix the mark limit so that it
      // is no larger than our current buffer size here. We need to do so immediately before
      // decoding the full image to avoid having our mark limit overridden by other calls to
      // mark and reset. See issue #225.
      callbacks.onObtainBounds();
    }
    // BitmapFactory.Options out* variables are reset by most calls to decodeStream, successful or
    // otherwise, so capture here in case we log below.
    int sourceWidth = options.outWidth;
    int sourceHeight = options.outHeight;
    String outMimeType = options.outMimeType;
    final Bitmap result;
    TransformationUtils.getBitmapDrawableLock().lock();
    try {
      result = BitmapFactory.decodeStream(is, null, options);
    } catch (IllegalArgumentException e) {
      IOException bitmapAssertionException =
          newIoExceptionForInBitmapAssertion(e, sourceWidth, sourceHeight, outMimeType, options);
      if (Log.isLoggable(TAG, Log.DEBUG)) {
        Log.d(TAG, "Failed to decode with inBitmap, trying again without Bitmap re-use",
            bitmapAssertionException);
      }
      if (options.inBitmap != null) {
        try {
          is.reset();
          bitmapPool.put(options.inBitmap);
          options.inBitmap = null;
          return decodeStream(is, options, callbacks, bitmapPool);
        } catch (IOException resetException) {
          throw bitmapAssertionException;
        }
      }
      throw bitmapAssertionException;
    } finally {
      TransformationUtils.getBitmapDrawableLock().unlock();
    }

    if (options.inJustDecodeBounds) {
      is.reset();

    }
    return result;
  }

计算缩放值的方法calculateScaling

private static void calculateScaling(
      ImageType imageType,
      InputStream is,
      DecodeCallbacks decodeCallbacks,
      BitmapPool bitmapPool,
      DownsampleStrategy downsampleStrategy,
      int degreesToRotate,
      int sourceWidth,
      int sourceHeight,
      int targetWidth,
      int targetHeight,
      BitmapFactory.Options options) throws IOException {
    // We can't downsample source content if we can't determine its dimensions.
    if (sourceWidth <= 0 || sourceHeight <= 0) {
      if (Log.isLoggable(TAG, Log.DEBUG)) {
        Log.d(TAG, "Unable to determine dimensions for: " + imageType
            + " with target [" + targetWidth + "x" + targetHeight + "]");
      }
      return;
    }

    final float exactScaleFactor;
    //计算缩放比例,DownsampleStrategy不同,计算方式也不一样。
    //默认是DownsampleStrategy是FIT_CENTER
   //exactScaleFactor计算方式:requestedWidth/sourceWidth,再取宽高的较小值

    if (degreesToRotate == 90 || degreesToRotate == 270) {//如果原始图片旋转了90度或者270度,交换一下getScaleFactor的参数就好。
      // If we're rotating the image +-90 degrees, we need to downsample accordingly so the image
      // width is decreased to near our target's height and the image height is decreased to near
      // our target width.
      //noinspection SuspiciousNameCombination
      exactScaleFactor = downsampleStrategy.getScaleFactor(sourceHeight, sourceWidth,
          targetWidth, targetHeight);
    } else {
      exactScaleFactor =
          downsampleStrategy.getScaleFactor(sourceWidth, sourceHeight, targetWidth, targetHeight);
    }

    if (exactScaleFactor <= 0f) {
      throw new IllegalArgumentException("Cannot scale with factor: " + exactScaleFactor
          + " from: " + downsampleStrategy
          + ", source: [" + sourceWidth + "x" + sourceHeight + "]"
          + ", target: [" + targetWidth + "x" + targetHeight + "]");
    }
    SampleSizeRounding rounding = downsampleStrategy.getSampleSizeRounding(sourceWidth,
        sourceHeight, targetWidth, targetHeight);
    if (rounding == null) {
      throw new IllegalArgumentException("Cannot round with null rounding");
    }

    int outWidth = round(exactScaleFactor * sourceWidth);//向上取整
    int outHeight = round(exactScaleFactor * sourceHeight);

    int widthScaleFactor = sourceWidth / outWidth;//宽缩放倍数
    int heightScaleFactor = sourceHeight / outHeight;//高缩放倍数

    int scaleFactor = rounding == SampleSizeRounding.MEMORY
        ? Math.max(widthScaleFactor, heightScaleFactor)
        : Math.min(widthScaleFactor, heightScaleFactor);

    int powerOfTwoSampleSize;
    // BitmapFactory does not support downsampling wbmp files on platforms <= M. See b/27305903.
    if (Build.VERSION.SDK_INT <= 23
        && NO_DOWNSAMPLE_PRE_N_MIME_TYPES.contains(options.outMimeType)) {
      powerOfTwoSampleSize = 1;//Android 6.0以下不支持采样率压缩。
    } else {
      powerOfTwoSampleSize = Math.max(1, Integer.highestOneBit(scaleFactor));
      if (rounding == SampleSizeRounding.MEMORY
          && powerOfTwoSampleSize < (1.f / exactScaleFactor)) {
        powerOfTwoSampleSize = powerOfTwoSampleSize << 1;//如果Sample策略是SampleSizeRounding.MEMORY即是省内存的,并且计算出来的inSampleSize<(原始宽/request宽),那么再把inSampleSize扩大一倍。
      }
    }

    // Here we mimic framework logic for determining how inSampleSize division is rounded on various
    // versions of Android. The logic here has been tested on emulators for Android versions 15-26.
    // PNG - Always uses floor
    // JPEG - Always uses ceiling
    // Webp - Prior to N, always uses floor. At and after N, always uses round.
    options.inSampleSize = powerOfTwoSampleSize;//把最终计算好的inSampleSize设置到options里
    int powerOfTwoWidth;
    int powerOfTwoHeight;
    if (imageType == ImageType.JPEG) {//如果是JPEG图片
      // libjpegturbo can downsample up to a sample size of 8. libjpegturbo uses ceiling to round.
      // After libjpegturbo's native rounding, skia does a secondary scale using floor
      // (integer division). Here we replicate that logic.
      int nativeScaling = Math.min(powerOfTwoSampleSize, 8);
      powerOfTwoWidth = (int) Math.ceil(sourceWidth / (float) nativeScaling);//向上取整,计算宽
      powerOfTwoHeight = (int) Math.ceil(sourceHeight / (float) nativeScaling);//向上取整,计算高
      int secondaryScaling = powerOfTwoSampleSize / 8;
      if (secondaryScaling > 0) {//如果powerOfTwoSampleSize>=8,计算第二次缩放后的宽高
        powerOfTwoWidth = powerOfTwoWidth / secondaryScaling;
        powerOfTwoHeight = powerOfTwoHeight / secondaryScaling;
      }
    } else if (imageType == ImageType.PNG || imageType == ImageType.PNG_A) {//如果是png图片
      powerOfTwoWidth = (int) Math.floor(sourceWidth / (float) powerOfTwoSampleSize);//根据之前计算的inSampleSize来计算宽高
      powerOfTwoHeight = (int) Math.floor(sourceHeight / (float) powerOfTwoSampleSize);
    } else if (imageType == ImageType.WEBP || imageType == ImageType.WEBP_A) {
      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        powerOfTwoWidth = Math.round(sourceWidth / (float) powerOfTwoSampleSize);
        powerOfTwoHeight = Math.round(sourceHeight / (float) powerOfTwoSampleSize);
      } else {
        powerOfTwoWidth = (int) Math.floor(sourceWidth / (float) powerOfTwoSampleSize);
        powerOfTwoHeight = (int) Math.floor(sourceHeight / (float) powerOfTwoSampleSize);
      }
    } else if (
        sourceWidth % powerOfTwoSampleSize != 0 || sourceHeight % powerOfTwoSampleSize != 0) {
      // If we're not confident the image is in one of our types, fall back to checking the
      // dimensions again. inJustDecodeBounds decodes do obey inSampleSize.
      int[] dimensions = getDimensions(is, options, decodeCallbacks, bitmapPool);
      // Power of two downsampling in BitmapFactory uses a variety of random factors to determine
      // rounding that we can't reliably replicate for all image formats. Use ceiling here to make
      // sure that we at least provide a Bitmap that's large enough to fit the content we're going
      // to load.
      powerOfTwoWidth = dimensions[0];
      powerOfTwoHeight = dimensions[1];
    } else {
      powerOfTwoWidth = sourceWidth / powerOfTwoSampleSize;
      powerOfTwoHeight = sourceHeight / powerOfTwoSampleSize;
    }

    double adjustedScaleFactor = downsampleStrategy.getScaleFactor(
        powerOfTwoWidth, powerOfTwoHeight, targetWidth, targetHeight);

    // Density scaling is only supported if inBitmap is null prior to KitKat. Avoid setting
    // densities here so we calculate the final Bitmap size correctly.
	//计算TargetDensity和Density的值,用于计算最终Bitmap的大小  
	//公式:输出图片的宽高= 原图片的宽高 / inSampleSize * (inTargetDensity / inDensity)
	
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
      options.inTargetDensity = adjustTargetDensityForError(adjustedScaleFactor);
      options.inDensity = getDensityMultiplier(adjustedScaleFactor);
    }
    if (isScaling(options)) {
      options.inScaled = true;
    } else {
      options.inDensity = options.inTargetDensity = 0;
    }

    if (Log.isLoggable(TAG, Log.VERBOSE)) {
      Log.v(TAG, "Calculate scaling"
          + ", source: [" + sourceWidth + "x" + sourceHeight + "]"
          + ", target: [" + targetWidth + "x" + targetHeight + "]"
          + ", power of two scaled: [" + powerOfTwoWidth + "x" + powerOfTwoHeight + "]"
          + ", exact scale factor: " + exactScaleFactor
          + ", power of 2 sample size: " + powerOfTwoSampleSize
          + ", adjusted scale factor: " + adjustedScaleFactor
          + ", target density: " + options.inTargetDensity
          + ", density: " + options.inDensity);
    }
  }

到这里Glide的图片处理就算是完成了。总结一下主要步骤:

  1. 首先根据网络请求返回的原始InputStream解码出原始图片的宽高、方向、旋转角度这些信息。
  2. 根据原始图片的信息计算inSampleSize、inTargetDensity和inDensity。
  3. 解码出目标的bitmap。
  4. 旋转图片。返回。
附录

下面关于硬件位图的介绍,摘自官方文档

什么是硬件位图(Hardware Bitmaps)?

Bitmap.Config.HARDWARE 是一种 Android O 添加的新的位图格式。硬件位图仅在显存 (graphic memory) 里存储像素数据,并对图片仅在屏幕上绘制的场景做了优化。

我们为什么应该使用硬件位图?

因为硬件位图仅储存像素数据的一份副本。一般情况下,应用内存中有一份像素数据(即像素字节数组),而在显存中还有一份副本(在像素被上传到 GPU之后)。而硬件位图仅持有 GPU 中的副本,因此:

硬件位图仅需要一半于其他位图配置的内存;
硬件位图可避免绘制时上传纹理导致的内存抖动。

干货分享---Android框架——Glide4-9-0的详细分析及介绍 图片加载框架一般由如下几部分构成:(1) RequestManager:请求生成和管理模块(2) Engine:引擎部分,负责创建任务(获取数据),并调度执行(3) GetDataInterface:数据获取接口,负责从各个数据源获取数据。比如 MemoryCache 从内存缓存获取数据、DiskCache 从本地缓存获取数据,下载器从网络获取数据等。(4) Displayer:资源(图片)显示器,用于显示或操作资源。 阅读详情

相关推荐

深入解析Glide源码:Android图片加载的实现

在图片加载过程中,Glide提供了丰富的加载选项,通过类来设置,以便于开发者根据实际需求定制加载行为。Glide的加载选项包括但不限于:图像尺寸、缓存策略、解码类型等。// 代码块展示了如何使用RequestOptions自定义图片加载行为.override(100, 100) // 设置加载图片的尺寸.centerCrop() // 设置图片的裁剪方式// 设置磁盘缓存策略。

weixin_35257663的博客 1042

Android图片处理之Glide使用大全

Android Glide是一个开源的图片加载和缓存处理的第三方框架。和Android的Picasso库类似,个人感觉比Android Picasso好用。Android Glide使自身内部已经实现了缓存策略,使得开发者摆脱Android图片加载的琐碎事务,专注逻辑业务的代码。Android Glide使用便利,短短几行简单明晰的代码,即可完成大多数图片从网络(或者本地)加载、显示的功能需求。

Losileeya 4万+

Android图片加密、解密、切割(源码)

支持图片加解密,支持文件字节流输入输出,支持按宫格切割图片,支持按像素自定义切割图片等多种方法。

Android 图片加载框架Glide源码详解

值得看的是currentGenerate是下图中3个的哪一个呢,从逻辑上看3个会依次遍历,分别是先处理过的resource缓存,然后是data缓存,最后是Source网络加载, 这也是glide的三级缓存。下面就要开始讲根据model,data找modelloader的逻辑了,做好心理准备,不简单,网上大都是一笔带过(因为他们也弄不清,的确有点复杂,绕来绕去),好不容易全网找到一篇,不知道是他的版本旧的,还是他理解的不对,珍惜吧,我认为是全篇最难的地方了。给你们看一下添加的listener。

门徒07的博客 761

Android图片各种处理效果源码

Android图片效果: "图片缩放", "图片圆角", "图片倒影", "旋转图片", "图片反转", "图片色调饱和度、色相、亮度处理", "涂鸦,水印", "图片上写文字", "怀旧效果", "模糊效果" ,"柔化效果(高斯模糊)", "浮雕效果", "锐化效果", "底片效果", "光照效果", "图片裁剪", "素描"

Android图片处理源码

Android图片处理源码,

Android Glide源码解析

源码方面主要从最基本的三个方法入手:with,load,into。Glide.with可以看到,每个重载方法内部都首先调用getRetriever()方法获取一个RequestManagerRetriever对象,然后调用其get方法来返回RequestManager。传入getRetriever()的参数都是Context,而RequestManagerRetriever.get()方法传入的参数各不相同,所以生命周期的绑定肯定发生在get方法中。下面,接着分析getRetriever()方法。

Dream It Possible 932

Android 常用图片特效处理源码.zip

Android 常用图片特效处理源码.zip

Android Glide使用详解

Glide 是一个快速高效的 Android 开源媒体管理和图像加载框架,它将媒体解码、内存和磁盘缓存以及资源池封装到一个简单易用的界面中。 Glide 支持拉取,解码和展示视频快照,图片,和GIF动画。 Glide的Api十分灵活,开发者甚至可以插入和替换成自己喜爱的任何网络栈。默认情况下,Glide使用的是一个定制化的基于HttpUrlConnection的栈,但同时也提供了与Google Volley和Square OkHttp快速集成的工具库。

帅次的博客 1万+

python特征选择pso_【来点干货】机器学习中常用的特征选择方法及非常详细的Python实例...

花费了很长时间整理编辑,转载请联系作者授权,违者必究。特征选择(Feature selection)是在构建预测模型的过程中减少输入变量的一个过程。它是机器学习中非常重要的一步并在很大程度上可以提高模型预测精度。这里我总结了一些机器学习中常见的比较有用的特征选择方法并附上相关python实现code。希望可以给大家一些启发。首先,我们为什么要进行特征选择呢?它有以下几个优点:减少过拟合:冗余数据常...

weixin_39927158的博客 1161

保姆级教程:用Cesium的Primitive API实现火箭发射全流程动画(含代码避坑)

本文详细介绍了如何使用Cesium的Primitive API实现火箭发射全流程动画,包括模型控制、关节动画和精细时序管理。通过实战代码示例和性能优化技巧,帮助开发者高效构建高复杂度航天仿真系统,显著提升三维可视化效果与运行效率。

weixin_42565652的博客 212

Android之图片处理 Glide4源码解析

这里写目录标题一级目录二级目录三级目录 一级目录 二级目录 三级目录

lizhichao628的专栏 1523

从‘最速控制’到代码:深入拆解韩京清ADRC中TD跟踪器的fhan函数(含Matlab/Simulink实例)

本文深入解析韩京清ADRC中的fhan函数,从理论到代码实现,详细拆解其在Matlab/Simulink中的应用。通过分析最速控制问题和非线性TD的连续形式,揭示fhan函数在信号处理和噪声抑制中的独特优势,并提供工程实践中的参数整定和优化技巧。

weixin_42659194的博客 246

Glide:用法、原理和源码解析

Glide是一款强大的Android图片加载库,它提供了简单易用的API和高效的图片加载功能。本文将介绍Glide的用法、原理和源码解析,帮助开发者更好地理解和使用这个优秀的图片加载库。

陆业聪 5867

永磁同步电机矢量控制——电流内环前馈解耦策略

在采用永磁同步电机矢量控制时,ud和uq的指令值存在耦合项。需要对其进行解耦,目前,常用的解耦设计方法有前馈解耦、偏差解耦以及内模控制,本文主要分享电流内环前馈解耦控制策略。

a12312341ll的博客 8691

19-5 ACC 自适应巡航控制系统需求

本专题由深圳季连AIgraphX李博及团队出品,主要讲解ACC自适应巡航控制系统的开发与仿真测试。具体说来,包括ACC标准/法规分析、系统需求、系统架构、软件需求、软件架构、算法建模、验证场景分析、仿真实验搭建等内容。 本节主要讲ACC系统需求。

AIgraphX 1088

网络编程与图片处理:Volley与Glide源码解析

网络编程与图片处理:Volley与Glide源码解析 本文深入分析了Android开发中两个核心框架:网络请求框架Volley和图片加载框架Glide的源码实现与架构设计。首先详细对比了Volley、OkHttp和Retrofit三种网络框架的架构设计、性能表现和适用场景,然后重点解析了Glide的四级缓存架构、内存管理机制和Bitmap复用策略,最后探讨了HTTP协议优化和大文件断点续传的实现原...

gitblog_00462的博客 357

【工具篇】万字长文拆解 Otter.ai:语音转文字领域的「瑞士军刀」是如何炼成的?(附保姆级教程)

但需要注意,2024年欧盟已出台《AI转录伦理指南》,要求所有录音需获得参与者明确授权。举个真实案例:某硅谷创业团队用Otter.ai记录产品评审会,系统自动标红争议点并生成待办事项,会议效率提升40%。AIGC技术原理、 实时转录解析、 定价策略分析、 语音算法拆解、 多平台兼容测试、 深度学习框架、 空间计算专利。——无论是跨国会议、课堂讲座还是深夜灵感,它都能实时生成带时间戳的文本记录,准确率高达95%。(此处建议插入Otter.ai会议转录动图,展示实时字幕生成效果)(数据来源:综合参考文献整理)

商务合作|问题讨论|交流学习 请联系作者微信,加微信请务必注明来意,博客主页有联系方式 1259

Android-Glide源码解析(一)调用流程

之前在做一个视频播放器项目时,由于该项目的时间比较长,代码也是经过了很多人手。这个项目的重点之一就是图片的加载。每个人对图片加载框架的喜好以及熟悉程度都不一样,导致了该项目里面有好几个ImageLoader。这样对系统资源是一个很大的浪费,维护起来也很麻烦。 接手之后第一件事就是统一图片加载框架。图片加载框架比较火的有Glide、Fresco和Picasso。试过用Fresco去加载一个几十张图片...

HuangLin的博客 863

PCL 区域生长分割【2026最新版】

区域生长算法原理介绍、代码实现以及一个具体实验场景的应用案例进一步实现将分割结果的分类保存至本地文件夹。最近一次更新时间为:2026年5月4日。

点云侠的博客 40万+

130页5万字某市档案馆数字档案馆建设方案

【版权声明】本资料来源网络,仅用于行业知识分享,供个人学习参考,请勿商用。【侵删致歉】如有侵权请联系小编,将在收到信息后第一时间进行删除!完整资料领取见文末,部分资料内容:国家档案局证实颁布《数字档案馆建设指南》, 明确了数字档案馆建设原则、要求、目标,对相关系统建设内容、功能要求进行了概括和详细说明。数字档案馆系统建设应以 OAIS&nbsp;参考模型为设计基础,以实现数字资源的长期保存与可信管理。设计的数字档案馆系统架构如下图。图-1 数字档案馆体系结构数字档案馆建设包括五大层次和三大保障体系:

zuoan1993的博客 1180

Android - 图片处理之Glide4.0

郭大神关于Glide文章的连接,很详细 Android图片加载框架最全解析(一),Glide的基本用法 Android图片加载框架最全解析(二),从源码的角度理解Glide的执行流程 Android图片加载框架最全解析(三),深入探究Glide的缓存机制 Android图片加载框架最全解析(四),玩转Glide的回调与监听 Android图片加载框架最全解析(五),Glide强大的图片...

wa172126691的博客 780

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

Glide 源码解读

目录导读三大主流图片框架对比 导读   时隔将近半个月,又到了要学习源码的时候。那么这里我最近就Glide进行一个源码的解读。至于为什么选择Glide进行源码解读,我们开始会就目前三大主流的图片框架进行简要比较,然后根据Glide的三大方法(with,load,into)进行源码解读,再在源码的层面上进行分析,发现一些Glide的其他神奇用法,那么闲话不多说,直接开始吧! 三大主流图片框架对比   再说主流框架之前,先提一个老伙计,也就是Universal ImageLoader,这是由白俄罗斯的Serge

number_cmd9的博客 654

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

Android图片处理框架之Glide学习使用

Glide 一个专注于平滑滚动的图片加载和缓存库

Science Explorer 611

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

Glide WebP动图压缩技术深度解析:Android图片加载性能优化指南

在Android应用开发中,动图加载性能优化一直是技术挑战的焦点。传统GIF格式动图体积庞大、加载缓慢、内存占用高,严重影响了用户体验。Glide作为专注于流畅滚动体验的图片加载库,通过WebP动图压缩技术实现了高达60%的文件体积缩减,同时保持优秀的视觉质量,为Android开发者提供了高效的技术解决方案。 ## 技术痛点分析:传统动图加载的性能瓶颈 Android应用中的动图加载面临多重挑

gitblog_00274的博客 1087

glide源码中包含了那种设计模式_简析Glide源码

什么是GlideAn image loading and caching library for Android focused on smooth scrolling一个使得Android图片列表滑动时更加流畅的图片加载和缓存库。Glide 优点(1) Glide 不仅是支持图片缓存和加载,它支持 Gif、WebP、缩略图、Video(2) 支持优先级处理(3) 与 Activity/Fragm...

weixin_39789690的博客 604
上一篇: Android-Glide源码解析(二)线程管理
下一篇: Android-Glide源码解析(一)调用流程
Huang-Lin
博客等级 码龄10年 20粉丝 58原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值