android 探索奇怪的照片旋转

1、发现问题

项目中有两个图片加载框架(不要问我为什么要这样,前任,也不算前任他还在公司o(╥﹏╥)o)大名鼎鼎的Glide和以前很火的ImageLoader

突然有一天,客户反馈说有个照片旋转了90度。明明自己拍照是正常的,上传到管理平台(也就是web端)也是正常的。

一开始,我们肯定觉得原因在于客户,因为有时候客户确实很SB,我们的理由是,这个列表,其他照片都正常,那说明我的代码没问题,肯定是客户传的照片本来就是旋转的。

虽然我们很肯定是客户的问题,但是问题还是要解决的,等客户去认证就慢了,于是乎,我自己调试接口拿到图片链接,然后我就。。。。裂开了

浏览器显示是正常的,没有旋转,我不死心,下载到电脑,还是正常的。

这个时候我意识到,问题并不简单,然后我试着换Glide看看。之前这里是用ImageLoader的。

真相就是,Glide正常,ImageLoader有问题。

于是我搜一下ImageLoader配置图片旋转的API,果然被我找到了considderExifParams这个方法,描述就是设置是否需要旋转,默认是false,然后我看了我们的代码,没有设置这个方法,那默认就是不旋转。

等等,好像有什么不对!

照片链接在浏览器上看是正常的,下载到电脑也是正常的,然后我在代码里也没有做旋转,然后照片显示就   旋    转    了

难道是框架内部帮我们处理了,因为Glide都没有问题,所以我试着ImageLoader的considderExifParams配置了true,运行看效果,正常了。。。。。

果然如此,此刻我心中暗喜,这就是码农的日常,找到并解决BUG的喜悦!

BUG虽然解决了,但是真正的原因其实还不知道,需要去认证自己的想法。

2、ImageLoader源码分析

如果全部分析ImageLoader框架的源码那就太费时了,我们只需要找到图片旋转的代码就可以了,带着目的去看源码才是效率最高的。

我们的目的是找到图片下载的地方,然后看看旋转是怎么实现的

顺着displayImage()往下找

ImageLoader.java

	public void displayImage(String uri, ImageView imageView, DisplayImageOptions options)         
    {
		displayImage(uri, new ImageViewAware(imageView), options, null, null);
	}
public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options,
			ImageSize targetSize, ImageLoadingListener listener, ImageLoadingProgressListener progressListener) {
		
		
		if (options == null) {
			options = configuration.defaultDisplayImageOptions;
		}

//省略了一些无关代码

	
			ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey,
					options, listener, progressListener, engine.getLockForUri(uri));
//这个对象后面会讲到,传人的engine
			LoadAndDisplayImageTask displayTask = new LoadAndDisplayImageTask(engine, imageLoadingInfo,
					defineHandler(options));
			if (options.isSyncLoading()) {
				displayTask.run();
			} else {
				engine.submit(displayTask);
			}
		}
	}

代码比较多,我去掉了无关代码,LoadAndDisplayImageTask,实现了runnable,所以到时候会执行run方法

先看一下run方法做了什么

LoadAndDisplayImageTask.java

@Override
	public void run() {

        //省略代码

		Bitmap bmp;
		try {
			checkTaskNotActual();

			bmp = configuration.memoryCache.get(memoryCacheKey);
			if (bmp == null || bmp.isRecycled()) {
				bmp = tryLoadBitmap();
				if (bmp == null) return; // listener callback already was fired

				checkTaskNotActual();
				checkTaskInterrupted();

				if (options.shouldPreProcess()) {
					L.d(LOG_PREPROCESS_IMAGE, memoryCacheKey);
					bmp = options.getPreProcessor().process(bmp);
					if (bmp == null) {
						L.e(ERROR_PRE_PROCESSOR_NULL, memoryCacheKey);
					}
				}

				if (bmp != null && options.isCacheInMemory()) {
					L.d(LOG_CACHE_IMAGE_IN_MEMORY, memoryCacheKey);
					configuration.memoryCache.put(memoryCacheKey, bmp);
				}
			} 

        //省略代码

	
		} catch (TaskCancelledException e) {
			fireCancelEvent();
			return;
		} finally {
			loadFromUriLock.unlock();
		}

        
		DisplayBitmapTask displayBitmapTask = new DisplayBitmapTask(bmp, imageLoadingInfo, engine, loadedFrom);
		runTask(displayBitmapTask, syncLoading, handler, engine);
	}

很明显,tryLoadBitmap()这可能会是获取图片的地方,跟进去看看

