Android issue – Avoid non-default constructors in fragments

序列化機制 1000 次序列化延遲 (ms) 1000 次反序列化延遲 (ms) 反射開銷 記憶體開銷
Parcelable 16.2 ms (基準 1x) 12.5 ms (基準 1x) 無 (直接記憶體塊讀寫) 極低
Serializable 104.8 ms (慢 6.5x) 98.4 ms (慢 7.8x) 極重 (動態型別反射) 高
Gson / JSON 382.1 ms (慢 23.5x) 415.6 ms (慢 33.2x) 重 (字串解析與映射) 極高

本篇重點

  1. 我遇到了 non-default constructors in fragments: use a default constructor plus Fragment#setArguments(Bundle) instead。

  2. 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

正當頭痛要用哪一套時,看到這篇文章還有這張圖

序列化機制 1000 次序列化延遲 (ms) 1000 次反序列化延遲 (ms) 反射開銷 記憶體開銷
Parcelable 16.2 ms (基準 1x) 12.5 ms (基準 1x) 無 (直接記憶體塊讀寫) 極低
Serializable 104.8 ms (慢 6.5x) 98.4 ms (慢 7.8x) 極重 (動態型別反射) 高
Gson / JSON 382.1 ms (慢 23.5x) 415.6 ms (慢 33.2x) 重 (字串解析與映射) 極高

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 給大家。

快捷鍵:Alt + Insert (Windows/Linux) 或 Cmd + N (macOS) 
-> 選擇「Parcelable」
-> 自動生成 describeContents()、writeToParcel() 與 CREATOR 模板程式碼。

一個 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/

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *