剛出來工做,就負責一個APP的某塊功能的編寫,該功能就是相似微博那樣的界面。微博界面的編寫其實是很是複雜的,雖然它只是一個ListView,但要想讓這個ListView滑得動,是的,在一些配置低的手機,隨便一個負載內容多的Item,都有可能致使OOM。。。若是隻是簡單的爲了實現了效果,能夠選擇將全部內容都寫在xml文件,若是佈局很差的話,就會出現嵌套過多的狀況,一樣也會出現OOM的狀況。。。就算不會出現OOM的狀況,也能滑得動,也會面臨是否可以滑得快。。。算法
要想能滑得動,也能滑得快,就要動點腦筋了。緩存
一開始很是簡單的代碼就是這樣:app
@Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder; if (convertView == null) { convertView = inflater.inflate(R.layout.list_item, null); holder = new ViewHolder(); …… convertView.setTag(holder); } else { holder = (ViewHolder)convertView.getTag(); } } /** * ViewHolder */ private static class ViewHolder { ImageView appIcon; TextView appName; TextView appInfo; }
這是谷歌推薦的方式,實際上也解決了不少性能上的問題。異步
Android原生的ListView本來就作了相應的緩存機制,Recycler。ide
Recycler的工做原理大體以下:組件化
假設屏幕最多能看到11個item,那麼當第1個item滾出屏幕,這個item的View進入RecycleBin中,第12個要出現前,經過 getView從回收站(RecycleBin)中重用這個View,而後設置數據,而沒必要從新建立一個View。佈局
這樣即便有上萬個Item,inflate的次數最多就是n,也就是一個屏幕可以容納的個數。性能
大部分的狀況均可以用這樣的代碼解決,但我以爲對於每一個Adapter都要寫一個ViewHolder實在是太麻煩了,因而進一步將代碼改寫爲這樣:測試
@Override public View getView(int position, View convertView, ViewGroup parent) { if (convertView == null) { convertView = inflater.inflate(R.layout.list_item, null); } ImageView icon = (ImageView)CommonViewHolder.get(convertView, R.id.image_view); }
其中CommonViewHolder的代碼以下:優化
public class CommonViewHolder { /** * 用於獲取ItemView中的控件 * * @param view ItemView * @param id 要獲取的控件的id * @param <T> 返回的控件的類型 * @return 返回的控件 */ public static <T extends View> T get(View view, int id) { SparseArrayCompat<View> viewHolder = (SparseArrayCompat<View>) view.getTag(); if (viewHolder == null) { viewHolder = new SparseArrayCompat<>(); view.setTag(viewHolder); } View childView = viewHolder.get(id); if (childView == null) { childView = view.findViewById(id); viewHolder.put(id, childView); } return (T) childView; } }
實際上,ViewHolder就是經過setTag方法將相應的控件做爲View的屬性保存起來,而後下次使用的時候就能夠直接複用。
既然明白了它的原理,就能夠對它進行改造,CommonViewHolder是利用SparseArrayCompact存儲控件。SparseArrayCompact是SparseArrayCompact是SparseArray的兼容類,本質上就是相似Map的鍵值對容器,谷歌宣稱它的性能比Map要好,由於內在的算法已經優化了。
這裏的工做很簡單,一樣是利用setTag方法保存View中的控件,可是卻把findViewById這樣的工做放在了CommonViewHolder中,這樣就不用每次都要調用findViewById方法了。
爲了考慮通用,還使用泛型。
代碼量瞬間就減小了不少,但這時就面臨了一個問題:Item項錯亂了。。。
在快速滑動的時候,圖片加載錯了,仔細調試,就發現是Recycler機制出現了問題。當快速滑動的時候,本來應該開始加載圖片的控件已經滑出屏幕,而後個人圖片加載是異步的,因此圖片就會加載在後面的Item上。
解決這個問題的方法一樣得利用setTag方法:
icon.setTag(imageUrl) .... if(imageUrl.equals(icon.getTag()){ .... }
經過setTag保存ImageView要加載的url信息,而後在下次加載的時候判斷是否相同。
問題解決了,但看到getView方法中爲了實現微博這樣承載大量信息的界面,不得不編寫大量的業務代碼,並且這樣複用性不好,由於微博詳情的界面和列表項的界面基本同樣,只是有一些不一樣而已。若是再寫一個,就有點傻逼了,但若是不寫,getView方法中確定又要寫更多的判斷。
爲了解決複用的問題,就開始組件化。
將微博界面拆成兩個部分:HeaderView和BodyView。HeaderView負責微博做者的基本信息,而BodyView就是微博內容。
這樣,我只要在getView方法中這樣寫:
add(new HeaderView()); ... add(new BodyView()); ...
也就是說,我從一個靜態佈局改爲動態佈局,這樣我在詳情那裏也能夠複用。
到了這裏本來也應該結束了,但我又想要爲微博業務編寫測試,但Android中View和業務的代碼是各類糾纏,很難徹底脫離View來測試業務。
通過思考和查找資料,我找到了一種方式:ViewModel。
編寫相應的ViewModel做爲Controller,就能夠將View和業務的代碼解綁:
public class ViewModel{ private String text; ... public void setText(TextView view){ view.setText(text); } }
這樣組件裏面的代碼就更少了,它只要聲明好控件而後傳進來就好了,數據的獲取和綁定都在ViewModel這裏。
而後咱們來寫一個簡單的測試:
ViewModel model = new ViewModel(); Button button = new Button(context); button.setText("你好"); model.changeButtonText(button); assertEquals("我好", button.getText()); public void changeButtonText(Button button){ button.setOnClickListener(new OnClickListener(){ button.setText("我好"); }); }
利用ViewModel,咱們能夠方便的測試Android種的業務。
這是到目前爲止的思考和嘗試,實際上,我認爲代碼還會不斷演化下去,如今已經開始出現MVVM的一些思想的應用,到了最後,沒準就會徹底演化成MVVM模式。
簡單的代碼,只要不斷思考,慢慢的,所能學到的東西就會變得愈來愈多,最後甚至超出咱們的想象。