從事Android以來的相關(guān)總結(jié)6-熟悉掌握ListView的優(yōu)化及異步任務(wù)加載網(wǎng)絡(luò)數(shù)據(jù)


Android四大組件

1.ContentProvider

共享應(yīng)用程序內(nèi)的數(shù)據(jù),在數(shù)據(jù)修改時可以監(jiān)聽

1.Activity

供用戶操作的界面

2.BroadcastReceiver

用來接收廣播,可以根據(jù)系統(tǒng)發(fā)生的一些時間做出一些處理

3.Service

長期在后臺運(yùn)行的,沒有界面的組件,用來在后臺執(zhí)行一些耗時的操作

?熟悉掌握ListView的優(yōu)化及異步任務(wù)加載網(wǎng)絡(luò)數(shù)據(jù)

一、異步任務(wù)加載網(wǎng)絡(luò)數(shù)據(jù):

在Android中提供了一個異步任務(wù)的類AsyncTask,簡單來說,這個類中的任務(wù)是運(yùn)行在后臺線程中的,并可以將結(jié)果放到UI線程中進(jìn)行處理,它定義了三種泛型,分別是Params、Progress和Result,分別表示請求的參數(shù)、任務(wù)的進(jìn)度和獲得的結(jié)果數(shù)據(jù)。

1、使用原因:

1)是其中使用了線程池技術(shù),而且其中的方法很容易實現(xiàn)調(diào)用

2)可以調(diào)用相關(guān)的方法,在開啟子線程前和后,進(jìn)行界面的更新

3)一旦任務(wù)多了,不用每次都new新的線程,可以直接使用

2、執(zhí)行的順序:

onPreExecute()【執(zhí)行前開啟】--- > doInBackground() --- > onProgressUpdate() --- >onPostExecute()

3、執(zhí)行過程:

當(dāng)一個異步任務(wù)開啟后,執(zhí)行過程如下:

1)、onPreExecute():

這個方法是執(zhí)行在主線程中的。這步操作是用于準(zhǔn)備好任務(wù)的,作為任務(wù)加載的準(zhǔn)備工作。建議在這個方法中彈出一個提示框。

2)、doInBackground():

這個方法是執(zhí)行在子線程中的。在onPreExecute()執(zhí)行完后,會立即開啟這個方法,在方法中可以執(zhí)行耗時的操作。需要將請求的參數(shù)傳遞進(jìn)來,發(fā)送給服務(wù)器,并將獲取到的數(shù)據(jù)返回,數(shù)據(jù)會傳給最后一步中;這些值都將被放到主線程中,也可以不斷的傳遞給下一步的onProgressUpdate()中進(jìn)行更新。可以通過不斷調(diào)用publishProgress(),將數(shù)據(jù)(或進(jìn)度)不斷傳遞給onProgressUpdate()方法,進(jìn)行不斷更新界面。

3)、onProgressUpdate():

這個方法是執(zhí)行在主線程中的。publishProgress()在doInBackground()中被調(diào)用后,才開啟的這個方法,它在何時被開啟是不確定的,執(zhí)行這個方法的過程中,doInBackground()是仍在執(zhí)行的,即子線程還在運(yùn)行著。

4)、onPostExecute():

這個方法是執(zhí)行在主線程中的。當(dāng)后臺的子線程執(zhí)行完畢后才調(diào)用此方法。doInBackground()返回的結(jié)果會作為參數(shù)被傳遞過來。可以在這個方法中進(jìn)行更新界面的操作。

5)、execute():

最后創(chuàng)建AsyncTask自定義的類,開啟異步任務(wù)。

3、實現(xiàn)原理:

1)、線程池的創(chuàng)建:

在創(chuàng)建了AsyncTask的時候,會默認(rèn)創(chuàng)建一個線程池ThreadPoolExecutor,并默認(rèn)創(chuàng)建出5個線程放入到線程池中,最多可防128個線程;且這個線程池是公共的唯一一份。

2)、任務(wù)的執(zhí)行:

在execute中,會執(zhí)行run方法,當(dāng)執(zhí)行完run方法后,會調(diào)用scheduleNext()不斷的從雙端隊列中輪詢,獲取下一個任務(wù)并繼續(xù)放到一個子線程中執(zhí)行,直到異步任務(wù)執(zhí)行完畢。

