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

  • Android-Glide源码解析(一)调用流程
  • Android-Glide源码解析(二)线程管理
  • Android-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 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  Resource decodeFromData(DataFetcher fetcher, Data data,
      DataSource dataSource) throws GlideException {
    try {
      if (data == null) {
        return null;
      }
      long startTime = LogTime.getLogTime();
      Resource result = decodeFromFetcher(data, dataSource);
      if (Log.isLoggable(TAG, Log.VERBOSE)) {
        logWithTimeAndKey("Decoded result " + result, startTime);
      }
      return result;
    } finally {
      fetcher.cleanup();
    }
  }

decodeFromData里面又调用了decodeFromFetcher。

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

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

  private  Resource runLoadPath(Data data, DataSource dataSource,
      LoadPath path) throws GlideException {
    Options options = getOptionsWithHardwareConfig(dataSource);
    DataRewinder 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(dataSource));
    } finally {
      rewinder.cleanup();
    }
  }

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

  public Resource load(DataRewinder rewinder, @NonNull Options options, int width,
      int height, DecodePath.DecodeCallback decodeCallback) throws GlideException {
    List 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 loadWithExceptionList(DataRewinder rewinder,
      @NonNull Options options,
      int width, int height, DecodePath.DecodeCallback decodeCallback,
      List exceptions) throws GlideException {
    Resource result = null;
    //noinspection ForLoopReplaceableByForEach to improve perf
    for (int i = 0, size = decodePaths.size(); i < size; i++) {//遍历所有可能的DecodePath。有可能是
      DecodePath 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 decode(DataRewinder rewinder, int width, int height,
      @NonNull Options options, DecodeCallback callback) throws GlideException {
    Resource decoded = decodeResource(rewinder, width, height, options);//解码出原始资源
    Resource transformed = callback.onResourceDecoded(decoded);//新建变换的资源
    return transcoder.transcode(transformed, options);//根据设置,变换成目标资源
  }

先看decodeResource方法。

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

直接看decodeResourceWithList方法

  @NonNull
  private Resource decodeResourceWithList(DataRewinder rewinder, int width,
      int height, @NonNull Options options, List exceptions) throws GlideException {
    Resource result = null;
    //noinspection ForLoopReplaceableByForEach to improve perf
    for (int i = 0, size = decoders.size(); i < size; i++) {
      ResourceDecoder 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 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 decode(InputStream is, int outWidth, int outHeight,
      Options options) throws IOException {
    return decode(is, outWidth, outHeight, options, EMPTY_CALLBACKS);
  }
  public Resource 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知识整理)