private Bitmap tryLoadBitmap() throws TaskCancelledException {
		Bitmap bitmap = null;
		try {
			File imageFile = configuration.diskCache.get(uri);
			if (imageFile != null && imageFile.exists() && imageFile.length() > 0) {
				L.d(LOG_LOAD_IMAGE_FROM_DISK_CACHE, memoryCacheKey);
				loadedFrom = LoadedFrom.DISC_CACHE;

				checkTaskNotActual();
				bitmap = decodeImage(Scheme.FILE.wrap(imageFile.getAbsolutePath()));
			}
			if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
				L.d(LOG_LOAD_IMAGE_FROM_NETWORK, memoryCacheKey);
				loadedFrom = LoadedFrom.NETWORK;

				String imageUriForDecoding = uri;
				if (options.isCacheOnDisk() && tryCacheImageOnDisk()) {
					imageFile = configuration.diskCache.get(uri);
					if (imageFile != null) {
						imageUriForDecoding = Scheme.FILE.wrap(imageFile.getAbsolutePath());
					}
				}

				checkTaskNotActual();
                //这里获取图片继续跟进
				bitmap = decodeImage(imageUriForDecoding);

				if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
					fireFailEvent(FailType.DECODING_ERROR, null);
				}
			}
		} catch (IllegalStateException e) {
			fireFailEvent(FailType.NETWORK_DENIED, null);
		} catch (TaskCancelledException e) {
			throw e;
		} catch (IOException e) {
			L.e(e);
			fireFailEvent(FailType.IO_ERROR, e);
		} catch (OutOfMemoryError e) {
			L.e(e);
			fireFailEvent(FailType.OUT_OF_MEMORY, e);
		} catch (Throwable e) {
			L.e(e);
			fireFailEvent(FailType.UNKNOWN, e);
		}
		return bitmap;
	}

bitmap = decodeImage(imageUriForDecoding);进行跟进这里

	private Bitmap decodeImage(String imageUri) throws IOException {
		ViewScaleType viewScaleType = imageAware.getScaleType();
		ImageDecodingInfo decodingInfo = new ImageDecodingInfo(memoryCacheKey, imageUri, uri, targetSize, viewScaleType,
				getDownloader(), options);
		return decoder.decode(decodingInfo);
	}

继续跟进这个 decoder.decode(decodingInfo); 

public interface ImageDecoder {

	/**
	 * Decodes image to {@link Bitmap} according target size and other parameters.
	 *
	 * @param imageDecodingInfo
	 * @return
	 * @throws IOException
	 */
	Bitmap decode(ImageDecodingInfo imageDecodingInfo) throws IOException;
}

发现decoder是个接口,那就找到实现者,跟踪LoadAndDisplayImageTask的构造方法

LoadAndDisplayImageTask.java

public LoadAndDisplayImageTask(ImageLoaderEngine engine, ImageLoadingInfo imageLoadingInfo, Handler handler) {
		this.engine = engine;
		this.imageLoadingInfo = imageLoadingInfo;
		this.handler = handler;

        //发现是engine的配置引用
		configuration = engine.configuration;

		downloader = configuration.downloader;
		networkDeniedDownloader = configuration.networkDeniedDownloader;
		slowNetworkDownloader = configuration.slowNetworkDownloader;

        //是配置类configuration的
		decoder = configuration.decoder;

		uri = imageLoadingInfo.uri;

//省略代码
}

engine好像有点眼熟在前面见过,在run方法里创建这个对象LoadAndDisplayImageTask,传人engine引用

好家伙,原来是在这初始化了,我们在application里会初始化这个init

ImageLoader.java

public synchronized void init(ImageLoaderConfiguration configuration) {
		if (configuration == null) {
			throw new IllegalArgumentException(ERROR_INIT_CONFIG_WITH_NULL);
		}
		if (this.configuration == null) {
			L.d(LOG_INIT_CONFIG);
			engine = new ImageLoaderEngine(configuration);
			this.configuration = configuration;
		} else {
			L.w(WARNING_RE_INIT_CONFIG);
		}
	}

而我初始化的时候没有设置加载器decoder,所以我们进入ImageLoaderConfiguration类看看decoder的创建

ImageLoaderConfiguration.java

private ImageLoaderConfiguration(final Builder builder) {
		//省略代码
		decoder = builder.decoder;
        //省略代码
}

接着看看它的构造器Builder

ImageLoaderConfiguration.java

		public ImageLoaderConfiguration build() {
			initEmptyFieldsWithDefaultValues();
			return new ImageLoaderConfiguration(this);
		}

很明显,默认赋值就在这里了initEmptyFieldsWithDefaultValues();

ImageLoaderConfiguration.java

private void initEmptyFieldsWithDefaultValues() {
			
            //省略代码
			
			if (decoder == null) {
				decoder = DefaultConfigurationFactory.createImageDecoder(writeLogs);
			}
			if (defaultDisplayImageOptions == null) {
				defaultDisplayImageOptions = DisplayImageOptions.createSimple();
			}
		}

找到加载器的创建了,继续跟进createImageDecoder,找到了这个BaseImageDecoder类,看看它的

BaseImageDecode.java

