借助工厂模式构建不同的 Fragment

还记的上篇文章 使用观察者模式解决单 Activity 与多个 Fragment 通信 中我使用了观察者模式暂时解决了 Activity 与多个 Fragment 之间的通信问题,最后的更新中我抽象了一个 Fragment 共同的基类:BaseFragment,在 BaseFragment 的构造方法中传入了 EventManager 也就是消息处理中心的实例,本来这样是没有问题的。直到今天,我升级了 AS 的 Gradle 的版本,然后重新编译项目的时候,报了一个错误:

《借助工厂模式构建不同的 Fragment》

为什么之前的时候没有发现这个错误吧,因为以前编译报错的时候,我一直是按快捷键 Alt + Enter 自动修正的,甚至有时候都没看清具体的错误描述信息是什么就被修正了。大部分情况下这些错误都可以被搞定的,主要还是以前碰到的都是类型转换之类的,看多了也就懒得再仔细看描述了。所以大概上次报错的时候我也是直接按了快捷键,结果就是会关闭关于这个错误的警告。但是这次我特意去看了一眼,然后 Google 了一下,明白了这个错误是什么警告是什么意思。

以前使用 Fragment 的时候,如果需要传入某个参数,经常就是给 Fragment 加一个构造方法吧参数传进去,有时候提示如果有了有参的构造方法,那么还需要添加无参的默认构造方法,而我也会顺手价格无参构造方法。但这次加了之后还是报错,而且还是之前的提示,因为一直提到 setArguments 这个方法,说如果要传递参数,最好使用这个方法。看了网络上各大博客的解释,就是说如果 Fragment 异常停止了,系统会自动重新创建 Fragment 的实例,但是并不会调用有参的构造方法,而是调用默认的无参构造方法,而 Fragment 内部会维护一个 Bundle 类型的变量,名字就叫 mArguments ,在 Fragment 重建的某个时期,会自动将 mArguments set 到新的实例上,可以看看 Fragment 的源码:

public static Fragment instantiate(Context context, String fname, @Nullable Bundle args) {
        try {
            Class<?> clazz = sClassMap.get(fname);
            if (clazz == null) {
                // Class not found in the cache, see if it's real, and try to add it
                clazz = context.getClassLoader().loadClass(fname);
                sClassMap.put(fname, clazz);
            }
            Fragment f = (Fragment)clazz.newInstance();
            if (args != null) {
                args.setClassLoader(f.getClass().getClassLoader());
                f.mArguments = args;//重点在这里
            }
            return f;
        } catch (ClassNotFoundException e) {
            throw new InstantiationException("Unable to instantiate fragment " + fname
                    + ": make sure class name exists, is public, and has an"
                    + " empty constructor that is public", e);
        } catch (java.lang.InstantiationException e) {
            throw new InstantiationException("Unable to instantiate fragment " + fname
                    + ": make sure class name exists, is public, and has an"
                    + " empty constructor that is public", e);
        } catch (IllegalAccessException e) {
            throw new InstantiationException("Unable to instantiate fragment " + fname
                    + ": make sure class name exists, is public, and has an"
                    + " empty constructor that is public", e);
        }
    }

重点是 try 语句块中的第二个 if 语句块,将 mArguments 赋到了新创建的 Fragment 上,所以如果继续使用构造方法来传参,那么当 Fragment 重启找不到 参数就会产生异常。即使你在 debug 期间关闭这个错误警告,当你打 release 包的时候仍然会导致编译失败。因此就需要使用 setArguments 方法来为 Fragment 传递参数了。

既然我已经抽象出了 BaseFragment ,那么我肯定不希望在每次实例化 Fragment 的时候都要写一遍 setArguments ,最好还是只需要在 BaseFragment 中进行处理就好了。一开始我是这么写的:

    public BaseFragment getInstance(EventManager manager) {
        BaseFragment fragment = new BaseFragment();
        Bundle bundle = new Bundle();
        bundle.putParcelable("event_manager", manager);
        fragment.setArguments(bundle);
        return fragment;
    }

