Java tutorial
/** * Copyright 2015 Matthew Lee 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.originqiu.library; import java.io.IOException; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; import android.annotation.TargetApi; import android.app.Activity; import android.app.Fragment; import android.app.FragmentManager; import android.os.Build; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.support.annotation.MainThread; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.WorkerThread; import android.support.v4.app.FragmentActivity; import okhttp3.Call; import okhttp3.Callback; import okhttp3.Response; /** * Thunder is modified from SugarTask which is a very nice repo to provide lifecycle safety * AsyncTask usage. * Created by OriginQiu on 16/8/17. */ public class Thunder { /* * handle message from WorkerThread and update something in MainThread, such * us progress. ? ?????? UI */ public interface MessageListener { void handleMessage(@NonNull Message message); } public interface ResponseCallBack { void onResponse(ResponseData response); } public interface FailureCallBack { void onFailure(Exception e); } public class Register { private Integer id; private Register(@NonNull Integer id) { this.id = id; } /* * Must. */ @MainThread public Thunder.Builder assign(Call call) { taskMap.put(id, call); return new Builder(id); } } public class Builder { private Integer id; private Builder(@NonNull Integer id) { this.id = id; } /* * Optional. ? */ @MainThread public Thunder.Builder handle(@NonNull MessageListener listener) { messageMap.put(id, listener); return this; } /* * Optional. ? */ @MainThread public Thunder.Builder finish(@NonNull ResponseCallBack callBack) { finishMap.put(id, callBack); return this; } /* * Optional. ? */ @MainThread public Thunder.Builder broken(@NonNull FailureCallBack callBack) { brokenMap.put(id, callBack); return this; } /* * Must. */ public void execute() { executeCall(id); } } private class Holder { private Integer id; private Object object; private Holder(@NonNull Integer id, @Nullable Object object) { this.id = id; this.object = object; } } /* * When you post a message from WorkerThread, your message.what should not * equals MESSAGE_*. ????? message.what ? MESSAGE_* */ public static final int MESSAGE_FINISH = 0x65530; public static final int MESSAGE_BROKEN = 0x65531; public static final int MESSAGE_STOP = 0x65532; public static final String TAG_HOOK = "HOOK"; private static final Integer ID_ACTIVITY = 0x65533; private static final Integer ID_FRAGMENT_ACTIVITY = 0x65534; private static final Integer ID_FRAGMENT = 0x65535; private static final Integer ID_SUPPORT_FRAGMENT = 0x65536; /* * So how to get context lifecycle state real-time? It's easy, just add a * hook fragment to Activity/FragmentActivity(v4)/Fragment/Fragment(v4) by * their FragmentManager, the hook fragment will follow it's parent * lifecycle, so we get state real-time :) ??? ??? * Activity/FragmentActivity(v4)/Fragment/Fragment(v4) FragmentManager * hook fragment hook fragment ?? * ?? */ public static class HookFragment extends Fragment { protected boolean postEnable = true; @Override public void onStop() { super.onStop(); if (postEnable) { Message message = new Message(); message.what = MESSAGE_STOP; post(message); } } } public static class HookSupportFragment extends android.support.v4.app.Fragment { protected boolean postEnable = true; @Override public void onStop() { super.onStop(); if (postEnable) { Message message = new Message(); message.what = MESSAGE_STOP; post(message); } } } @MainThread public static Register with(@NonNull Activity activity) { getInstance().registerHookToContext(activity); return getInstance().buildRegister(activity); } @MainThread public static Register with(@NonNull FragmentActivity activity) { getInstance().registerHookToContext(activity); return getInstance().buildRegister(activity); } @MainThread public static Register with(@NonNull Fragment fragment) { getInstance().registerHookToContext(fragment); return getInstance().buildRegister(fragment); } @MainThread public static Register with(@NonNull android.support.v4.app.Fragment fragment) { getInstance().registerHookToContext(fragment); return getInstance().buildRegister(fragment); } /* * Post message from WorkerThread to MainThread: SugarTask.post(YOUR * MESSAGE); ?????? SugarTask.post(YOUR MESSAGE); */ @WorkerThread public static void post(@NonNull Message message) { getInstance().handler.sendMessage(message); } private static class SugarTaskHolder { public static final Thunder INSTANCE = new Thunder(); } private static Thunder getInstance() { return SugarTaskHolder.INSTANCE; } /* * Every thread has an unique id, so we can manage thread easily. * ? id id ?? */ private static AtomicInteger count = new AtomicInteger(0); /* * Hold current context(Activity/FragmentActivity(v4)/Fragment/Fragment(v4), * when context lifecycle stop, remember use resetHolder() to reset holder. * ? (Activity/FragmentActivity(v4)/Fragment/Fragment(v4) * ??? resetHolder() holder ? */ private Holder holder = null; private Map<Integer, Call> taskMap = new ConcurrentHashMap<>(); private Map<Integer, MessageListener> messageMap = new ConcurrentHashMap<>(); private Map<Integer, ResponseCallBack> finishMap = new ConcurrentHashMap<>(); private Map<Integer, FailureCallBack> brokenMap = new ConcurrentHashMap<>(); /* * When message.what = MESSAGE_STOP, clear all MainThread callback, so we * decouple WorkerThread and MainThread, avoid OOM. message.what = * MESSAGE_STOP ? ????? OOM */ private Handler handler = new Handler(Looper.getMainLooper(), new Handler.Callback() { @Override public boolean handleMessage(@NonNull Message message) { if (message.what == MESSAGE_FINISH && message.obj instanceof Holder) { final Holder result = (Holder) message.obj; taskMap.remove(result.id); messageMap.remove(result.id); brokenMap.remove(result.id); final ResponseCallBack callBack = finishMap.remove(result.id); if (callBack != null) { callBack.onResponse((ResponseData) result.object); } getInstance().dispatchUnregister(); } else if (message.what == MESSAGE_BROKEN && message.obj instanceof Holder) { final Holder result = (Holder) message.obj; taskMap.remove(result.id); messageMap.remove(result.id); finishMap.remove(result.id); final FailureCallBack callBack = brokenMap.remove(result.id); if (callBack != null) { callBack.onFailure((Exception) result.object); } getInstance().dispatchUnregister(); } else if (message.what == MESSAGE_STOP) { resetHolder(); taskMap.clear(); messageMap.clear(); finishMap.clear(); brokenMap.clear(); } else { for (MessageListener listener : messageMap.values()) { listener.handleMessage(message); } } return true; } }); private Register buildRegister(@NonNull Activity activity) { holder = new Holder(ID_ACTIVITY, activity); return new Register(count.getAndIncrement()); } private Register buildRegister(@NonNull FragmentActivity activity) { holder = new Holder(ID_FRAGMENT_ACTIVITY, activity); return new Register(count.getAndIncrement()); } private Register buildRegister(@NonNull Fragment fragment) { holder = new Holder(ID_FRAGMENT, fragment); return new Register(count.getAndIncrement()); } private Register buildRegister(@NonNull android.support.v4.app.Fragment fragment) { holder = new Holder(ID_SUPPORT_FRAGMENT, fragment); return new Register(count.getAndIncrement()); } private void executeCall(final Integer id) { if (taskMap.containsKey(id)) { final Message message = Message.obtain(); try { Call call = taskMap.get(id); call.enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { message.what = MESSAGE_BROKEN; message.obj = new Holder(id, e); post(message); } @Override public void onResponse(Call call, Response response) throws IOException { message.what = MESSAGE_FINISH; message.obj = new Holder(id, new ResponseData.Builder().code(response.code()) .message(response.message()).body(response.body().string()).build()); post(message); } }); } catch (Exception e) { message.what = MESSAGE_BROKEN; message.obj = new Holder(id, e); post(message); } } } private void registerHookToContext(@NonNull Activity activity) { FragmentManager manager = activity.getFragmentManager(); HookFragment hookFragment = (HookFragment) manager.findFragmentByTag(TAG_HOOK); if (hookFragment == null) { hookFragment = new HookFragment(); manager.beginTransaction().add(hookFragment, TAG_HOOK).commitAllowingStateLoss(); } } private void registerHookToContext(@NonNull FragmentActivity activity) { android.support.v4.app.FragmentManager manager = activity.getSupportFragmentManager(); HookSupportFragment hookSupportFragment = (HookSupportFragment) manager.findFragmentByTag(TAG_HOOK); if (hookSupportFragment == null) { hookSupportFragment = new HookSupportFragment(); manager.beginTransaction().add(hookSupportFragment, TAG_HOOK).commitAllowingStateLoss(); } } @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) private void registerHookToContext(@NonNull Fragment fragment) { FragmentManager manager = fragment.getChildFragmentManager(); HookFragment hookFragment = (HookFragment) manager.findFragmentByTag(TAG_HOOK); if (hookFragment == null) { hookFragment = new HookFragment(); manager.beginTransaction().add(hookFragment, TAG_HOOK).commitAllowingStateLoss(); } } private void registerHookToContext(@NonNull android.support.v4.app.Fragment fragment) { android.support.v4.app.FragmentManager manager = fragment.getChildFragmentManager(); HookSupportFragment hookSupportFragment = (HookSupportFragment) manager.findFragmentByTag(TAG_HOOK); if (hookSupportFragment == null) { hookSupportFragment = new HookSupportFragment(); manager.beginTransaction().add(hookSupportFragment, TAG_HOOK).commitAllowingStateLoss(); } } private void dispatchUnregister() { if (holder == null || taskMap.size() > 0) { return; } if (holder.id.equals(ID_ACTIVITY) && holder.object instanceof Activity) { unregisterHookToContext((Activity) holder.object); } else if (holder.id.equals(ID_FRAGMENT_ACTIVITY) && holder.object instanceof FragmentActivity) { unregisterHookToContext((FragmentActivity) holder.object); } else if (holder.id.equals(ID_FRAGMENT) && holder.object instanceof Fragment) { unregisterHookToContext((Fragment) holder.object); } else if (holder.id.equals(ID_SUPPORT_FRAGMENT) && holder.object instanceof android.support.v4.app.Fragment) { unregisterHookToContext((android.support.v4.app.Fragment) holder.object); } resetHolder(); } private void unregisterHookToContext(@NonNull Activity activity) { FragmentManager manager = activity.getFragmentManager(); HookFragment hookFragment = (HookFragment) manager.findFragmentByTag(TAG_HOOK); if (hookFragment != null) { hookFragment.postEnable = false; manager.beginTransaction().remove(hookFragment).commitAllowingStateLoss(); } } private void unregisterHookToContext(@NonNull FragmentActivity activity) { android.support.v4.app.FragmentManager manager = activity.getSupportFragmentManager(); HookSupportFragment hookSupportFragment = (HookSupportFragment) manager.findFragmentByTag(TAG_HOOK); if (hookSupportFragment != null) { hookSupportFragment.postEnable = false; manager.beginTransaction().remove(hookSupportFragment).commitAllowingStateLoss(); } } @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) private void unregisterHookToContext(@NonNull Fragment fragment) { FragmentManager manager = fragment.getChildFragmentManager(); HookFragment hookFragment = (HookFragment) manager.findFragmentByTag(TAG_HOOK); if (hookFragment != null) { hookFragment.postEnable = false; manager.beginTransaction().remove(hookFragment).commitAllowingStateLoss(); } } private void unregisterHookToContext(@NonNull android.support.v4.app.Fragment fragment) { android.support.v4.app.FragmentManager manager = fragment.getChildFragmentManager(); HookSupportFragment hookSupportFragment = (HookSupportFragment) manager.findFragmentByTag(TAG_HOOK); if (hookSupportFragment != null) { hookSupportFragment.postEnable = false; manager.beginTransaction().remove(hookSupportFragment).commitAllowingStateLoss(); } } private void resetHolder() { if (holder == null) { return; } holder.id = 0; holder.object = null; holder = null; } }