3)、消息的處理:

在執(zhí)行完onPreExecute()方法之后,執(zhí)行了doInBackground()方法,然后就不斷的發(fā)送請求獲取數(shù)據(jù);在這個AsyncTask中維護(hù)了一個InternalHandler的類,這個類是繼承Handler的,獲取的數(shù)據(jù)是通過handler進(jìn)行處理和發(fā)送的。在其handleMessage方法中,將消息傳遞給onProgressUpdate()進(jìn)行進(jìn)度的更新,也就可以將結(jié)果發(fā)送到主線程中,進(jìn)行界面的更新了。

4、需要注意的是:

①、這個AsyncTask類必須由子類調(diào)用

②、雖然是放在子線程中執(zhí)行的操作,但是不建議做特別耗時的操作,如果操作過于耗時,建議使用線程池ThreadPoolExecutor和FutureTask

示例代碼:


二、ListView優(yōu)化:

ListView的工作原理

首先來了解一下ListView的工作原理(可參見http://mobile.51cto.com/abased-410889.htm),如圖:

1、如果你有幾千幾萬甚至更多的選項(item)時,其中只有可見的項目存在內(nèi)存(內(nèi)存內(nèi)存哦,說的優(yōu)化就是說在內(nèi)存中的優(yōu)化!!!)中,其他的在Recycler中

2、ListView先請求一個type1視圖(getView)然后請求其他可見的項目。convertView在getView中是空(null)的

3、當(dāng)item1滾出屏幕,并且一個新的項目從屏幕低端上來時,ListView再請求一個type1視圖。convertView此時不是空值了,它的值是item1。你只需設(shè)定新的數(shù)據(jù)然后返回convertView,不必重新創(chuàng)建一個視圖


一、復(fù)用convertView

,減少findViewById

的次數(shù)

1、優(yōu)化一:復(fù)用convertView

Android系統(tǒng)本身為我們考慮了ListView的優(yōu)化問題,在復(fù)寫的Adapter的類中,比較重要的兩個方法是getCount()和getView()。界面上有多少個條顯示,就會調(diào)用多少次的getView()方法;因此如果在每次調(diào)用的時候,如果不進(jìn)行優(yōu)化,每次都會使用View.inflate(….)的方法,都要將xml文件解析,并顯示到界面上,這是非常消耗資源的:因為有新的內(nèi)容產(chǎn)生就會有舊的內(nèi)容銷毀,所以,可以復(fù)用舊的內(nèi)容。

優(yōu)化:

在getView()方法中,系統(tǒng)就為我們提供了一個復(fù)用view的歷史緩存對象convertView,當(dāng)顯示第一屏的時候,每一個item都會新創(chuàng)建一個view對象,這些view都是可以被復(fù)用的;如果每次顯示一個view都要創(chuàng)建一個,是非常耗費內(nèi)存的;所以為了節(jié)約內(nèi)存,可以在convertView不為null的時候,對其進(jìn)行復(fù)用

2、優(yōu)化二:緩存item條目的引用——ViewHolder

findViewById()這個方法是比較耗性能的操作,因為這個方法要找到指定的布局文件,進(jìn)行不斷地解析每個節(jié)點:從最頂端的節(jié)點進(jìn)行一層一層的解析查詢,找到后在一層一層的返回,如果在左邊沒找到,就會接著解析右邊,并進(jìn)行相應(yīng)的查詢,直到找到位置(如圖)。因此可以對findViewById進(jìn)行優(yōu)化處理,需要注意的是:

》》》》特點:xml文件被解析的時候,只要被創(chuàng)建出來了,其孩子的id就不會改變了。根據(jù)這個特點,可以將孩子id存入到指定的集合中,每次就可以直接取出集合中對應(yīng)的元素就可以了。


優(yōu)化:

在創(chuàng)建view對象的時候,減少布局文件轉(zhuǎn)化成view對象的次數(shù);即在創(chuàng)建view對象的時候,把所有孩子全部找到,并把孩子的引用給存起來

①定義存儲控件引用的類ViewHolder

這里的ViewHolder類需要不需要定義成static,根據(jù)實際情況而定,如果item不是很多的話,可以使用,這樣在初始化的時候,只加載一次,可以稍微得到一些優(yōu)化

不過,如果item過多的話,建議不要使用。因為static是Java中的一個關(guān)鍵字,當(dāng)用它來修飾成員變量時,那么該變量就屬于該類,而不是該類的實例。所以用static修飾的變量,它的生命周期是很長的,如果用它來引用一些資源耗費過多的實例(比如Context的情況最多),這時就要盡量避免使用了。

classViewHolder{

//定義item中相應(yīng)的控件

}

②創(chuàng)建自定義的類:ViewHolderholder = null;

③將子view添加到holder中:

在創(chuàng)建新的listView的時候,創(chuàng)建新的ViewHolder,把所有孩子全部找到,并把孩子的引用給存起來

通過view.setTag(holder)將引用設(shè)置到view中

通過holder,將孩子view設(shè)置到此holder中,從而減少以后查詢的次數(shù)

④在復(fù)用listView中的條目的時候,通過view.getTag(),將view對象轉(zhuǎn)化為holder,即轉(zhuǎn)化成相應(yīng)的引用,方便在下次使用的時候存入集合。

通過view.getTag(holder)獲取引用(需要強(qiáng)轉(zhuǎn))

示例代碼:


二、ListView

中數(shù)據(jù)的分批及分頁加載:

需求:

ListView有一萬條數(shù)據(jù),如何顯示;如果將十萬條數(shù)據(jù)加載到內(nèi)存,很消耗內(nèi)存

解決辦法:

優(yōu)化查詢的數(shù)據(jù):先獲取幾條數(shù)據(jù)顯示到界面上

進(jìn)行分批處理---à優(yōu)化了用戶體驗

進(jìn)行分頁處理---à優(yōu)化了內(nèi)存空間

說明:

一般數(shù)據(jù)都是從數(shù)據(jù)庫中獲取的,實現(xiàn)分批(分頁)加載數(shù)據(jù),就需要在對應(yīng)的DAO中有相應(yīng)的分批(分頁)獲取數(shù)據(jù)的方法,如findPartDatas ()

1、準(zhǔn)備數(shù)據(jù):

在dao中添加分批加載數(shù)據(jù)的方法:findPartDatas ()

在適配數(shù)據(jù)的時候,先加載第一批的數(shù)據(jù),需要加載第二批的時候,設(shè)置監(jiān)聽檢測何時加載第二批

2、設(shè)置ListView的滾動監(jiān)聽器:setOnScrollListener(new OnScrollListener{….})

①、在監(jiān)聽器中有兩個方法:滾動狀態(tài)發(fā)生變化的方法(onScrollStateChanged)和listView被滾動時調(diào)用的方法(onScroll)

②、在滾動狀態(tài)發(fā)生改變的方法中,有三種狀態(tài):

手指按下移動的狀態(tài):SCROLL_STATE_TOUCH_SCROLL: //觸摸滑動

慣性滾動(滑翔(flgin)狀態(tài)):SCROLL_STATE_FLING: //滑翔

靜止?fàn)顟B(tài):SCROLL_STATE_IDLE://靜止

