【问题标题】:Listview memory leak列表视图内存泄漏
【发布时间】:2011-12-24 19:32:39
【问题描述】:

我有一个带有适配器的简单列表视图。 我动态创建了 10 多个 listviewitems。然后我一次又一次地上下滚动...... 我可以看到可用内存不断下降...

我需要释放什么? 注意 - 有一个 imageview - 但在我的测试中我没有使用任何图像,所以它是 View.GONE。

另外 - 我可以使用哪个工具来分析 android 上的内存使用情况。我找到了 yourKit,但是如何为 android 配置它(我在设备上运行应用程序)/

活动类

package org.BJ.Food4All.Activities.NewRecipe;


import org.BJ.Food4All.R;
import org.BJ.Food4All.Recipe;
import org.BJ.Food4All.Recipe.Instruction;
import org.BJ.Food4All.Activities.RecipeBook.RecipeInstructionsListViewAdapter;
import org.BJ.Food4All.Activities.RecipeBook.SharedData;
import org.BJ.Food4All.utils.CameraUtil;
import org.BJ.Food4All.utils.ImageUploadItem;

import android.app.ListActivity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.util.Log;
import android.view.ContextMenu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.View.OnClickListener;
import android.widget.AdapterView;
import android.widget.EditText;

public class Instructions extends ListActivity implements OnClickListener
{
    private final static String mTAG = "Instructions";
    private EditText mInstructionEditText = null;
    private RecipeInstructionsListViewAdapter mListViewAdapter = null;
    private Recipe mEditRecipe = PrivateResources.GetRecipe();

    private CameraUtil  mCameraUtil = new CameraUtil(this);

    private int mSelectedEntryIndex = -1;

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.new_recipe_instruction_tab);

        mInstructionEditText = (EditText)findViewById(R.id.newRecipeInstructionEditTextId);
        View addInstructionButton = findViewById(R.id.naddInstructionButtonId);

        // Sanity check
        if(mInstructionEditText == null || addInstructionButton == null)
        {
            Log.e(mTAG, "NULL pointers");
            // secure exit
            finish();
        }

        // Set up click listeners for all the buttons
        addInstructionButton.setOnClickListener(this);

        mListViewAdapter = new RecipeInstructionsListViewAdapter(this, R.layout.recipes_instruction_list_single_view_entry, mEditRecipe.GetInstructions());

        setListAdapter(mListViewAdapter);

        registerForContextMenu(getListView());
    }

    public void onClick(View v)
    {
        switch(v.getId())
        {
            case R.id.naddInstructionButtonId:
                AddInstructionToRecipe(v);
                break;

            default:
                Log.e(mTAG, "Invalid ID:" + v.getId());
                // secure exit
                finish();

        }
    }

    private void AddInstructionToRecipe(View v)
    {
        String instructionText = mInstructionEditText.getText().toString();

        if(instructionText == null)
        {
            return;
        }

        Instruction newInstruction = new Instruction(   mEditRecipe.GetInstructions().size() + 1,   // Index
                                                        instructionText,                            // The instruction
                                                        null,
                                                        true);

        if( mEditRecipe.AddInstruction(newInstruction) != true)
        {
            // TODO - ERROR
        }
        else
        {
            mListViewAdapter.notifyDataSetChanged();
        }
    }

    /*
     * (non-Javadoc)
     * @see android.app.Activity#onCreateContextMenu(android.view.ContextMenu, android.view.View, android.view.ContextMenu.ContextMenuInfo)
     */
    @Override
    public void onCreateContextMenu(ContextMenu menu, 
                                    View v,
                                    ContextMenuInfo menuInfo) 
    {
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.instructions_ctx_menu, menu);

        super.onCreateContextMenu(menu, v, menuInfo);
    }

    /*
     * (non-Javadoc)
     * @see android.app.Activity#onContextItemSelected(android.view.MenuItem)
     */
    @Override
    public boolean onContextItemSelected(MenuItem item) 
    {
        super.onContextItemSelected(item);

        AdapterView.AdapterContextMenuInfo menuInfo;
        menuInfo = (AdapterView.AdapterContextMenuInfo)item.getMenuInfo();
        mSelectedEntryIndex = menuInfo.position;

        switch(item.getItemId()) 
        {
            case R.id.deleteId:
                mEditRecipe.RemoveInstruction(mSelectedEntryIndex);
                mListViewAdapter.notifyDataSetChanged();
                return true;

            case R.id.takePictureId:
                mCameraUtil.TakePicture();
                return true;
        }

        return false;
    }

    /*
     * (non-Javadoc)
     * @see android.app.Activity#onActivityResult(int, int, android.content.Intent)
     */
    @Override
    protected void onActivityResult(int requestCode, 
                                    int resultCode, 
                                    Intent data) 
    {
//      String imageLocation = mCameraUtil.onActivityResult(requestCode, resultCode, data);
        Bitmap imageBitmap = mCameraUtil.onActivityResult(requestCode, resultCode, data);
        // TODO - switch to parameter passed in the intent!!!! like TakePicture(index);
//      mEditRecipe.GetInstructions().get( mSelectedEntryIndex ).SetBitmap( imageBitmap ); //SetInstructionImageLocation(imageLocation);
        mSelectedEntryIndex = -1;

        // Update the listviewitem with the picture
        mListViewAdapter.notifyDataSetChanged();
    }
}

