/*
* Copyright (C) 2016 Peng fei Pan <sky@xiaopan.me>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.xiaopan.sketch.feature.large;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Rect;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import java.lang.ref.WeakReference;
import me.xiaopan.sketch.Configuration;
import me.xiaopan.sketch.SLog;
import me.xiaopan.sketch.SLogType;
import me.xiaopan.sketch.Sketch;
import me.xiaopan.sketch.SketchMonitor;
import me.xiaopan.sketch.cache.BitmapPool;
import me.xiaopan.sketch.cache.BitmapPoolUtils;
import me.xiaopan.sketch.decode.ImageType;
import me.xiaopan.sketch.feature.ImageOrientationCorrector;
/**
* 解码处理器,运行在解码线程中,负责解码
*/
class DecodeHandler extends Handler {
private static final String NAME = "DecodeHandler";
private static final int WHAT_DECODE = 1001;
private boolean disableInBitmap;
private WeakReference<TileExecutor> reference;
private BitmapPool bitmapPool;
private SketchMonitor monitor;
private ImageOrientationCorrector orientationCorrector;
public DecodeHandler(Looper looper, TileExecutor executor) {
super(looper);
this.reference = new WeakReference<>(executor);
Configuration configuration = Sketch.with(executor.callback.getContext()).getConfiguration();
this.bitmapPool = configuration.getBitmapPool();
this.monitor = configuration.getMonitor();
this.orientationCorrector = configuration.getImageOrientationCorrector();
}
@Override
public void handleMessage(Message msg) {
TileExecutor decodeExecutor = reference.get();
if (decodeExecutor != null) {
decodeExecutor.mainHandler.cancelDelayDestroyThread();
}
switch (msg.what) {
case WHAT_DECODE:
decode(decodeExecutor, msg.arg1, (Tile) msg.obj);
break;
}
if (decodeExecutor != null) {
decodeExecutor.mainHandler.postDelayRecycleDecodeThread();
}
}
public void postDecode(int key, Tile tile) {
Message message = obtainMessage(DecodeHandler.WHAT_DECODE);
message.arg1 = key;
message.obj = tile;
message.sendToTarget();
}
private void decode(TileExecutor executor, int key, Tile tile) {
if (executor == null) {
if (SLogType.LARGE.isEnabled()) {
SLog.w(SLogType.LARGE, NAME, "weak reference break. key: %d, tile=%s", key, tile.getInfo());
}
return;
}
if (tile.isExpired(key)) {
executor.mainHandler.postDecodeError(key, tile, new DecodeErrorException(DecodeErrorException.CAUSE_BEFORE_KEY_EXPIRED));
return;
}
if (tile.isDecodeParamEmpty()) {
executor.mainHandler.postDecodeError(key, tile, new DecodeErrorException(DecodeErrorException.CAUSE_DECODE_PARAM_EMPTY));
return;
}
ImageRegionDecoder regionDecoder = tile.decoder;
if (regionDecoder == null || !regionDecoder.isReady()) {
executor.mainHandler.postDecodeError(key, tile, new DecodeErrorException(DecodeErrorException.CAUSE_DECODER_NULL_OR_NOT_READY));
return;
}
Rect srcRect = new Rect(tile.srcRect);
int inSampleSize = tile.inSampleSize;
// 根据图片方向旋转src区域
if (regionDecoder.getImageOrientation() != 0) {
orientationCorrector.reverseRotate(srcRect, regionDecoder.getImageSize(), regionDecoder.getImageOrientation());
}
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = inSampleSize;
ImageType imageType = regionDecoder.getImageType();
if (imageType != null) {
options.inPreferredConfig = imageType.getConfig(false);
}
if (BitmapPoolUtils.sdkSupportInBitmapForRegionDecoder() && !disableInBitmap) {
BitmapPoolUtils.setInBitmapFromPoolForRegionDecoder(options, srcRect, bitmapPool);
}
long time = System.currentTimeMillis();
Bitmap bitmap = null;
try {
bitmap = regionDecoder.decodeRegion(srcRect, options);
} catch (IllegalArgumentException e) {
e.printStackTrace();
// TODO: 2017/5/9 过滤异常message,准确的识别出inBitmap异常
// 要是因为inBitmap而解码失败就停止继续使用并再此尝试
if (BitmapPoolUtils.sdkSupportInBitmapForRegionDecoder()) {
if (!disableInBitmap && options.inBitmap != null) {
disableInBitmap = BitmapPoolUtils.inBitmapThrowForRegionDecoder(e, options, monitor, bitmapPool,
regionDecoder.getImageUri(), regionDecoder.getImageSize().x, regionDecoder.getImageSize().y, srcRect);
options.inBitmap = null;
try {
bitmap = regionDecoder.decodeRegion(srcRect, options);
} catch (Throwable error) {
error.printStackTrace();
}
}
}
} catch (Throwable error) {
error.printStackTrace();
}
int useTime = (int) (System.currentTimeMillis() - time);
if (bitmap == null || bitmap.isRecycled()) {
executor.mainHandler.postDecodeError(key, tile, new DecodeErrorException(DecodeErrorException.CAUSE_BITMAP_NULL));
return;
}
if (tile.isExpired(key)) {
BitmapPoolUtils.freeBitmapToPoolForRegionDecoder(bitmap, Sketch.with(executor.callback.getContext()).getConfiguration().getBitmapPool());
executor.mainHandler.postDecodeError(key, tile, new DecodeErrorException(DecodeErrorException.CAUSE_AFTER_KEY_EXPIRED));
return;
}
// 恢复图片方向
if (regionDecoder.getImageOrientation() != 0) {
Bitmap newBitmap = orientationCorrector.rotate(bitmap, regionDecoder.getImageOrientation(), bitmapPool);
if (newBitmap != null && !newBitmap.isRecycled()) {
if (newBitmap != bitmap) {
BitmapPoolUtils.freeBitmapToPool(bitmap, bitmapPool);
bitmap = newBitmap;
}
} else if (bitmap.isRecycled()) {
executor.mainHandler.postDecodeError(key, tile, new DecodeErrorException(DecodeErrorException.CAUSE_BITMAP_NULL));
return;
}
}
executor.mainHandler.postDecodeCompleted(key, tile, bitmap, useTime);
}
public void clean(String why) {
if (SLogType.LARGE.isEnabled()) {
SLog.w(SLogType.LARGE, NAME, "clean. %s" + why);
}
removeMessages(WHAT_DECODE);
}
public static class DecodeErrorException extends Exception {
public static final int CAUSE_BITMAP_NULL = 1101;
public static final int CAUSE_BEFORE_KEY_EXPIRED = 1102;
public static final int CAUSE_AFTER_KEY_EXPIRED = 1103;
public static final int CAUSE_CALLBACK_KEY_EXPIRED = 1104;
public static final int CAUSE_DECODE_PARAM_EMPTY = 1105;
public static final int CAUSE_DECODER_NULL_OR_NOT_READY = 1106;
private int cause;
public DecodeErrorException(int cause) {
this.cause = cause;
}
public int getErrorCause() {
return cause;
}
public String getCauseMessage() {
if (cause == CAUSE_BITMAP_NULL) {
return "bitmap is null or recycled";
} else if (cause == CAUSE_BEFORE_KEY_EXPIRED) {
return "key expired before decode";
} else if (cause == CAUSE_AFTER_KEY_EXPIRED) {
return "key expired after decode";
} else if (cause == CAUSE_CALLBACK_KEY_EXPIRED) {
return "key expired before callback";
} else if (cause == CAUSE_DECODE_PARAM_EMPTY) {
return "decode param is empty";
} else if (cause == CAUSE_DECODER_NULL_OR_NOT_READY) {
return "decoder is null or not ready";
} else {
return "unknown";
}
}
}
}