3、對不同的狀態(tài)進(jìn)行處理:

分批加載數(shù)據(jù),只關(guān)心靜止?fàn)顟B(tài):關(guān)心最后一個可見的條目,如果最后一個可見條目就是數(shù)據(jù)適配器(集合)里的最后一個,此時可加載更多的數(shù)據(jù)。在每次加載的時候,計算出滾動的數(shù)量,當(dāng)滾動的數(shù)量大于等于總數(shù)量的時候,可以提示用戶無更多數(shù)據(jù)了。

示例代碼:

/給listview注冊一個滾動的監(jiān)聽器.

lv_call_sms_safe.setOnScrollListener(newOnScrollListener() {

? ? ? ? ? ? ? ? ? ? ?//當(dāng)滾動狀體發(fā)生變化的時候調(diào)用的方法

? ? ? ? ? ? ? ? ? ? ?@Override

? ? ? ? ? ? ? ? ? ? ? publicvoid onScrollStateChanged(AbsListView view, int scrollState) {

? ? ? ? ? ? ? ? ? ? ? ? ? ? ?switch(scrollState) {

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?caseSCROLL_STATE_FLING: //滑翔

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? break;

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? caseSCROLL_STATE_IDLE: //靜止

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?//在靜止?fàn)顟B(tài)下關(guān)心最后一個可見的條目如果最后一個可見條目就是數(shù)據(jù)適配器里面的最后一個,加載更多數(shù)據(jù).

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? intposition = lv_call_sms_safe.getLastVisiblePosition(); //位置從0開始

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? intsize = blackNumbers.size();//從1開始的.

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? if(position == (size - 1)) {

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? Log.i(TAG,"拖動到了最后一個條目,加載更多數(shù)據(jù)");

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?startIndex+= maxNumber;

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?if(startIndex>=totalCount){

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? Toast.makeText(getApplicationContext(),"沒有更多數(shù)據(jù)了..",0).show();

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? return;

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?}

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? fillData();

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?break;

? ? ? ? ? ? ? ? ? ? ? ? ?}

? ? ? ? ? ? ? ? ? ? ? ? ? break;

? ? ? ? ? ? ? ? ? ? ? ? ?caseSCROLL_STATE_TOUCH_SCROLL: //觸摸滑動

? ? ? ? ? ? ? ? ? ? ? ? ? break;

}

}

//當(dāng)listview被滾動的時候調(diào)用的方法

@Override

publicvoid onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, inttotalItemCount) {

}

});

