me.originqiu.library.Thunder.java Source code

Java tutorial

Introduction

Here is the source code for me.originqiu.library.Thunder.java

Source

/**
 * 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;
    }
}