2013-08-25 116 views
2

我有一個自定義的ListView,我顯示了一些從本地數據庫檢索到的武器。我總共有88行,每行調用一個文本和一個圖像,每次調用getView()ListView滯後,而滾動速度快,垃圾收集器瘋狂,每秒刪除一些1M對象。我不明白爲什麼。ListView性能很慢

在我發佈我的Adapter實現之前,有關如何設置圖像的一些說明。我的武器類只是一個數據的持有者和獲取者。這是多麼的名字和圖像的數據庫中被創建時,收到設定(是它似乎很奇怪,但所有其他解決方案的工作,甚至更慢):

private Weapon buildWeapon(Cursor cursor) { 
    Weapon w = new Weapon(); 
    w.setId(cursor.getLong(0)); 
    w.setName(cursor.getString(1)); 
    w.setImage(Constants.ALL_WEAPON_IMAGES[(int) cursor.getLong(0)-1]); 


    return w; 
} 

所以我有一個包含在所有武器圖像的Array形式爲R.drawable.somegun。數據結構的實現方式使ID-1始終指向我的Array中的正確可繪製引用。武器類中的圖像字段是Integer。現在你有一個想法,我getImage()方法是如何工作的,並在下面進入我的Adapter

public class Weapon_Adapter extends BaseAdapter { 
private List<Weapon> items; 
private LayoutInflater inflater = null; 
private WeaponHolder weaponHolder; 
private Weapon wp; 


static class WeaponHolder { 
    public TextView text; 
    public ImageView image; 
} 

// Context and all weapons of specified class are passed here 

public Weapon_Adapter(List<Weapon> items, Context c) { 
    this.items = (List<Weapon>) items; 
    inflater = LayoutInflater.from(c); 
    Log.d("Adapter:", "Adapter created"); 
} 

@Override 
public int getCount() { 
    return items.size(); 
} 

@Override 
public Weapon getItem(int position) { 
    return items.get(position); 
} 


@Override 
public long getItemId(int position) { 
    return position; 
} 

@Override 
public View getView(int position, View convertView, ViewGroup parent) { 

    wp = (Weapon) getItem(position); 

    if (convertView == null) { 
     convertView = inflater.inflate(R.layout.category_row, null); 
     weaponHolder = new WeaponHolder(); 
     weaponHolder.text = (TextView) convertView 
       .findViewById(R.id.tvCatText); 
     weaponHolder.image = (ImageView) convertView 
       .findViewById(R.id.imgCatImage); 
     convertView.setTag(weaponHolder); 
    } 

     weaponHolder = (WeaponHolder) convertView.getTag(); 


    weaponHolder.text.setText(wp.getName()); 
    weaponHolder.image.setImageResource(wp.getImage()); 
      // weaponHolder.image.setImageResource(R.drawable.ak74m); 




    return convertView; 

}} 

現在奇怪的事情:使用outcommented行靜態設置相同的圖像的所有項目刪除即使不是所有的滯後和GC叫一次!我沒有明白它.. wp.getImage()返回完全相同的東西,每個武器只有R.drawable.name是不同的。但是GC在滾動時刪除了大量的對象和時間滯後。任何想法我做錯了什麼?

UPDATE

我搬到設置圖像的AsyncTask現在滯後了:

public class AsyncImageSetter extends AsyncTask<Void, Void, Void> { 

private ImageView img; 
private int image_resId; 
private Bitmap bmp; 
private Context c; 

public AsyncImageSetter(Context c, ImageView img, int image_ResId, Bitmap bmp) { 

    this.img = img; 
    this.image_resId = image_ResId; 
    this.bmp = bmp; 
    this.c = c; 

} 

@Override 
protected Void doInBackground(Void... params) { 

    bmp = BitmapFactory.decodeResource(c.getResources(), image_resId); 

    return null; 
} 

@Override 
protected void onPostExecute(Void result) { 

    img.setImageBitmap(bmp); 
    bmp = null; 

    super.onPostExecute(result); 
} 

    } 

然而,滾動整個GC時仍稱像瘋了似的RAM消耗增加上下列出。現在的問題是:如何優化圖像回收以避免RAM使用量增加?

+0

使用延遲加載技術來加載圖像。檢查此鏈接http://stackoverflow.com/questions/16789676/caching-images-and-displaying/16978285#16978285可能也會幫助你,如果圖像很大嘗試縮小圖像 – Raghunandan

+0

我讀過關於它,它是適合從應用程序之外的地方加載一堆數據。我的圖片數量非常有限,全部來自可繪製文件夾。我試圖找出爲什麼outcommented行工作的很快,而我的getImage()方法幾乎相同會導致List滯後。圖像不是很大,可以通過ImageView的ScaleType參數來縮放 – Droidman