/**

*填充數(shù)據(jù)

*/

? ? ? ? ? ? ? ? private void fillData() {

? ? ? ? ? ? ? ? ? ? ? ? ? ? //通知用戶一下正在獲取數(shù)據(jù)

? ? ? ? ? ? ? ? ? ? ? ? ? ? ?ll_loading.setVisibility(View.VISIBLE);

? ? ? ? ? ? ? ? ? ? ? ? ? ? ?newThread() {

? ? ? ? ? ? ? ? ? ? ? ? ? ? ?publicvoid run() {

? ? ? ? ? ? ? ? ? ? ? ? ? ? //獲取全部的黑名單號碼

? ? ? ? ? ? ? ? ? ? ? ? ? ? if(blackNumbers != null) {

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?blackNumbers.addAll(dao.findPartBlackNumbers(startIndex,maxNumber));

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? }else {

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?blackNumbers= dao.findPartBlackNumbers(startIndex,maxNumber);

? ? ? ? ? ? ? ? ? ? ? ?}

? ? ? ? ? ? ? ? ? ? ? ? ?handler.sendEmptyMessage(0);

? ? ? ? ? ? ? ? ?//lv_call_sms_safe.setAdapter(new CallSmsSafeAdapter());

? ? ? ? ? ? ? ? ? ? ? ? ? };

? ? ? ? ? ? ? ? ? ? ? ?}.start();

}



三、復(fù)雜ListView的處理:(待進(jìn)一步總結(jié))

說明:

listView的界面顯示是通過getCount和getView這兩個方法來控制的

getCount:返回有多少個條目

getView:返回每個位置條目顯示的內(nèi)容

提供思路:

對于含有多個類型的item的優(yōu)化處理:由于ListView只有一個Adapter的入口,可以定義一個總的Adapter入口,存放各種類型的Adapter

以安全衛(wèi)士中的進(jìn)程管理的功能為例。效果如圖:

1、定義兩個(或多個)集合

每個集合中存入的是對應(yīng)不同類型的內(nèi)容(這里為:用戶程序(userAppinfos)和系統(tǒng)程序的集合(systemAppinfos))

2、在初始化數(shù)據(jù)(填充數(shù)據(jù))中初始化兩個集合

如,此處是在fillData方法中初始化

3、在數(shù)據(jù)適配器中,復(fù)寫對應(yīng)的方法

getCount():計算所有需要顯示的條目個數(shù),這里包括listView和textView

getView():對顯示在不同位置的條目進(jìn)行if處理

4、數(shù)據(jù)類型的判斷

需要注意的是,在復(fù)用view的時候,需要對convertView進(jìn)行類型判斷,是因為這里含有各種不同類型的view,在view滾動顯示的時候,對于不同類型的view不能復(fù)用,所有需要判斷

示例代碼:

獲取條目個數(shù)


類型判斷:


getView中條目位置的選擇:


