本文同步自我是一隻香脆的大雞排
嘿,我親愛的Android老司機。
你是否還依稀記得第一次使用TextView的setText方法設定了一個空資料,得到了這麼個玩意。
(文中錯誤,見底部解釋)Caused by: java.lang.NullPointerException
對的,這玩意老煩人了,寫習慣了OC PHP等語言的程式設計師在java和android裡估計會瘋掉。
要滿世界判斷是否為空,是否等於雙引號【“”】。
我們也許是使用過TextUtils.isEmpty(s)
,StringUtils.isEmpty(s)
這種判斷。
但是無一例外它們都需要在我們設定到View之前做一次判斷。
不,我受夠了!!
Scenes1
我們來搞事情解決一下這種問題。
首先我們知道,大多數情況下控制元件上的資料都是來自服務端的介面吐出。介面中給出的資料有空的情況是非常常見的。
多數情況下後臺給出的資料如:size=1 name=null
轉成json後
會變成:{size:1,name:null}
或者這樣{size:1,"name":"null"}
或者這樣:{size:1}
看見沒,name可以是null,也可以是雙引號”null“,甚至是直接不返回。
移動端同學新手上路第一天看完返回的結果:nmpp啊啊啊啊啊啊!!!。
後端同學:你咬我?就是沒有資料呀,反正我是不會改的。
方案一
能難倒我?不改就不改。蠻了不起了吧?
看我來個初值大法。
public class SmartZero {
String name = "";
String pwd = "";
int size;
}
複製程式碼
額,如果我有一萬個欄位怎麼辦,這裡的雙引號寫一萬個記憶體會不會有影響啊?
有了,這樣寫。
public class SmartOne {
final static String NULL = "";
String name = NULL;
String pwd = NULL;
int size;
}
複製程式碼
啊哈哈,這樣就只有一個靜態引用了。
提示:
常量字串被引用時,如果內容在一致的情況下,會在常量池裡只有一份,所有的引用將指向該地址。
簡單來說,這裡根即便寫與不寫static String NULL = "";
和前者的寫法在執行時記憶體裡的變化並無區別。他們的區別僅是第一種寫法會導致暫存器上空字元會被從新設值指向空字串。所以後則的寫法可能會節約細微的時間,幾乎可以忽略不計。(詳情分析讀者可以看smali逆向檔案)
這樣寫完後得到的結果就是:
馬上就有大兄弟要說了:
你這個好low哇。不對吧,你這個只滿足了是null的情況下。
如果name服務端返回的是雙引號的“null”這種玩意,怎麼搞?
你還不是一樣要在TextView.setText之前判斷一把?
赫,大兄弟勿躁,勿躁嘛!且聽我把話說完,我們還有方案二的,堅決抵制在View層做資料髒檢查。
方案二
目前主流Gson和Fastjson是序列化json最好用的方式。大雞排這裡嘗試了下Gson的方式。我們不能直接使用Gson來轉換。這裡需要用到Gson庫裡的TypeAdapter來擴充套件一下我們自定義的物件。實現方式如下。
還是前面的物件:
public class Smart {
String name;
String pwd;
int size;
}
複製程式碼
不同的是,這裡我們沒有改動它。而是通過Adpter來解析,或者說是串改本身json中的鍵值資料。
public class SmartTypeAdapter extends TypeAdapter<Smart> {
@Override
public Smart read(JsonReader in) throws IOException {
final Smart smart = new Smart();
smart.name = "";
smart.pwd = "暫無";//初始化值是因為Gson不會遍歷在json中沒有的欄位
in.beginObject();
String s=null;
while (in.hasNext()) {
if (in.peek() == JsonToken.NULL) {
in.nextNull();
in.endObject();
return smart;
}
switch (in.nextName()) {
case "name":
if (in.peek() != JsonToken.NULL){
s = in.nextString();
if (s != null&&!s.equals("null")){
smart.name = s;
break;
}
}
smart.name = "";
break;
case "pwd":
if (in.peek() != JsonToken.NULL){
s = in.nextString();
if (s != null&&!s.equals("null")){
smart.pwd = s;
break;
}
}
smart.pwd = "暫無";
break;
case "size":
smart.size = in.nextInt();
break;
}
}
in.endObject();
return smart;
}
}
複製程式碼
現在我們執行一下看看效果。
當json為:{size:18}
的情況下。
當json為:{size:18,name:"null",pwd:"null"}
的情況下。
我們可以發現方案二的辦法要比方案一好很多,可定製化程度高。完全可以過濾掉字串"null"這種情況的髒資料。
當json為:{"size": 18,"name": "null","pwd": null}
null沒有雙引號的情況就不展示了,效果同上。
TypeAdapter的擴充套件性其實不僅僅可以用在這裡做不同型別的空判斷,它還可以做動態的json鍵值或者多型別對映。
總結
大多數情況在View層裡再做很多髒資料判斷並不合適,但實際情況我們還是這樣寫著。
直到我們學會了偷懶
不再寫
if if if
等等等於空
錯誤 錯誤 清除 清除 記憶清除 歸零
bug fixed:
1: 這裡並不會空指標,是我以前記錯了。該打臉,該打!昨天晚上從2.2的原始碼翻到7.0所有的setText方法都不會引起空指標異常。是我的疏忽。近憑藉以前的記憶去認為會空指標。給大家添麻煩了。
private void setText(CharSequence text, BufferType type,
boolean notifyBefore, int oldlen) {
if (text == null) {
text = "";
}
.....
}
複製程式碼
但是如果服務端返回空資料,我們就不管了,這個還是不夠體面的。要麼服務端改,要麼我們在中間層過濾掉。再不行就是日常if else。 可我總覺著在View層做這些事情是不夠明智的。View應該只關注自己要展示什麼東西,而不應該出現異常資料還放到了V層處理。應該在M層就要被過濾掉或處理。
一些不成熟的想法:
1.通用形DataAdpter在反序列化時進行過濾,但如果做到通用。就一定不再滿足個性化的需求。
2.我有想過Hook TextView的方法來實現髒資料偷懶行為,這不外乎兩種做法。要麼反射再注入Hook。要麼編譯時對程式碼埋點。做法都不是很完美。
3.參考lombok的實現,或許也能做到在物件Get的時候生成一些校驗。
4.依賴as做成外掛Gradle Pulgin,在編譯時像lomBok一樣。他們都不會產生後期執行時和或像反射帶來的效能影響。
拼死掙扎的Android程式設計師。