+0

有多大每個圖像? – m0skit0

回答

1

如果您修復了低fps問題,並且快速滾動效果到目前爲止您確實很好。

當頻繁的GC行動僅僅是一個整容問題,而您不面臨OutOfMemoryException或其他任何缺點時,那麼您應該可以這樣做。如果這不是您的選擇,您還可以執行另一項操作:除了下采樣和緩存之外,在啓動AsyncTask之後以及實際檢索資源文​​件之前,還可以添加一個小的人工等待時間(50-150ms)。然後,您在您的任務中添加一個取消標誌,必須在人工延遲後進行檢查。如果它設置爲true,則不請求資源文件。

一些(不執行)的代碼示例:

class MyImageLoader extends AsyncTask { 
    private boolean cancel = false 

    private Bitmap bitmap; 

    public void cancel() { cancel = true } 

    public void doInBackground() { 
     sleep(100); 
     if(!cancel) { 
      bitmap = BitmapFactory.decodeResource(...); 
     } 
    } 
} 

class Adapter { 

    static class WeaponHolder { 
     public TextView text; 
     public ImageView image; 
     public MyImageLoader loader; 
    } 

    public View getView(int position, View convertView, ViewGroup parent) { 
     WeaponHolder holder; 

     if (convertView == null) { 
      ... 
      holder = new WeaponHolder(); 
     } else { 
      holder = convertView.getTag(); 
      holder.loader.cancel(); // Cancel currently active loading process 
     } 

     holder.loader = new MyImageLoader(); 
     holder.loader.execute(); 

     return convertView; 
    } 
} 

這樣大部分的圖像將不會從你的內存,如果用戶滾動真正快,你可以節省大量的內存讀取。

+0

以及我的目標是儘可能減少內存使用量。 – Droidman

+0

我有一個應用程序,嚴重依賴大型圖像的無盡滾動列表。當我添加上面解釋的延遲方法時,事情變得更加順利,低端設備的內存問題也更少。如果延遲足夠低,用戶將不會感到額外的等待時間。你應該試試看。 – Taig

+0

是啊謝謝,我改變了代碼來匹配你的例子(不過延遲了),它在我的SGS2 I9100上運行得非常順利。我的最低SDK是14,所以我(理論上)不會面對真正的低性能設備。事情是,這不是一個簡單的ListView在我的情況。我在ViewPager中有6個ListFragments,每個ListFragments都被填充上面我的代碼中顯示的數據。所以我關心每個MB的RAM。在正常的佈局文件夾中,我的ImageView是150x50dp,但原始位圖較大。將它們縮小到我的AsyncTask中是否有意義? – Droidman

2

因爲將所有位圖加載到Android的內存通常是不切實際的,所以您應該假設您會不時地得到GC。

然而,你可以做什麼考慮下一個提示:

  1. 縮減的位圖,你需要向他們展示的大小。您可以使用google's waymy way

  2. 檢查您放置圖像文件的文件夾。很多人把它們放在res/drawable文件夾中,不明白爲什麼它們會比原來的尺寸大得多(這是因爲密度 - 這是mdpi,而設備可能是xhdpi或xxhdpi)。例如,如果圖像位於可繪製文件夾中,並且您在xhdpi設備(如galaxy S3)上運行它,則需要(WIDTH * 2)*(HEIGHT * 2)* 4個字節。如果圖像爲200x200,則其位圖對象至少需要400 * 400 * 4 = 640,000個字節。在xxhdpi設備上會更糟糕,比如銀河系s4和htc系統。

  3. 考慮使用內存高速緩存,像LruCache

  4. 如果位圖不具有透明度好,你看不到任何質量差異,可以考慮使用的RGB_565 config而不是默認的。這將需要每個像素2個字節,而不是每個像素4個字節。

  5. 如果您可以負責足夠,您可以使用JNI進行緩存。我爲此任務做了一個小代碼,here。請閱讀我在那裏寫的所有筆記。

順便說一句,我已經注意到你使用的圖像標識符數組。如果圖像的名稱中包含一些邏輯(例如:img1,img2,...),則可以使用getResources()。getIdentifier(...)來代替。

+0

我想緩存圖像的選項會爲我做,你知道任何好的示例/教程/示例緩存圖像和在ListView中使用它們嗎?我感覺有點失落,因爲我從來沒有與緩存數據密切合作.. – Droidman

+0

當然,其他的點都沒有幫助你?無論如何,這是一個關於緩存位圖的鏈接:http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html。請注意,LruCache需要API 12及更高版本,但它可作爲兼容性庫在某處:http://developer.android.com/reference/android/support/v4/util/LruCache.html。 –