Glide的图片格式判断逻辑

如何判断一张图片是否为GIF图
  • 根据后缀名判断:一般服务器返回图片时都有后缀名,这个时候我们可以根据文件的后缀名来判断;但是根据后缀名判断不可靠,有可能是用户手动修改的文件后缀名

  • 根据头信息判断:计算机存储数据是以二进制字节码存储,字节码的前几位标识了文件类型,也就是头信息;相对于gif我们可以取文件的前三个字节看是否为0x474946GIF文件

  • 使用BitmapFactory.Options来判断:BitmapFactory.Options有一个属性outMimeType,这个属性就标识了文件的具体类型,我们可以根据这个mimeType来获取文件的类型

    private fun getBitmapType(bitmapPath: String) {
        val options = BitmapFactory.Options()
        options.inJustDecodeBounds = true
        BitmapFactory.decodeFile(bitmapPath, options)
        options.inJustDecodeBounds = false
        val mimeType = options.outMimeType
        Log.d("图片类型", mimeType)
    }
    
Glide如何判断一张图片是什么类型

首先,我们还是根据源码走吧

Glide.with(this).load(samplePath).into(sampleImageView)

我们从into()方法开始,作为我们分析源码的入口

public ViewTarget<ImageView, TranscodeType> into(@NonNull ImageView view) {
    Util.assertMainThread();
    Preconditions.checkNotNull(view);

    return into(
        glideContext.buildImageViewTarget(view, transcodeClass),
        /*targetListener=*/ null,
        requestOptions);
}
private <Y extends Target<TranscodeType>> Y into() {
    Request request = buildRequest(target, targetListener, options);

    requestManager.clear(target);
    target.setRequest(request);
    requestManager.track(target, request);

    return target;
}

先是创建了一个ImageViewTarget,然后创建一个Request,最后执行了这个请求,并返回target;主要逻辑应该是执行请求,那我们进入这个track方法

void track(@NonNull Target<?> target, @NonNull Request request) {
    targetTracker.track(target);
    requestTracker.runRequest(request);
}

继续进入runRequest逻辑

public void runRequest(@NonNull Request request) {
  requests.add(request);
  request.begin();
}

然后进入SingleRequest#begin()中的onSizeReady()

@Override
public void onSizeReady(int width, int height) {
  loadStatus = engine.load(xxx);
}
public <R> LoadStatus load() {
  EngineJob<R> engineJob =engineJobFactory.build(xxx);
  DecodeJob<R> decodeJob =decodeJobFactory.build(xxxx);
  jobs.put(key, engineJob);
  
  engineJob.addCallback(cb);
  engineJob.start(decodeJob);
  return new LoadStatus(cb, engineJob);
}

这里创建了一些列解码任务,然后去执行任务,这里我们只需要去查看DecodeJob的run执行逻辑

public void run() {
    runWrapped();
}
private void runWrapped() {
  switch (runReason) {
    case INITIALIZE:
      stage = getNextStage(Stage.INITIALIZE);
      currentGenerator = getNextGenerator();
      runGenerators();
      break;
    case SWITCH_TO_SOURCE_SERVICE:
      runGenerators();
      break;
    case DECODE_DATA:
      decodeFromRetrievedData();
      break;
    default:
      throw new IllegalStateException("Unrecognized run reason: " + runReason);
  }
}

然后查看解码数据的逻辑decodeFromRetrievedData()

private void decodeFromRetrievedData() {
  Resource<R> resource = null;
  try {
    resource = decodeFromData(currentFetcher, currentData, currentDataSource);
  } catch (GlideException e) {
    e.setLoggingDetails(currentAttemptingKey, currentDataSource);
    throwables.add(e);
  }
}
private <Data> Resource<R> decodeFromData(xxx) throws GlideException {
  try {
    long startTime = LogTime.getLogTime();
    Resource<R> result = decodeFromFetcher(data, dataSource);
    return result;
  } finally {
    fetcher.cleanup();
  }
}
private <Data> Resource<R> decodeFromFetcher(xxx)throws GlideException {
  LoadPath<Data, ?, R> path = decodeHelper.getLoadPath((Class<Data>) data.getClass());
  return runLoadPath(data, dataSource, path);
}
private <Data, ResourceType> Resource<R> runLoadPath(xxx) throws GlideException {
  Options options = getOptionsWithHardwareConfig(dataSource);
  DataRewinder<Data> rewinder = glideContext.getRegistry().getRewinder(data);
  try {
    return path.load(
        rewinder, options, width, height, new DecodeCallback<ResourceType>(dataSource));
  } finally {
    rewinder.cleanup();
  }
}

然后查看path.load()