四、ListView中圖片的優(yōu)化:

1、處理圖片的方式:

如果自定義Item中有涉及到圖片等等的,一定要狠狠的處理圖片,圖片占的內(nèi)存是ListView項中最惡心的,處理圖片的方法大致有以下幾種:

①、不要直接拿路徑就去循環(huán)decodeFile();使用Option保存圖片大小、不要加載圖片到內(nèi)存去

②、拿到的圖片一定要經(jīng)過邊界壓縮

③、在ListView中取圖片時也不要直接拿個路徑去取圖片,而是以WeakReference(使用WeakReference代替強(qiáng)引用。

比如可以使用WeakReference?mContextRef)、SoftReference、WeakHashMap等的來存儲圖片信息,是圖片信息不是圖片哦!

④、在getView中做圖片轉(zhuǎn)換時,產(chǎn)生的中間變量一定及時釋放

2、異步加載圖片基本思想:

1)、先從內(nèi)存緩存中獲取圖片顯示(內(nèi)存緩沖)

2)、獲取不到的話從SD卡里獲取(SD卡緩沖)

3)、都獲取不到的話從網(wǎng)絡(luò)下載圖片并保存到SD卡同時加入內(nèi)存并顯示(視情況看是否要顯示)

原理:

優(yōu)化一:先從內(nèi)存中加載,沒有則開啟線程從SD卡或網(wǎng)絡(luò)中獲取,這里注意從SD卡獲取圖片是放在子線程里執(zhí)行的,否則快速滑屏的話會不夠流暢。

優(yōu)化二:于此同時,在adapter里有個busy變量,表示listview是否處于滑動狀態(tài),如果是滑動狀態(tài)則僅從內(nèi)存中獲取圖片,沒有的話無需再開啟線程去外存或網(wǎng)絡(luò)獲取圖片。

優(yōu)化三:ImageLoader里的線程使用了線程池,從而避免了過多線程頻繁創(chuàng)建和銷毀,有的童鞋每次總是new一個線程去執(zhí)行這是非常不可取的,好一點的用的AsyncTask類,其實內(nèi)部也是用到了線程池。在從網(wǎng)絡(luò)獲取圖片時,先是將其保存到sd卡,然后再加載到內(nèi)存,這么做的好處是在加載到內(nèi)存時可以做個壓縮處理,以減少圖片所占內(nèi)存。

Tips:這里可能出現(xiàn)圖片亂跳(錯位)的問題:

圖片錯位問題的本質(zhì)源于我們的listview使用了緩存convertView,假設(shè)一種場景,一個listview一屏顯示九個item,那么在拉出第十個item的時候,事實上該item是重復(fù)使用了第一個item,也就是說在第一個item從網(wǎng)絡(luò)中下載圖片并最終要顯示的時候,其實該item已經(jīng)不在當(dāng)前顯示區(qū)域內(nèi)了,此時顯示的后果將可能在第十個item上輸出圖像,這就導(dǎo)致了圖片錯位的問題。所以解決之道在于可見則顯示,不可見則不顯示。在ImageLoader里有個imageViews的map對象,就是用于保存當(dāng)前顯示區(qū)域圖像對應(yīng)的url集,在顯示前判斷處理一下即可。

Adapter示例代碼:

public class LoaderAdapter extendsBaseAdapter{

? ? ? ? ? private static final String TAG = "LoaderAdapter";

? ? ? ? ? private boolean mBusy = false;?????????? //是否處于滑動中

? ? ? ? ? public void setFlagBusy(boolean busy) {

? ? ? ? ? this.mBusy = busy;

}

? ? ? ? ? private ImageLoader mImageLoader;

? ? ? ? ? private int mCount;

? ? ? ? ? private Context mContext;

? ? ? ? ? private String[] urlArrays;

? ? ? ? ? public LoaderAdapter(int count, Context context, String[]url) {

? ? ? ? ?this.mCount = count;

? ? ? ? ?this.mContext = context;

? ? ? ? ?urlArrays = url;

? ? ? ? ? mImageLoader = new ImageLoader(context);

}

? ? ? ? ? public ImageLoader getImageLoader(){

? ? ? ? ? return mImageLoader;

}

? ? ? ? ?@Override

? ? ? ? ?public int getCount() {

? ? ? ? ?return mCount;

}

? ? ? ?@Override

? ? ? ?public Object getItem(int position) {

? ? ? ?return position;

}

? ? ? ? @Override

? ? ? ? ?public long getItemId(int position) {

return position;

}

? ? ? ? @Override

? ? ? ? ?public View getView(int position, View convertView, ViewGroupparent) {

? ? ? ? ?ViewHolder viewHolder = null;

? ? ? ? ?if (convertView == null) {//加載新創(chuàng)建的view

? ? ? ? ?convertView = LayoutInflater.from(mContext).inflate(R.layout.list_item,null);

? ? ? ? ?viewHolder = new ViewHolder();

? ? ? ? ?viewHolder.mTextView = (TextView) convertView.findViewById(R.id.tv_tips);

? ? ? ? ? viewHolder.mImageView = (ImageView)convertView.findViewById(R.id.iv_image);

? ? ? ? ?convertView.setTag(viewHolder);

? ? ? ? ?} else {

? ? ? ? ?viewHolder= (ViewHolder) convertView.getTag();

}

? ? ? ? ?String url = "";

? ? ? ? ?url = urlArrays[position % urlArrays.length];

? ? ? ? ?viewHolder.mImageView.setImageResource(R.drawable.ic_launcher);

? ? ? ? ?if (!mBusy) {

? ? ? ? mImageLoader.DisplayImage(url, viewHolder.mImageView, false);

? ? ? ? ?viewHolder.mTextView.setText("--" + position + "--IDLE||TOUCH_SCROLL");

? ? ? ? } else {

? ? ? ? mImageLoader.DisplayImage(url, viewHolder.mImageView, true);

? ? ? ? viewHolder.mTextView.setText("--" + position +"--FLING");

}

? ? ? //復(fù)用歷史緩存view

? ? ? return convertView;

}

? ? ? ?static class ViewHolder {

? ? ? ? ? TextView mTextView;

? ? ? ? ?ImageView mImageView;

? ? ? }

}

3、內(nèi)存緩沖機(jī)制:

首先限制內(nèi)存圖片緩沖的堆內(nèi)存大小,每次有圖片往緩存里加時判斷是否超過限制大小,超過的話就從中取出最少使用的圖片并將其移除。

當(dāng)然這里如果不采用這種方式,換做軟引用也是可行的,二者目的皆是最大程度的利用已存在于內(nèi)存中的圖片緩存,避免重復(fù)制造垃圾增加GC負(fù)擔(dān);OOM溢出往往皆因內(nèi)存瞬時大量增加而垃圾回收不及時造成的。只不過二者區(qū)別在于LinkedHashMap里的圖片緩存在沒有移除出去之前是不會被GC回收的,而SoftReference里的圖片緩存在沒有其他引用保存時隨時都會被GC回收。所以在使用LinkedHashMap這種LRU算法緩存更有利于圖片的有效命中,當(dāng)然二者配合使用的話效果更佳,即從LinkedHashMap里移除出的緩存放到SoftReference里,這就是內(nèi)存的二級緩存。

本例采用的是LRU算法,先看看MemoryCache的實現(xiàn)


五、ListView的其他優(yōu)化:

1、盡量避免在BaseAdapter中使用static來定義全局靜態(tài)變量:

static是Java中的一個關(guān)鍵字,當(dāng)用它來修飾成員變量時,那么該變量就屬于該類,而不是該類的實例。所以用static修飾的變量,它的生命周期是很長的,如果用它來引用一些資源耗費過多的實例(比如Context的情況最多),這時就要盡量避免使用了。

2、盡量使用getApplicationContext:

如果為了滿足需求下必須使用Context的話:Context盡量使用Application Context,因為Application的Context的生命周期比較長,引用它不會出現(xiàn)內(nèi)存泄露的問題

3、盡量避免在ListView適配器中使用線程:

