列表视图内存泄漏 [英] Listview memory leak
问题描述
我有一个简单的列表视图适配器。 我动态创建10+ listviewitems。然后,我上下滚动一遍又一遍又一遍.... 我可以看到,可用内存不断下降......
我在哪里需要释放什么? 注意 - 有一个ImageView的 - 但在我的测试中,我没有使用任何图像,以便它View.GONE
另外 - 与工具,我可以分析在Android的内存使用情况。我发现yourKit,但我怎么配置为Android(我在设备上运行应用程序)/
Activity类
包org.BJ.Food4All.Activities.NewRecipe;
进口org.BJ.Food4All.R;
进口org.BJ.Food4All.Recipe;
进口org.BJ.Food4All.Recipe.Instruction;
进口org.BJ.Food4All.Activities.RecipeBook.RecipeInstructionsListViewAdapter;
进口org.BJ.Food4All.Activities.RecipeBook.SharedData;
进口org.BJ.Food4All.utils.CameraUtil;
进口org.BJ.Food4All.utils.ImageUploadItem;
进口android.app.ListActivity;
进口android.content.Intent;
进口android.graphics.Bitmap;
进口android.os.Bundle;
进口android.util.Log;
进口android.view.ContextMenu;
进口android.view.MenuInflater;
进口android.view.MenuItem;
进口android.view.View;
进口android.view.ContextMenu.ContextMenuInfo;
进口android.view.View.OnClickListener;
进口android.widget.AdapterView;
进口android.widget.EditText;
公共类指令扩展ListActivity实现OnClickListener
{
私人最终静态字符串MTAG =说明;
私人的EditText mInstructionEditText = NULL;
私人RecipeInstructionsListViewAdapter mListViewAdapter = NULL;
私人配方mEditRecipe = PrivateResources.GetRecipe();
私人CameraUtil mCameraUtil =新CameraUtil(本);
私人诠释mSelectedEntryIndex = -1;
@覆盖
保护无效的onCreate(包savedInstanceState)
{
super.onCreate(savedInstanceState);
的setContentView(R.layout.new_recipe_instruction_tab);
mInstructionEditText =(EditText上)findViewById(R.id.newRecipeInstructionEditTextId);
查看addInstructionButton = findViewById(R.id.naddInstructionButtonId);
// 完整性检查
如果(mInstructionEditText == NULL || addInstructionButton == NULL)
{
Log.e(MTAG,空指针);
//安全退出
完();
}
//设置点击监听所有的按钮
addInstructionButton.setOnClickListener(本);
mListViewAdapter =新RecipeInstructionsListViewAdapter(这一点,R.layout.recipes_instruction_list_single_view_entry,mEditRecipe.GetInstructions());
setListAdapter(mListViewAdapter);
registerForContextMenu(getListView());
}
公共无效的onClick(视图v)
{
开关(v.getId())
{
案例R.id.naddInstructionButtonId:
AddInstructionToRecipe(五);
打破;
默认:
Log.e(MTAG,无效的编号:+ v.getId());
//安全退出
完();
}
}
私人无效AddInstructionToRecipe(视图v)
{
字符串instructionText = mInstructionEditText.getText()的toString()。
如果(instructionText == NULL)
{
返回;
}
指令newInstruction =新指令(mEditRecipe.GetInstructions()。大小()+ 1,//指数
instructionText,//指令
空值,
真正);
如果(mEditRecipe.AddInstruction(newInstruction)!=真)
{
// TODO - 错误
}
其他
{
mListViewAdapter.notifyDataSetChanged();
}
}
/ *
*(非Javadoc中)
* @see android.app.Activity#onCreateContextMenu(android.view.ContextMenu,android.view.View,android.view.ContextMenu.ContextMenuInfo)
* /
@覆盖
公共无效onCreateContextMenu(文本菜单菜单,
视图V,
ContextMenuInfo menuInfo)
{
MenuInflater充气= getMenuInflater();
inflater.inflate(R.menu.instructions_ctx_menu,菜单);
super.onCreateContextMenu(菜单,V,menuInfo);
}
/ *
*(非Javadoc中)
* @see android.app.Activity#onContextItemSelected(android.view.MenuItem)
* /
@覆盖
公共布尔onContextItemSelected(菜单项项)
{
super.onContextItemSelected(项目);
AdapterView.AdapterContextMenuInfo menuInfo;
menuInfo =(AdapterView.AdapterContextMenuInfo)item.getMenuInfo();
mSelectedEntryIndex = menuInfo.position;
开关(item.getItemId())
{
案例R.id.deleteId:
mEditRecipe.RemoveInstruction(mSelectedEntryIndex);
mListViewAdapter.notifyDataSetChanged();
返回true;
案例R.id.takePictureId:
mCameraUtil.TakePicture();
返回true;
}
返回false;
}
/ *
*(非Javadoc中)
* @see android.app.Activity#onActivityResult(INT,INT,android.content.Intent)
* /
@覆盖
保护无效onActivityResult(INT申请code,
INT结果code,
意图数据)
{
//字符串imageLocation = mCameraUtil.onActivityResult(要求code,因此code,数据);
位图imageBitmap = mCameraUtil.onActivityResult(要求code,因此code,数据);
// TODO - 开关的意图传递的参数!!!!像TakePicture(指数);
// mEditRecipe.GetInstructions()得到(mSelectedEntryIndex).SetBitmap(imageBitmap)。 // SetInstructionImageLocation(imageLocation);
mSelectedEntryIndex = -1;
//更新与图片中的ListViewItem
mListViewAdapter.notifyDataSetChanged();
}
}
适配器类
包org.BJ.Food4All.Activities.RecipeBook;
进口的java.util.ArrayList;
进口org.BJ.Food4All.R;
进口org.BJ.Food4All.Recipe.Instruction;
进口org.BJ.Food4All.utils.GlobalDefs;
进口android.content.Context;
进口android.graphics.Bitmap;
进口android.graphics.Color;
进口android.graphics.Typeface;
进口android.net.Uri;
进口android.view.LayoutInflater;
进口android.view.View;
进口android.view.ViewGroup;
进口android.view.ViewGroup.LayoutParams;
进口android.widget.ArrayAdapter;
进口android.widget.ImageView;
进口android.widget.ListView;
进口android.widget.TextView;
公共类RecipeInstructionsListViewAdapter扩展ArrayAdapter<结构>
{
私人语境mContext;
私人的ArrayList<结构> mItems;
私人LayoutInflater mInflater;
公共RecipeInstructionsListViewAdapter(上下文的背景下,INT textViewResourceId,ArrayList的<结构>项目)
{
超(背景下,textViewResourceId,项目);
mContext =背景;
mItems =项目;
mInflater =(LayoutInflater)mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
@覆盖
公共查看getView(INT位置,
查看convertView,
父母的ViewGroup)
{
ViewHolder持有人=新ViewHolder();
如果(convertView == NULL)
{
convertView = mInflater.inflate(R.layout.recipes_instruction_list_single_view_entry,NULL);
}
如果(super.getItem(位置)!= 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);
字体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(位置).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(位置).getText());
位图imageBitmap = mItems.get(位置).GetBitmap();
//字符串imageLocation = mItems.get(位置).GetInstructionImageLocation();
如果(imageBitmap!= NULL)
{
holder.instructionImage.setImageBitmap(imageBitmap); // setImageURI(Uri.parse(imageLocation));
holder.instructionImage.setVisibility(View.VISIBLE);
}
其他
{
holder.instructionImage.setVisibility(View.GONE);
}
convertView.setTag(保持器);
convertView.setLayoutParams(新ListView.LayoutParams(LayoutParams.FILL_PARENT,LayoutParams.WRAP_CONTENT));
}
其他
{
}
返回convertView;
}
@覆盖
公共布尔的IsEnabled(INT位置)
{
返回true;
}
静态类ViewHolder
{
TextView的instructionIndex;
TextView的instructionText;
ImageView的instructionImage;
}
}
我不知道这是否是正确的分类这是一个错误,但每次使用Typeface.createFromAsset如果创建了一个新的字体资产,不松开。请参见这。
你可以做的就是加载字样,当你加载的应用程序,并引用它们静态。我把我的应用字样。
公共类YourApp扩展android.app.Application {
公共无效的onCreate(){
super.onCreate();
//字样缓存
initializeTypefaces();
}
公共静态类字体{
公共静态字样定理;
}
私人无效initializeTypefaces(){
Fonts.THEOREM = Typeface.createFromAsset(getAssets(),字体/ theorem.otf);
}
}
然后,我做我的适配器:
textView.setTypeface(YourApp.Fonts.THEOREM);
您可以去<一href="http://trace.adityalesmana.com/2010/08/declare-global-variable-in-android-via-android-app-application/">here来看看如何在Android中使用的应用程序。
最后,它看起来像你仍然创造只有当convertView是空的ViewHolder每次来代替。我会检讨这个视频来了解全貌。 <一href="http://www.google.com/events/io/2010/sessions/world-of-listview-android.html">http://www.google.com/events/io/2010/sessions/world-of-listview-android.html
下面是我如何使用ViewHolder方法的一个例子:
@覆盖
公共查看getView(INT POS,查看convertView,ViewGroup中父){
ViewHolder持有人;
如果(convertView == NULL || convertView.getTag()== NULL){
convertView = inflater.inflate(R.layout.list_item,父母,假);
持有人=新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); //回收时只会发生一次!
convertView.setTag(保持器);
}其他{
支架=(ViewHolder)convertView.getTag();
}
holder.text1.setText(someText);
holder.text2.setText(someText);
返回convertView;
}
I have a simple list view with adapter. I create 10+ listviewitems dynamically. Then I scroll up and down again and again and again.... I can see that available memory keeps going down...
Where do I need to free and what? Note - there is an imageview - but in my test I have not used any images so it is View.GONE.
Also - with which tool can I profile the memory usage on the android. I have found yourKit,but how do I configure it for the android (I run the application on the device)/
The Activity class
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();
}
}
The adapter class
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;
}
}
I'm not sure if it is correct to classify this as a bug, but every time you use Typeface.createFromAsset if creates a new font asset and does not release it. See this.
What you can do is load the typefaces when you load your app and reference them statically. I put my typefaces in Application.
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");
}
}
And then I do this in my adapter:
textView.setTypeface(YourApp.Fonts.THEOREM);
You can go here to see how to use Application in Android.
Lastly, it looks like your still creating your ViewHolder every time instead of only when convertView is null. I would review this video to get the whole picture. http://www.google.com/events/io/2010/sessions/world-of-listview-android.html
Here is an example of how I use the ViewHolder method:
@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;
}
这篇关于列表视图内存泄漏的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!