public Resource<Transcode> load(xxx) throws GlideException {
    return loadWithExceptionList(rewinder, options, width, height, decodeCallback, throwables);
}
private Resource<Transcode> loadWithExceptionList(xxx) throws GlideException {
    for (int i = 0, size = decodePaths.size(); i < size; i++) {
        DecodePath<Data, ResourceType, Transcode> path = decodePaths.get(i);
        result = path.decode(rewinder, width, height, options, decodeCallback);
    }
  return result;
}
public Resource<Transcode> decode(xxx) throws GlideException {
  Resource<ResourceType> decoded = decodeResource(rewinder, width, height, options);
  Resource<ResourceType> transformed = callback.onResourceDecoded(decoded);
  return transcoder.transcode(transformed, options);
}
private Resource<ResourceType> decodeResource(xxx) throws GlideException {
    return decodeResourceWithList(rewinder, width, height, options, exceptions);
}
private Resource<ResourceType> decodeResourceWithList(xxx) throws GlideException {
    Resource<ResourceType> result = null;
    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);
            }
        } catch (IOException | RuntimeException | OutOfMemoryError e) {
            exceptions.add(e);
        }
    }
    
    return result;
}

主要的处理逻辑就在这里了,这里会获取到一个decoders列表,然后把sourceData让每一个decoder调用handles()去尝试处理,如果这个decoder能够处理这个data,就会调用自己的decode方法去处理这个数据,然后得到一个Resource;而decoder的handles方法就是去判断文件类型的,而这decoders集合是在Glide初始化的时候添加的;那么gif文件的判断是ByteBufferGifDecoder处理的

public boolean handles(@NonNull ByteBuffer source, @NonNull Options options) throws IOException {
  return !options.get(GifOptions.DISABLE_ANIMATION)
      && ImageHeaderParserUtils.getType(parsers, source) == ImageType.GIF;
}
public static ImageType getType(xxx)throws IOException {
    if (buffer == null) {
      return ImageType.UNKNOWN;
    }
    
    for (int i = 0, size = parsers.size(); i < size; i++) {
      ImageHeaderParser parser = parsers.get(i);
      ImageType type = parser.getType(buffer);
      if (type != ImageType.UNKNOWN) {
        return type;
      }
    }
    return ImageType.UNKNOWN;
}

这里时候全局的工具类ImageHeaderParserUtils来处理的,而getType这个静态方法是由ImageHeaderParser接口来实现的,所以具体的实现需要看子类的实现逻辑,这里以DefaultImageHeaderParser来分析

private ImageType getType(Reader reader) throws IOException {
    final int firstTwoBytes = reader.getUInt16();
    
    // JPEG.
    if (firstTwoBytes == EXIF_MAGIC_NUMBER) {
      return JPEG;
    }
    
    final int firstFourBytes = (firstTwoBytes << 16 & 0xFFFF0000) | (reader.getUInt16() & 0xFFFF);
    // PNG.
    if (firstFourBytes == PNG_HEADER) {
      // See: http://stackoverflow.com/questions/2057923/how-to-check-a-png-for-grayscale-alpha
      // -color-type
      reader.skip(25 - 4);
      int alpha = reader.getByte();
      // A RGB indexed PNG can also have transparency. Better safe than sorry!
      return alpha >= 3 ? PNG_A : PNG;
    }
    
    // GIF from first 3 bytes.
    if (firstFourBytes >> 8 == GIF_HEADER) {
      return GIF;
    }
    
    // WebP (reads up to 21 bytes). See https://developers.google.com/speed/webp/docs/riff_container
    // for details.
    if (firstFourBytes != RIFF_HEADER) {
      return UNKNOWN;
    }
    
    // Bytes 4 - 7 contain length information. Skip these.
    reader.skip(4);
    final int thirdFourBytes =
        (reader.getUInt16() << 16 & 0xFFFF0000) | (reader.getUInt16() & 0xFFFF);
    if (thirdFourBytes != WEBP_HEADER) {
      return UNKNOWN;
    }
    
    final int fourthFourBytes =
        (reader.getUInt16() << 16 & 0xFFFF0000) | (reader.getUInt16() & 0xFFFF);
    if ((fourthFourBytes & VP8_HEADER_MASK) != VP8_HEADER) {
      return UNKNOWN;
    }
    
    if ((fourthFourBytes & VP8_HEADER_TYPE_MASK) == VP8_HEADER_TYPE_EXTENDED) {
      // Skip some more length bytes and check for transparency/alpha flag.
      reader.skip(4);
      return (reader.getByte() & WEBP_EXTENDED_ALPHA_FLAG) != 0 ? ImageType.WEBP_A : ImageType.WEBP;
    }
    
    if ((fourthFourBytes & VP8_HEADER_TYPE_MASK) == VP8_HEADER_TYPE_LOSSLESS) {
      // See chromium.googlesource.com/webm/libwebp/+/master/doc/webp-lossless-bitstream-spec.txt
      // for more info.
      reader.skip(4);
      return (reader.getByte() & WEBP_LOSSLESS_ALPHA_FLAG) != 0 ? ImageType.WEBP_A : ImageType.WEBP;
    }
    return ImageType.WEBP;
}

这个方法里面就是图片的各种格式判断逻辑

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

推荐阅读更多精彩内容