package ml.puredark.hviewer.http; import android.net.Uri; import android.os.Looper; import android.os.SystemClock; import com.facebook.common.logging.FLog; import com.facebook.imagepipeline.image.EncodedImage; import com.facebook.imagepipeline.producers.BaseNetworkFetcher; import com.facebook.imagepipeline.producers.BaseProducerContextCallbacks; import com.facebook.imagepipeline.producers.Consumer; import com.facebook.imagepipeline.producers.FetchState; import com.facebook.imagepipeline.producers.ProducerContext; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.WeakHashMap; import java.util.concurrent.Executor; import ml.puredark.hviewer.HViewerApplication; import ml.puredark.hviewer.R; import okhttp3.CacheControl; import okhttp3.Call; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; import okhttp3.ResponseBody; /** * Created by PureDark on 2016/8/27. */ public class MyOkHttpNetworkFetcher extends BaseNetworkFetcher<MyOkHttpNetworkFetcher.OkHttpNetworkFetchState> { private static final String TAG = "OkHttpNetworkFetchProducer"; private static final String QUEUE_TIME = "queue_time"; private static final String FETCH_TIME = "fetch_time"; private static final String TOTAL_TIME = "total_time"; private static final String IMAGE_SIZE = "image_size"; //为了Fresco请求时可以根据不同Uri加不同的header,必须要得有一个全局变量(Fresco的硬伤) public static WeakHashMap<Uri, String> headers = new WeakHashMap<>(); private final OkHttpClient mOkHttpClient; private Executor mCancellationExecutor; /** * @param okHttpClient client to use */ public MyOkHttpNetworkFetcher(OkHttpClient okHttpClient) { mOkHttpClient = okHttpClient; mCancellationExecutor = okHttpClient.dispatcher().executorService(); } @Override public OkHttpNetworkFetchState createFetchState( Consumer<EncodedImage> consumer, ProducerContext context) { return new OkHttpNetworkFetchState(consumer, context); } @Override public void fetch(final OkHttpNetworkFetchState fetchState, final Callback callback) { fetchState.submitTime = SystemClock.elapsedRealtime(); final Uri uri = fetchState.getUri(); String headerStr = headers.get(uri); Request.Builder builder = new Request.Builder(); try { if (headerStr != null && !"".equals(headerStr)) { JsonObject object = new JsonParser().parse(headerStr).getAsJsonObject(); for (Map.Entry<String, JsonElement> entry : object.entrySet()) { String header = entry.getKey(); String value = entry.getValue().getAsString(); builder.addHeader(header, value); } } } catch (Exception e) { } final Request request = builder .cacheControl(new CacheControl.Builder().noStore().build()) .header("User-Agent", HViewerApplication.mContext.getResources().getString(R.string.UA)) .url(uri.toString()) .get() .build(); final Call call = mOkHttpClient.newCall(request); fetchState.getContext().addCallbacks( new BaseProducerContextCallbacks() { @Override public void onCancellationRequested() { if (Looper.myLooper() != Looper.getMainLooper()) { call.cancel(); } else { mCancellationExecutor.execute(() -> call.cancel()); } } }); call.enqueue( new okhttp3.Callback() { @Override public void onResponse(Call call, Response response) throws IOException { fetchState.responseTime = SystemClock.elapsedRealtime(); final ResponseBody body = response.body(); try { if (!response.isSuccessful()) { handleException( call, new IOException("Unexpected HTTP code " + response), callback); return; } long contentLength = body.contentLength(); if (contentLength < 0) { contentLength = 0; } callback.onResponse(body.byteStream(), (int) contentLength); } catch (Exception e) { handleException(call, e, callback); } finally { try { body.close(); } catch (Exception e) { FLog.w(TAG, "Exception when closing response body", e); } } } @Override public void onFailure(Call call, IOException e) { handleException(call, e, callback); } }); } @Override public void onFetchCompletion(OkHttpNetworkFetchState fetchState, int byteSize) { fetchState.fetchCompleteTime = SystemClock.elapsedRealtime(); } @Override public Map<String, String> getExtraMap(OkHttpNetworkFetchState fetchState, int byteSize) { Map<String, String> extraMap = new HashMap<>(4); extraMap.put(QUEUE_TIME, Long.toString(fetchState.responseTime - fetchState.submitTime)); extraMap.put(FETCH_TIME, Long.toString(fetchState.fetchCompleteTime - fetchState.responseTime)); extraMap.put(TOTAL_TIME, Long.toString(fetchState.fetchCompleteTime - fetchState.submitTime)); extraMap.put(IMAGE_SIZE, Integer.toString(byteSize)); return extraMap; } /** * Handles exceptions. * <p> * <p> OkHttp notifies callers of cancellations via an IOException. If IOException is caught * after request cancellation, then the exception is interpreted as successful cancellation * and onCancellation is called. Otherwise onFailure is called. */ private void handleException(final Call call, final Exception e, final Callback callback) { if (call.isCanceled()) { callback.onCancellation(); } else { callback.onFailure(e); } } public static class OkHttpNetworkFetchState extends FetchState { public long submitTime; public long responseTime; public long fetchCompleteTime; public OkHttpNetworkFetchState( Consumer<EncodedImage> consumer, ProducerContext producerContext) { super(consumer, producerContext); } } }