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

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


为什么之前的时候没有发现这个错误吧,因为以前编译报错的时候,我一直是按快捷键 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<>();
    private static SparseArray<Fragment> mFragments = new SparseArray<>();//更新于 3 月 22 日

    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.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容