/*
* 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.os.Handler;
import android.os.Looper;
import android.os.Message;
import java.lang.ref.WeakReference;
import me.xiaopan.sketch.SLogType;
import me.xiaopan.sketch.Sketch;
import me.xiaopan.sketch.SLog;
import me.xiaopan.sketch.cache.BitmapPoolUtils;
import me.xiaopan.sketch.cache.BitmapPool;
import me.xiaopan.sketch.util.KeyCounter;
/**
* 运行在主线程,负责将执行器的结果发送到主线程
*/
class MainHandler extends Handler {
private static final String NAME = "MainHandler";
private static final int WHAT_RECYCLE_DECODE_THREAD = 2001;
private static final int WHAT_INIT_COMPLETED = 2002;
private static final int WHAT_INIT_FAILED = 2003;
private static final int WHAT_DECODE_COMPLETED = 2004;
private static final int WHAT_DECODE_FAILED = 2005;
private BitmapPool bitmapPool;
private WeakReference<TileExecutor> executorReference;
public MainHandler(Looper looper, TileExecutor executor) {
super(looper);
executorReference = new WeakReference<>(executor);
bitmapPool = Sketch.with(executor.callback.getContext()).getConfiguration().getBitmapPool();
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case WHAT_RECYCLE_DECODE_THREAD:
recycleDecodeThread();
break;
case WHAT_INIT_COMPLETED:
InitResult initResult = (InitResult) msg.obj;
initCompleted(initResult.imageRegionDecoder, initResult.imageUrl, msg.arg1, initResult.keyCounter);
break;
case WHAT_INIT_FAILED:
InitErrorResult initErrorResult = (InitErrorResult) msg.obj;
initError(initErrorResult.exception, initErrorResult.imageUrl, msg.arg1, initErrorResult.keyCounter);
break;
case WHAT_DECODE_COMPLETED:
DecodeResult decodeResult = (DecodeResult) msg.obj;
decodeCompleted(msg.arg1, decodeResult.tile, decodeResult.bitmap, decodeResult.useTime);
break;
case WHAT_DECODE_FAILED:
DecodeErrorResult decodeErrorResult = (DecodeErrorResult) msg.obj;
decodeError(msg.arg1, decodeErrorResult.tile, decodeErrorResult.exception);
break;
}
}
/**
* 延迟三十秒停止解码线程
*/
public void postDelayRecycleDecodeThread() {
cancelDelayDestroyThread();
Message destroyMessage = obtainMessage(MainHandler.WHAT_RECYCLE_DECODE_THREAD);
sendMessageDelayed(destroyMessage, 30 * 1000);
}
private void recycleDecodeThread() {
TileExecutor executor = executorReference.get();
if (executor != null) {
executor.recycleDecodeThread();
}
}
/**
* 取消停止解码线程的延迟任务
*/
public void cancelDelayDestroyThread() {
removeMessages(MainHandler.WHAT_RECYCLE_DECODE_THREAD);
}
public void postInitCompleted(ImageRegionDecoder decoder, String imageUri, int initKey, KeyCounter keyCounter) {
Message message = obtainMessage(MainHandler.WHAT_INIT_COMPLETED);
message.arg1 = initKey;
message.obj = new InitResult(decoder, imageUri, keyCounter);
message.sendToTarget();
}
public void postInitError(Exception e, String imageUri, int key, KeyCounter keyCounter) {
Message message = obtainMessage(MainHandler.WHAT_INIT_FAILED);
message.arg1 = key;
message.obj = new InitErrorResult(e, imageUri, keyCounter);
message.sendToTarget();
}
public void postDecodeCompleted(int key, Tile tile, Bitmap bitmap, int useTime) {
Message message = obtainMessage(MainHandler.WHAT_DECODE_COMPLETED);
message.arg1 = key;
message.obj = new DecodeResult(bitmap, tile, useTime);
message.sendToTarget();
}
public void postDecodeError(int key, Tile tile, DecodeHandler.DecodeErrorException exception) {
Message message = obtainMessage(MainHandler.WHAT_DECODE_FAILED);
message.arg1 = key;
message.obj = new DecodeErrorResult(tile, exception);
message.sendToTarget();
}
private void initCompleted(ImageRegionDecoder decoder, String imageUri, int key, KeyCounter keyCounter) {
TileExecutor executor = executorReference.get();
if (executor == null) {
if (SLogType.LARGE.isEnabled()) {
SLog.w(SLogType.LARGE, NAME, "weak reference break. initCompleted. key: %d, imageUri: %s", key, decoder.getImageUri());
}
decoder.recycle();
return;
}
int newKey = keyCounter.getKey();
if (key != newKey) {
if (SLogType.LARGE.isEnabled()) {
SLog.w(SLogType.LARGE, NAME, "init key expired. initCompleted. key: %d. newKey: %d, imageUri: %s", key, newKey, decoder.getImageUri());
}
decoder.recycle();
return;
}
executor.callback.onInitCompleted(imageUri, decoder);
}
private void initError(Exception exception, String imageUri, int key, KeyCounter keyCounter) {
TileExecutor executor = executorReference.get();
if (executor == null) {
if (SLogType.LARGE.isEnabled()) {
SLog.w(SLogType.LARGE, NAME, "weak reference break. initError. key: %d, imageUri: %s", key, imageUri);
}
return;
}
int newKey = keyCounter.getKey();
if (key != newKey) {
if (SLogType.LARGE.isEnabled()) {
SLog.w(SLogType.LARGE, NAME, "key expire. initError. key: %d. newKey: %d, imageUri: %s", key, newKey, imageUri);
}
return;
}
executor.callback.onInitError(imageUri, exception);
}
private void decodeCompleted(int key, Tile tile, Bitmap bitmap, int useTime) {
TileExecutor executor = executorReference.get();
if (executor == null) {
if (SLogType.LARGE.isEnabled()) {
SLog.w(SLogType.LARGE, NAME, "weak reference break. decodeCompleted. key: %d, tile=%s", key, tile.getInfo());
}
BitmapPoolUtils.freeBitmapToPoolForRegionDecoder(bitmap, bitmapPool);
return;
}
if (!tile.isExpired(key)) {
executor.callback.onDecodeCompleted(tile, bitmap, useTime);
} else {
BitmapPoolUtils.freeBitmapToPoolForRegionDecoder(bitmap, bitmapPool);
executor.callback.onDecodeError(tile,
new DecodeHandler.DecodeErrorException(DecodeHandler.DecodeErrorException.CAUSE_CALLBACK_KEY_EXPIRED));
}
}
private void decodeError(int key, Tile tile, DecodeHandler.DecodeErrorException exception) {
TileExecutor executor = executorReference.get();
if (executor == null) {
if (SLogType.LARGE.isEnabled()) {
SLog.w(SLogType.LARGE, NAME, "weak reference break. decodeError. key: %d, tile=%s", key, tile.getInfo());
}
return;
}
executor.callback.onDecodeError(tile, exception);
}
private static final class DecodeResult {
public Tile tile;
public Bitmap bitmap;
public int useTime;
public DecodeResult(Bitmap bitmap, Tile tile, int useTime) {
this.bitmap = bitmap;
this.tile = tile;
this.useTime = useTime;
}
}
private static final class DecodeErrorResult {
public Tile tile;
public DecodeHandler.DecodeErrorException exception;
public DecodeErrorResult(Tile tile, DecodeHandler.DecodeErrorException exception) {
this.tile = tile;
this.exception = exception;
}
}
private static final class InitResult{
public String imageUrl;
public ImageRegionDecoder imageRegionDecoder;
public KeyCounter keyCounter;
public InitResult(ImageRegionDecoder imageRegionDecoder, String imageUrl, KeyCounter keyCounter) {
this.imageRegionDecoder = imageRegionDecoder;
this.imageUrl = imageUrl;
this.keyCounter = keyCounter;
}
}
private static final class InitErrorResult {
public String imageUrl;
public Exception exception;
public KeyCounter keyCounter;
public InitErrorResult(Exception exception, String imageUrl, KeyCounter keyCounter) {
this.exception = exception;
this.imageUrl = imageUrl;
this.keyCounter = keyCounter;
}
}
}