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 com.blue.leaves.util.task; 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.os.Process; 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 java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicInteger; public class SugarTask { /* * WorkerThread interface, * do what you want to do on background thread by implementing this interface. * * ??? * ?? */ public interface TaskDescription { Object onBackground(); } /* * MainThread interface, * handle message from WorkerThread and update something in MainThread, * such us progress. * * ? * ?????? UI */ public interface MessageListener { void handleMessage(@NonNull Message message); } /* * MainThread interface, * when WorkerThread finish without Exception, * and context lifecycle safety, * onFinish() will be called. * * ? * ????? * ? */ public interface FinishListener { void onFinish(@Nullable Object result); } /* * MainThread interface, * when WorkerThread end with Exception, * and context lifecycle safety, * onBroken() will be called. * * ? * ????? * ? */ public interface BrokenListener { void onBroken(@NonNull Exception e); } public class Register { private Integer id; private Register(@NonNull Integer id) { this.id = id; } /* * Must. * * */ @MainThread public SugarTask.Builder assign(@NonNull TaskDescription description) { taskMap.put(id, description); return new Builder(id); } } public class Builder { private Integer id; private Builder(@NonNull Integer id) { this.id = id; } /* * Optional. * * ? */ @MainThread public SugarTask.Builder handle(@NonNull MessageListener listener) { messageMap.put(id, listener); return this; } /* * Optional. * * ? */ @MainThread public SugarTask.Builder finish(@NonNull FinishListener listener) { finishMap.put(id, listener); return this; } /* * Optional. * * ? */ @MainThread public SugarTask.Builder broken(@NonNull BrokenListener listener) { brokenMap.put(id, listener); return this; } /* * Must. * * */ @MainThread public void execute() { executor.execute(buildRunnable(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); } } } /* * SugarTask begin from this, it looks like this: * SugarTask.with().assign().handle().finish().broken().execute(); * * ??? * SugarTask.with().assign().handle().finish().broken().execute(); */ @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 SugarTask INSTANCE = new SugarTask(); } private static SugarTask 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, TaskDescription> taskMap = new ConcurrentHashMap<>(); private Map<Integer, MessageListener> messageMap = new ConcurrentHashMap<>(); private Map<Integer, FinishListener> finishMap = new ConcurrentHashMap<>(); private Map<Integer, BrokenListener> brokenMap = new ConcurrentHashMap<>(); private Executor executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 8); /* * 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) { Holder result = (Holder) message.obj; taskMap.remove(result.id); messageMap.remove(result.id); brokenMap.remove(result.id); FinishListener listener = finishMap.remove(result.id); if (listener != null) { listener.onFinish(result.object); } getInstance().dispatchUnregister(); } else if (message.what == MESSAGE_BROKEN && message.obj instanceof Holder) { Holder result = (Holder) message.obj; taskMap.remove(result.id); messageMap.remove(result.id); finishMap.remove(result.id); BrokenListener listener = brokenMap.remove(result.id); if (listener != null) { listener.onBroken((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 Runnable buildRunnable(@NonNull final Integer id) { return new Runnable() { @Override public void run() { Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); /* * TODO: * * Thread safety problem. * * */ if (taskMap.containsKey(id)) { Message message = Message.obtain(); try { message.what = MESSAGE_FINISH; message.obj = new Holder(id, taskMap.get(id).onBackground()); } 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; } }