因為線程產(chǎn)生內(nèi)存泄露的主要原因在于線程生命周期的不可控制。之前使用的自定義ListView中適配數(shù)據(jù)時使用AsyncTask自行開啟線程的,這個比用Thread更危險,因為Thread只有在run函數(shù)不結(jié)束時才出現(xiàn)這種內(nèi)存泄露問題,然而AsyncTask內(nèi)部的實現(xiàn)機(jī)制是運(yùn)用了線程執(zhí)行池(ThreadPoolExcutor),這個類產(chǎn)生的Thread對象的生命周期是不確定的,是應(yīng)用程序無法控制的,因此如果AsyncTask作為Activity的內(nèi)部類,就更容易出現(xiàn)內(nèi)存泄露的問題。解決辦法如下:

①、將線程的內(nèi)部類,改為靜態(tài)內(nèi)部類。

②、在線程內(nèi)部采用弱引用保存Context引用

示例代碼:

public?abstract?class?WeakAsyncTask?extends?AsyncTask?{

? ? ? ? ? ? ? ? ? ? protected?WeakReference?mTarget;

? ? ? ? ? ? ? ? ? ? public?WeakAsyncTask(WeakTarget?target)?{

? ? ? ? ? ? ? ? ? ? mTarget?=?new?WeakReference(target);

}

? ? ? ? ? ? ? ? ? ?@Override

? ? ? ? ? ? ? ? ? ?protected?final?void?onPreExecute()?{

? ? ? ? ? ? ? ? ? ?final?WeakTarget?target?=?mTarget.get();

? ? ? ? ? ? ? ? ? if?(target?!=?null)?{

? ? ? ? ? ? ? ? ? this.onPreExecute(target);

}

}

? ? ? ? ? ? ? ? ? ? @Override

? ? ? ? ? ? ? ? ? ?protected?final?Result?doInBackground(Params...?params)?{

? ? ? ? ? ? ? ? ? ? final?WeakTarget?target?=?mTarget.get();

? ? ? ? ? ? ? ? ? ? if?(target?!=?null)?{

? ? ? ? ? ? ? ? ? ? return?this.doInBackground(target,?params);

? ? ? ? ? ? ? ? ? ?}?else?{

? ? ? ? ? ? ? ? ? return?null;

? ? ? ? ? ? ? ? ? }

}

? ? ? ? ? ? ? ? ? ?@Override

? ? ? ? ? ? ? ? ? ?protected?final?void?onPostExecute(Result?result)?{

? ? ? ? ? ? ? ? ? ?final?WeakTarget?target?=?mTarget.get();

? ? ? ? ? ? ? ? ? ?if?(target?!=?null)?{

? ? ? ? ? ? ? ? ? ?this.onPostExecute(target,?result);

}

}

? ? ? ? ? ? ? ? ?protected?void?onPreExecute(WeakTarget?target)?{

? ? ? ? ? ? ? ? ? ? ? ?//?No?default?action

}

? ? ? ? ? ? ? protected?abstract?Result?doInBackground(WeakTarget?target,?Params...?params);

? ? ? ? ? ? ? ? protected?void?onPostExecute(WeakTarget?target,?Result?result)?{

? ? ? ? ? ? ? ?/ /?No?default?action

}

}

六、ScrollView和ListView的沖突問題:【摘自網(wǎng)絡(luò)】

解決方法之一:

在ScrollView添加一個ListView會導(dǎo)致listview控件顯示不全,這是因為兩個控件的滾動事件沖突導(dǎo)致。所以需要通過listview中的item數(shù)量去計算listview的顯示高度,從而使其完整展示,如下提供一個方法供大家參考。

示例代碼:

public voidsetListViewHeightBasedOnChildren(ListView listView) {

ListAdapter listAdapter = listView.getAdapter();

if (listAdapter == null) {

return;

}

int totalHeight = 0;

for (int i = 0; i < listAdapter.getCount(); i++) {

View listItem = listAdapter.getView(i, null, listView);

listItem.measure(0, 0);

totalHeight += listItem.getMeasuredHeight();

}

ViewGroup.LayoutParams params = listView.getLayoutParams();

params.height = totalHeight + (listView.getDividerHeight() *(listAdapter.getCount() - 1));

params.height += 5;//if without this statement,the listview will bea little short

listView.setLayoutParams(params);

}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,606評論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,582評論 3 418
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,540評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,028評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,801評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,223評論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,294評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,442評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,976評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,800評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,996評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,543評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,233評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,926評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,702評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 47,991評論 2 374

推薦閱讀更多精彩內(nèi)容