适配器类

package org.BJ.Food4All.Activities.RecipeBook;

import java.util.ArrayList;

import org.BJ.Food4All.R;
import org.BJ.Food4All.Recipe.Instruction;
import org.BJ.Food4All.utils.GlobalDefs;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.Typeface;
import android.net.Uri;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;

public class RecipeInstructionsListViewAdapter extends ArrayAdapter<Instruction> 
{
    private Context mContext;
    private ArrayList<Instruction> mItems;
    private LayoutInflater mInflater;

    public RecipeInstructionsListViewAdapter(Context context, int textViewResourceId, ArrayList<Instruction>items) 
    {
        super(context, textViewResourceId, items);

        mContext = context;
        mItems  = items;

        mInflater = (LayoutInflater)mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    }

    @Override
    public View getView(int position, 
                        View convertView, 
                        ViewGroup parent) 
    {
          ViewHolder holder = new ViewHolder();

          if (convertView == null) 
          {
              convertView = mInflater.inflate(R.layout.recipes_instruction_list_single_view_entry, null);
          }

          if(super.getItem(position) != null)
          {
              holder.instructionIndex = (TextView) convertView.findViewById( R.id.listUp_RecipeInstructionNumberTextBoxId);
              holder.instructionText = (TextView) convertView.findViewById( R.id.listUp_RecipeInstructionTextTextBoxId);
              holder.instructionImage = (ImageView)convertView.findViewById( R.id.listUp_RecipeInstructionImageViewId);

              Typeface tf = Typeface.createFromAsset(mContext.getAssets(), "Eras_Bold.ttf");
              holder.instructionIndex.setTypeface(tf);
              holder.instructionIndex.setTextSize(30);
              holder.instructionIndex.setTextColor(GlobalDefs.GetHeadlineColor());
              holder.instructionIndex.setText( Integer.toString(mItems.get(position).getIndex()));

              tf = Typeface.createFromAsset(mContext.getAssets(), "Arial.ttf");
              holder.instructionText.setTypeface(tf);
              holder.instructionText.setTextSize(14);
              holder.instructionText.setTextColor(Color.BLACK);
              holder.instructionText.setText(mItems.get(position).getText());

              Bitmap imageBitmap = mItems.get(position).GetBitmap();
//              String imageLocation = mItems.get(position).GetInstructionImageLocation();
              if(imageBitmap != null)
              {
                  holder.instructionImage.setImageBitmap(imageBitmap);// setImageURI( Uri.parse(imageLocation ));
                  holder.instructionImage.setVisibility(View.VISIBLE);
              }
              else
              {
                  holder.instructionImage.setVisibility(View.GONE);
              }

              convertView.setTag(holder);
              convertView.setLayoutParams(new ListView.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT));
          } 
          else 
          {
          }

          return convertView;
    }

    @Override
    public boolean isEnabled(int position) 
    {
        return true;
    }

    static class ViewHolder 
    {
          TextView  instructionIndex;
          TextView  instructionText;
          ImageView instructionImage;
    }
}