但是如果这样的返回去的就是一个 BaseFragment 的实例,肯定不能够转型成为其他具体的 Fragment 的,所以只好放弃了。仔细想了下,这样中间需要加工(设置参数),然后生产出同一个种类但是不同口味的产品(具体的 Fragment 的实现),不就是以前了解的工厂模式的试用范围么。立马行动,一个简单的工厂类就出来了:

/** * Created by Alpha on 2017/3/20. * 借助于工厂模式来构建 fragment ,同时设置共同的参数 */

public class FragmentFactory {

    public static final int TYPE_AGENDA = 1;
    public static final int TYPE_CONTEXT = 2;
    public static final int TYPE_EVENT = 3;
    public static final int TYPE_FINISH = 4;
    public static final int TYPE_INBOX = 5;
    public static final int TYPE_MEMO = 6;
    public static final int TYPE_PROJECT = 7;
    public static final int TYPE_TODO = 8;
    public static final int TYPE_TRASH = 9;

    private static Map<Integer, Fragment> mFragments = new HashMap<>();

    public static Fragment create(Integer fragmentName) {
        Fragment fragment = mFragments.get(fragmentName);
        if (fragment == null) {
            switch (fragmentName) {
                case TYPE_AGENDA:
                    fragment = new AgendaFragment();
                    break;
                case TYPE_CONTEXT:
                    fragment = new ContextFragment();
                    break;
                case TYPE_EVENT:
                    fragment = new EventFragment();
                    break;
                case TYPE_FINISH:
                    fragment = new FinishFragment();
                    break;
                case TYPE_INBOX:
                    fragment = new InboxFragment();
                    break;
                case TYPE_MEMO:
                    fragment = new MemoFragment();
                    break;
                case TYPE_PROJECT:
                    fragment = new ProjectFragment();
                    break;
                case TYPE_TODO:
                    fragment = new ToDoFragment();
                    break;
                case TYPE_TRASH:
                    fragment = new TrashFragment();
                    break;
            }
            Bundle bundle = new Bundle();
            bundle.putParcelable("event_manager", EventManager.getInstance());
            fragment.setArguments(bundle);
            if (fragment != null) {
                mFragments.put(fragmentName, fragment);
            }
        }
        return fragment;
    }
}

看得出来这个工厂模式特别简单,甚至严格来说并不能算是工厂模式,而算是一种编程习惯,因为在这里知识简单解决了我之前的定制化的问题,并没有什么设计思想体现在里面,也没有什么深奥的封装,不过这并没有什么影响啊,在这里我并不需要多么复杂的模式,仅仅依靠上面的代码就已经可以完成我的需求了,那么就不再需要更复杂的模式来增加工作量了,否则我觉得就是过度设计了。

同时 BaseFragment 的内容也需要有所改变了:

public class BaseFragment extends Fragment implements Observer {
    private static final String TAG = "BaseFragment";
    protected EventManager eventManager;
    protected Handler handler;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        Bundle bundle = getArguments();
        eventManager = bundle.getParcelable("event_manager");
        eventManager.registerObserver(this);
        super.onCreate(savedInstanceState);
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        if (context instanceof MainActivity) {
            MainActivity activity = (MainActivity) context;
            this.handler = activity.mHandler;
        }
    }

    @Override
    public void onUpdate(Message msg) {
        throw new RuntimeException("must override this method in Observer!");
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        eventManager.removeObserver(this);
    }
}

到这里工厂模式的部分就结束了,不知道你有没有注意到,我在获取 EventManager 的时候用的是 EventManager.getInstance() ,没错,这是个单例模式,而且是双重检查锁定的单例。主要还是上一篇文章中有小伙伴问是否在多线程环境下也适用,正好我后来也确实有了多线程通信的需求,所以为了保证在多线程环境下也能够使用,改进了 EventManager 内部的实现,并且为里面的方法也加了同步保护,不过我现在并不打算写出来,因为我还没有实验过多线程环境下的可靠性,目前还只是能用的程度,所以就当成一个小彩蛋吧。

本文最早发布在 alphagao.com.

    原文作者:算法小白
    原文地址: https://juejin.im/entry/58d1349e44d90400684fb275
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