借助工廠模式構(gòu)建不同的 Fragment

還記的上篇文章 使用觀察者模式解決單 Activity 與多個 Fragment 通信 中我使用了觀察者模式暫時解決了 Activity 與多個 Fragment 之間的通信問題,最后的更新中我抽象了一個 Fragment 共同的基類:BaseFragment,在 BaseFragment 的構(gòu)造方法中傳入了 EventManager 也就是消息處理中心的實例,本來這樣是沒有問題的。直到今天,我升級了 AS 的 Gradle 的版本,然后重新編譯項目的時候,報了一個錯誤:


為什么之前的時候沒有發(fā)現(xiàn)這個錯誤吧,因為以前編譯報錯的時候,我一直是按快捷鍵 Alt + Enter 自動修正的,甚至有時候都沒看清具體的錯誤描述信息是什么就被修正了。大部分情況下這些錯誤都可以被搞定的,主要還是以前碰到的都是類型轉(zhuǎn)換之類的,看多了也就懶得再仔細(xì)看描述了。所以大概上次報錯的時候我也是直接按了快捷鍵,結(jié)果就是會關(guān)閉關(guān)于這個錯誤的警告。但是這次我特意去看了一眼,然后 Google 了一下,明白了這個錯誤是什么警告是什么意思。

以前使用 Fragment 的時候,如果需要傳入某個參數(shù),經(jīng)常就是給 Fragment 加一個構(gòu)造方法吧參數(shù)傳進(jìn)去,有時候提示如果有了有參的構(gòu)造方法,那么還需要添加無參的默認(rèn)構(gòu)造方法,而我也會順手價格無參構(gòu)造方法。但這次加了之后還是報錯,而且還是之前的提示,因為一直提到 setArguments 這個方法,說如果要傳遞參數(shù),最好使用這個方法。看了網(wǎng)絡(luò)上各大博客的解釋,就是說如果 Fragment 異常停止了,系統(tǒng)會自動重新創(chuàng)建 Fragment 的實例,但是并不會調(diào)用有參的構(gòu)造方法,而是調(diào)用默認(rèn)的無參構(gòu)造方法,而 Fragment 內(nèi)部會維護(hù)一個 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 賦到了新創(chuàng)建的 Fragment 上,所以如果繼續(xù)使用構(gòu)造方法來傳參,那么當(dāng) Fragment 重啟找不到 參數(shù)就會產(chǎn)生異常。即使你在 debug 期間關(guān)閉這個錯誤警告,當(dāng)你打 release 包的時候仍然會導(dǎo)致編譯失敗。因此就需要使用 setArguments 方法來為 Fragment 傳遞參數(shù)了。

既然我已經(jīng)抽象出了 BaseFragment ,那么我肯定不希望在每次實例化 Fragment 的時候都要寫一遍 setArguments ,最好還是只需要在 BaseFragment 中進(jìn)行處理就好了。一開始我是這么寫的:

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

但是如果這樣的返回去的就是一個 BaseFragment 的實例,肯定不能夠轉(zhuǎn)型成為其他具體的 Fragment 的,所以只好放棄了。仔細(xì)想了下,這樣中間需要加工(設(shè)置參數(shù)),然后生產(chǎn)出同一個種類但是不同口味的產(chǎn)品(具體的 Fragment 的實現(xiàn)),不就是以前了解的工廠模式的試用范圍么。立馬行動,一個簡單的工廠類就出來了:

/**
 * Created by Alpha on 2017/3/20.
 * 借助于工廠模式來構(gòu)建 fragment ,同時設(shè)置共同的參數(shù)
 */

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

看得出來這個工廠模式特別簡單,甚至嚴(yán)格來說并不能算是工廠模式,而算是一種編程習(xí)慣,因為在這里知識簡單解決了我之前的定制化的問題,并沒有什么設(shè)計思想體現(xiàn)在里面,也沒有什么深奧的封裝,不過這并沒有什么影響啊,在這里我并不需要多么復(fù)雜的模式,僅僅依靠上面的代碼就已經(jīng)可以完成我的需求了,那么就不再需要更復(fù)雜的模式來增加工作量了,否則我覺得就是過度設(shè)計了。

同時 BaseFragment 的內(nèi)容也需要有所改變了:

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);
    }
}

到這里工廠模式的部分就結(jié)束了,不知道你有沒有注意到,我在獲取 EventManager 的時候用的是 EventManager.getInstance() ,沒錯,這是個單例模式,而且是雙重檢查鎖定的單例。主要還是上一篇文章中有小伙伴問是否在多線程環(huán)境下也適用,正好我后來也確實有了多線程通信的需求,所以為了保證在多線程環(huán)境下也能夠使用,改進(jìn)了 EventManager 內(nèi)部的實現(xiàn),并且為里面的方法也加了同步保護(hù),不過我現(xiàn)在并不打算寫出來,因為我還沒有實驗過多線程環(huán)境下的可靠性,目前還只是能用的程度,所以就當(dāng)成一個小彩蛋吧。

本文最早發(fā)布在 alphagao.com.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容