本篇重點
- 我遇到了 non-default constructors in fragments: use a default constructor plus Fragment#setArguments(Bundle) instead。
- Parcelable 很厲害。
這次開發 side project 用到大量的 fragment出於方便的情況就使用底下的用法
public class MainPageTabFragment extends Fragment { String tab; User user; public MainPageTabFragment(String tab , User user) { this.tab = tab; this.user = user; } } // How to use MainPageTabFragment fragment = new MainPageTabFragment(tab, user);
這在用 debug build 時並沒有遇到任何問題,但是到要用 release build 時卻出現了 “Avoid non-default constructors in fragments: use a default constructor plus Fragment#setArguments(Bundle) instead” 這樣的提示錯誤,導致 release build 不過,主因是由於 lint check code ,並同時建議使用 setArgument() 來取代使用含有參數的建構子。
看到這邊如果就只是想單純解決 lint 的問題,那提供最簡單的方法~~~~~那就不要檢查啊,只要在 build.gradle 內加入底下的 code
android { lintOptions { checkReleaseBuilds false } }
或是在 fragment 加上 @SuppressLint(“ValidFragment”)
ref:stackoverflow
到這裡,就解決了 lint 導致 release build 的問題,客倌想先交差給老闆的話,這邊可以收工啦,如果還有興趣的話就繼續來研究為何 lint 不建議這樣搞。
主要原因是 fragment 的特性
『All subclasses of Fragment must include a public no-argument constructor. The framework will often re-instantiate a fragment class when needed, in particular during state restore, and needs to be able to find this constructor to instantiate it. If the no-argument constructor is not available, a runtime exception will occur in some cases during state restore.』
結論就是在某些情況下(螢幕方向轉換),fragment 被系統重建時,只會呼叫無參數的建構子,你特地用有參數的建構子存起來的參數不會被存起來。看到這邊就知道,過去的用法多危險(雖然我鎖定螢幕方向了,但還是很難保證有啥鬼情況),因此接下來就來重構程式碼
public static MainPageTabFragment newInstance(String tab, User user) { MainPageTabFragment fragment = new MainPageTabFragment(); Bundle argument = new Bundle(); argument.putString(MainPageTabFragment.KEY_TAB, tab); argument.putParcelable(MainPageTabFragment.KEY_LOGIN_USER, user); fragment.setArguments(argument); return fragment; } // then get argument on onCreate() or onCreateView() public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { Bundle argument = getArguments(); this.tab = argument.getString(KEY_TAB); this.loginUser = (User) argument.getParcelable(KEY_LOGIN_USER); } // How to use MainPageTabFragment fragment = MainPageTabFragment.newInstance(tab,user);
看到這邊發現一件糟糕的事情,就是 User 並非 java 基本型別,根本無法放進 Bundle 裡面,那就不能 setArgument(),然後整套重來。
事情當然沒有那麼嚴重,把 User 序列化就可以了,序列化的方法不少(自己寫都可以),但目前最主流的有三種
1. Serializable
2. Parcelable
3. Gson
正當頭痛要用哪一套時,看到這篇文章還有這張圖
Parcelable 超級快,就決定是你了。(至於為何那麼快,可以詳細閱讀文章,主要是 gson and Serializable 都有需要型別映射的時間)定案後接者就要來實作 User 的 Parcelable interface,這部分主要有幾個模塊
// 講一下做啥 @Override public int describeContents() { return 0; } // 寫入容器 parcel @Override public void writeToParcel(Parcel dest, int flags) { dest.writeValue(this.id); dest.writeString(this.userName); dest.writeString(this.email); dest.writeString(this.userID); dest.writeString(this.userCode); dest.writeString(this.userIcon); } // 傳入容器 parcel protected User(Parcel in) { this.id = (Long) in.readValue(Long.class.getClassLoader()); this.userName = in.readString(); this.email = in.readString(); this.userID = in.readString(); this.userCode = in.readString(); this.userIcon = in.readString(); } // 提供還原方法 public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { @Override public User createFromParcel(Parcel source) { return new User(source); } @Override public User[] newArray(int size) { return new User[size]; } };
最後提一下,上面的 Parcelable 根本就是 dirty code ,所以直覺一定有自動化工具,所以分享 android parcelable code generator 給大家。
一個 CLICK 就全部自動生出 CODE,感謝各位看到這裡。打烊啦
ref:
http://www.developerphil.com/parcelable-vs-serializable/
http://blog.csdn.net/kroclin/article/details/40902721
http://blog.prolificinteractive.com/2014/07/18/why-we-love-parcelable/