【问题讨论】:

    标签: java android listview memory memory-leaks


    【解决方案1】:

    我不确定将其归类为错误是否正确,但每次使用 Typeface.createFromAsset 时,如果创建新字体资源并且不释放它。见this

    您可以做的是在加载应用时加载字体并静态引用它们。我把我的字体放在应用程序中。

    public class YourApp extends android.app.Application {
        public void onCreate() {
            super.onCreate();
    
            // typeface caching
            initializeTypefaces();
        }
    
        public static class Fonts {
            public static Typeface THEOREM;
        }
    
        private void initializeTypefaces(){
            Fonts.THEOREM   = Typeface.createFromAsset(getAssets(), "fonts/theorem.otf");
        }
    }
    

    然后我在我的适配器中执行此操作:

    textView.setTypeface(YourApp.Fonts.THEOREM);
    

    你可以去here看看如何在Android中使用Application。

    最后,看起来您仍然每次都在创建 ViewHolder,而不是仅在 convertView 为空时创建。我会查看此视频以了解整个情况。 http://www.google.com/events/io/2010/sessions/world-of-listview-android.html

    这是我如何使用 ViewHolder 方法的示例:

    @Override
    public View getView(int pos, View convertView, ViewGroup parent) {
        ViewHolder holder;
    
        if(convertView == null || convertView.getTag() == null){
            convertView = inflater.inflate(R.layout.list_item, parent, false);
            holder = new ViewHolder();
    
            holder.text1  = (TextView)convertView.findViewById(R.id.list_item_text1);
            holder.text2  = (TextView)convertView.findViewById(R.id.list_item_text2);
            holder.text1.setTypeface(YourApp.Fonts.THEOREM); // only happens once when recycling!
    
            convertView.setTag(holder);
        }else{
            holder = (ViewHolder) convertView.getTag();
        }
    
        holder.text1.setText("someText");
        holder.text2.setText("someText");
        return convertView;
    }
    

    【讨论】:

    • 真的很好的帮助 - 也有视频。不知道它存在...非常感谢!!!
    • 我还有一个问题,如果我的适配器根据位置膨胀不同的布局 - 第一个和最后一个有特殊布局(圆边背景图片)。我目前在 getView 函数中使用以下 getItem(position) 并根据它膨胀不同的布局。每次都不充气怎么办???
    • 我不是 100% 确定,但我会尽最大努力扩大相同的布局并更改背景图像以获得圆角效果。如果这只是页眉和页脚,你可以看看 addHeader 和 addFooter 函数:developer.android.com/reference/android/widget/…, java.lang.Object, boolean)
    • 指出了一个bug(现在应该在Android 4.0中修复)并提到了convertView的概念。这篇文章肯定会赢得一些支持!
    【解决方案2】:

    您可以尝试释放资源if convertView != null。如果是,您可以找到您的Views and null 他们。您也可以尝试获取ImageView 的位图并回收。 您还可以将您的 ViewHolder 作为成员添加到您的 RecipeInstructionsListViewAdapter 并在构造函数中实例化一次。

    【讨论】:

    • 关于位图问题 - 如果我有 X 个 listviewitems,并且屏幕上只显示 X 中的 N 个 - 另一个是否会通过其位图自动回收?通常,当应用程序有一个带有图像的列表视图并从服务器查询更多结果时,所有已经 D/L 的数据都被保留而不被回收。关于这个的任何想法
    • 如果您的问题正确,我不放心:如果您只显示 X 个项目中的 N 个,我假设您没有将位图保存在 X 中的 M 中而不是 N 中。因此,在 M 中没有需要清除的资源。回收不会自动发生。如果显示此项目,ListView 会为您提供 convertView 作为参数。如果 convertView != null 它保存最后一个项目的视图,该项目滚动到屏幕范围之外。在你调用 findViewById 来填充你的持有人后,你可以尝试调用 ´((BitmapDrawable)holder.instructionImage.getDrawable()).getBitmap().recycle()`
    • 谢谢...会检查一下
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-03-01
    • 1970-01-01
    • 2019-03-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多