public Bitmap decode(ImageDecodingInfo decodingInfo) throws IOException {
		Bitmap decodedBitmap;
		ImageFileInfo imageInfo;

        //获取输入流
		InputStream imageStream = getImageStream(decodingInfo);
		if (imageStream == null) {
			L.e(ERROR_NO_IMAGE_STREAM, decodingInfo.getImageKey());
			return null;
		}
		try {
            //找到了,获取图片大小和旋转角度
			imageInfo = defineImageSizeAndRotation(imageStream, decodingInfo);
			imageStream = resetStream(imageStream, decodingInfo);
			Options decodingOptions = prepareDecodingOptions(imageInfo.imageSize, decodingInfo);
			decodedBitmap = BitmapFactory.decodeStream(imageStream, null, decodingOptions);
		} finally {
			IoUtils.closeSilently(imageStream);
		}

		if (decodedBitmap == null) {
			L.e(ERROR_CANT_DECODE_IMAGE, decodingInfo.getImageKey());
		} else {
			decodedBitmap = considerExactScaleAndOrientatiton(decodedBitmap, decodingInfo, imageInfo.exif.rotation,
					imageInfo.exif.flipHorizontal);
		}
		return decodedBitmap;
	}

终于找到我们想要的代码了imageInfo = defineImageSizeAndRotation(imageStream, decodingInfo);

BaseImageDecode.java

	protected ImageFileInfo defineImageSizeAndRotation(InputStream imageStream, ImageDecodingInfo decodingInfo)
			throws IOException {
		Options options = new Options();
		options.inJustDecodeBounds = true;
		BitmapFactory.decodeStream(imageStream, null, options);

		ExifInfo exif;
		String imageUri = decodingInfo.getImageUri();
        //这里的判断,很关键,就是我们需要的
		if (decodingInfo.shouldConsiderExifParams() && canDefineExifParams(imageUri, options.outMimeType)) {
            //获取旋转角度
			exif = defineExifOrientation(imageUri);
		} else {
            //默认旋转角度是0
			exif = new ExifInfo();
		}
		return new ImageFileInfo(new ImageSize(options.outWidth, options.outHeight, exif.rotation), exif);
	}

exif = defineExifOrientation(imageUri);这里应该就是获取图片旋转角度的逻辑,进去看看

BaseImageDecode.java

protected ExifInfo defineExifOrientation(String imageUri) {
		int rotation = 0;
		boolean flip = false;
		try {
            //找到了,这个对象,就是获取图片信息的
			ExifInterface exif = new ExifInterface(Scheme.FILE.crop(imageUri));
            //这里获取到图片的旋转角度
			int exifOrientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
			switch (exifOrientation) {
				case ExifInterface.ORIENTATION_FLIP_HORIZONTAL:
					flip = true;
				case ExifInterface.ORIENTATION_NORMAL:
					rotation = 0;
					break;
				case ExifInterface.ORIENTATION_TRANSVERSE:
					flip = true;
				case ExifInterface.ORIENTATION_ROTATE_90:
					rotation = 90;
					break;
				case ExifInterface.ORIENTATION_FLIP_VERTICAL:
					flip = true;
				case ExifInterface.ORIENTATION_ROTATE_180:
					rotation = 180;
					break;
				case ExifInterface.ORIENTATION_TRANSPOSE:
					flip = true;
				case ExifInterface.ORIENTATION_ROTATE_270:
					rotation = 270;
					break;
			}
		} catch (IOException e) {
			L.w("Can't read EXIF tags from file [%s]", imageUri);
		}
		return new ExifInfo(rotation, flip);
	}

ExifInterface类 是获取照片信息的,如果你对这个对象不熟悉,自行百度,我们回到decode(ImageDecodingInfo decodingInfo)方法,看看拿到旋转角度后怎么做

这段代码 decodedBitmap = considerExactScaleAndOrientatiton(decodedBitmap, decodingInfo, imageInfo.exif.rotation, imageInfo.exif.flipHorizontal);

BaseImageDecode.java

protected Bitmap considerExactScaleAndOrientatiton(Bitmap subsampledBitmap, ImageDecodingInfo decodingInfo,
			int rotation, boolean flipHorizontal) {
		Matrix m = new Matrix();
		//省略代码
		// Rotate bitmap if need
		if (rotation != 0) {
			m.postRotate(rotation);//矩阵旋转

			if (loggingEnabled) L.d(LOG_ROTATE_IMAGE, rotation, decodingInfo.getImageKey());
		}
        //最终得到Bitmap
		Bitmap finalBitmap = Bitmap.createBitmap(subsampledBitmap, 0, 0, subsampledBitmap.getWidth(), subsampledBitmap
				.getHeight(), m, true);
		if (finalBitmap != subsampledBitmap) {
			subsampledBitmap.recycle();
		}
		return finalBitmap;
	}

这里根据之前算出来的角度,矩阵旋转,得到最终的Bitmap

总结

最终获取图片角度的类是Android原生提供的ExifInterface类  
           获取旋转角度 int exifOrientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
然后再出来,得到角度值,根据矩阵旋转,得到最终的bitmap Bitmap finalBitmap = Bitmap.createBitmap(subsampledBitmap, 0, 0, subsampledBitmap.getWidth(), subsampledBitmap
                .getHeight(), m, true);//m是矩阵Matrix

、下一次看看Glide的源码,看看是不是也这样实现的

思考:原来浏览器上看到的和下载到电脑的图片方向,并不是正确的方向。我想是浏览器和电脑端都已经做了处理。

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值