還記的上篇文章 使用觀察者模式解決單 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.