This is the error I am receiving on the Android log, exactly, this is:
08-06 12:16:28.763: E/dalvikvm-heap(27065): Out of memory on a 184-byte allocation.
The "184" depends, sometimes it is 184, sometimes it is 24, other 42......etc....
I was looking everywhere, and this error is common for Activities where loading pictures, my problem is that I am not loading picture, but only text.
My Activity is a ListActivity, where I load data from a DataBase(only text), and after a while, all the time the same error.
Anyone knows how to solve it??
Thanks a lot!
Since you are using a listActivity check if you have implemented the optimizations shown here.
I solved a similar issue with list view by implementing the optimization
Here are some excerpts from the presentation about optimizing a listAdapter
The Slow way
public View getView(int position, View convertView, ViewGroup parent) {
View item = mInflater.inflate(R.layout.list_item_icon_text, null);
((TextView) item.findViewById(R.id.text)).setText(DATA[position]);
((ImageView) item.findViewById(R.id.icon)).setImageBitmap(
(position & 1) == 1 ? mIcon1 : mIcon2);
return item;
}
The Proper way
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = mInflater.inflate(R.layout.item, parent, false);
}
((TextView) convertView.findViewById(R.id.text)).setText(DATA[position]);
((ImageView) convertView.findViewById(R.id.icon)).setImageBitmap(
(position & 1) == 1 ? mIcon1 : mIcon2);
return convertView;
}
The Best Way
static class ViewHolder {
TextView text;
ImageView icon;
}
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.list_item_icon_text,
parent, false);
holder = new ViewHolder();
holder.text = (TextView) convertView.findViewById(R.id.text);
holder.icon = (ImageView) convertView.findViewById(R.id.icon);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.text.setText(DATA[position]);
holder.icon.setImageBitmap((position & 1) == 1 ? mIcon1 : mIcon2);
return convertView;
}
Sometimes you can get OutOfMemory error, when you try to execute too long SQL statements (eg, you don't use the String[] argument for the variables in the query, but hard code them in the statement).
Try editing your where statements to field=? format, and specify the variables in the designated query parameter.
See this thread: Sqlite Out of Memory when preparing update statement
If this isn't the problem in your case, than I can't think of anything else with this little information.
In my case, the problem caused by big-size images. I removed some of them (temporarily for test) and the problem solved. Note that in my case, the problem just was in lowest API-level I was trying the app (API 16).
Finally, optimized them for solving the problem permanently.
Related
I have an Adapter with ViewHolder. Let's say I want to set data only once to the ViewHoler inside the getView() method, what's actually happened is, its set the data every time I'm scrolling the view.
How can I check if the ViewHolder was already init or not?
public View getView(final int position, View convertView, ViewGroup parent) {
final ViewHolder holder;
if (convertView == null) {
holder = new ViewHolder();
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
Deal deal = dealsArr.get(position);
holder.textView.setText(deal.getPrice);
// Here is my issue. how can I do it only once?
changePrice(deal, true);
}
I asssume you already have the basic understanding of Android Adapters & working of getCount() & getView() if not see this
Either adapter returns a null View or an Inflated View; findViewbyId(R.id.xmlID) is always executed;
ViewHolder is used to avoid frequent call and ensure the smooth scrolling of your listview.
Excellent Explanation here
First spend sometime of understand Listview's recycling mechanism!
The link I had shared also has a link to that.!
public View getView(final int position, View convertView, ViewGroup parent) {
//rest of the code goes here
}
focus on the parameters of this method,
the convertViewwhich is for recycling will be null initially because there is no recycling done yet
so this part of the code will be executed;
if (convertView == null) { //convertView is null when recycling isn't done
holder = new ViewHolder();
convertView.setTag(holder);
}
so we initialize the view-holder & refer all our Views(Buttons, TextViews etc) using findview by id.
the next time getView() is called; it executes this part of the code;
else {
holder = (ViewHolder) convertView.getTag();
}
I'd strongly recommend to first understand working of Android Adapters & List View Recyling Mechanism without using ViewHolders in your code. first you understand how & when the methods are invoked then use ViewHolder for more optimization
I'm working on making a custom ArrayAdapter so that my list has the first element in a different color.
The thing is, when I execute this code in the get view method:
#Override
public View getView(int position, View convertView, ViewGroup parent){
Club club = (Club)getItem(position);
if(convertView == null){
convertView = LayoutInflater.from(getContext()).inflate(R.layout.list_item, parent, false);
}
TextView textView = (TextView)convertView.findViewById(R.id.txtListItem);
if(position == 0 && club.getName().contains("All")){
textView.setTextColor(ContextCompat.getColor(getContext(), R.color.orange));
}
textView.setText(club.getName());
return convertView;
}
The first item is orange, yes, but the 10th one (below the screen) is also when I scroll down :( in another list with more elements than what the screen can hold, I have several that are orange.. I don't understand why, please help!
This is happening because views are recycling/reused. You are setting orange color for the first item but not setting default color for the rest. Just add an else clause to your if statement above, something like this
if(position == 0 && club.getName().contains("All")){
Log.d(ClubAdapter.class.getName(), club.getName());
textView.setTextColor(ContextCompat.getColor(getContext(), R.color.orange));
}else{
textView.setTextColor(ContextCompat.getColor(getContext(), R.color.your_default_color));
}
Most likely this happens because Android is recycling the view which was originally used for the first row. You need an else clause to set the color back to normal.
This happens because the adapter tries to reuse as many views as possible for performance improvement. That's why you should not create a view in getView, but reuse them (only create if it is null):
if(convertView == null){
convertView = LayoutInflater.from(getContext()).inflate(R.layout.list_item, parent, false);
}
Even though you shouldn't create a new view, you have to make sure that you set the parameters that you want for each getView call (also called "binding"), so:
if(position == 0){
//set your first view color and whatever
}else{
//set your common view colors..
}
Because listview recycle views Google suggests to use the ViewHolder pattern. You should also set a default color if the cell is different. It would look something like this.
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder;
if(convertView == null){
convertView = LayoutInflater.from(context).inflate(R.layout.list_item, parent, false);
viewHolder = new ViewHolder();
viewHolder.textView = (TextView) convertView.findViewById(R.id.txtListItem);
convertView.setTag(viewHolder);
}
else {
viewHolder = (ViewHolder) convertView.getTag();
}
if(position == 0 && club.getName().contains("All")){
viewHolder.textView.setTextColor(ContextCompat.getColor(getContext(), R.color.orange));
}else{
//Set default color
}
viewHolder.textView.setText(club.getName());
return convertView;
}
static class ViewHolder {
protected TextView textView;
}
I want to develop an application like the facebook application does. (**(i.e.,)**Loading images,Texts,Videos on the list item with infinite list rows). Here i am showing the sampled image(inSamplesize=4) on the listview item to reduce the usage of heap size .
This time, the heap size is keep on increasing while scrolling the listview. I am suggesting that this is because of image object in the list item is not been recycled. If the heap size is keep increasing, The chance of OOM(Out Of Memory) exception may occurs. How to achieve this concept and ensure the chance of OOM exception is less?.
Make sure you use view holder pattern in your listview's adapter :
http://www.javacodegeeks.com/2013/09/android-viewholder-pattern-example.html
Or do some googling, basically all apps should follow this pattern.
Reusing the convertView in getView() of listadapter should solve OOM exception.
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = mInflater.inflate(R.layout.yourlayout, null);
}
ImageView iv = (ImageView) convertView.findViewById(R.id.image);
iv.setImageResource(R.drawable.image);
return convertView;
}
Using a ViewHolder with above will make scrolling fast
static class ViewHolder {
private ImageView imageView;
}
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.yourlayout, null);
holder = new ViewHolder();
holder.imageView = (ImageView) convertView.findViewById(R.id.image);
convertView.setTag(holder);
}
else {
holder = (ViewHolder) convertView.getTag();
}
holder.ImageView.setImageResource(R.drawable.image);
return convertView;
}
Don't use very large size images, if u are displaying the images in list for thumbs only resize them according to your UI. Android apps only gets less then 20mb for its execution, So using large images exceeds the memory available.
and add android:largeHeap="true" in application tag of your manifest.
Is there any faster alternative to Gallery widget in Android, because Gallery is so slow and lagging, i tryed to use view holder to reuse views, but without result. i used classic method
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.image, null);
// Creates a ViewHolder and store references to the two children views
// we want to bind data to.
holder = new ViewHolder();
holder.icon = (ImageView) convertView.findViewById(R.id.ImageView01);
convertView.setTag(holder);
} else {
// Get the ViewHolder back to get fast access to the TextView
// and the ImageView.
holder = (ViewHolder) convertView.getTag();
}
holder.icon.setImageDrawable(getPicture(items[position]));
return convertView;
}
This is a couple of various galleries created by other and uploaded to GitHub. You might need to checkout them to see how they are and how fast they are.
I have a question, maybe a silly one, but I think it is important.
Why the parameter: convertView (View) on the
public View getView(int position, View convertView, ViewGroup parent)
is always null? android is supposed to recycle the views once they're created the first time, isn't it? or how can I do to recycle those views?
I feel like the method receives those 3 parameters, but in none of the google examples they use either of them.
Unfortunately, convertView will always be null, due to Android bug 3376. Gallery does not implement View recycling (at least as of Gingerbread/2.3.4).
A commenter in the bug suggests forking Gallery.java (from AOSP) and implementing it yourself, which may be the best option.
The convertView parameter indeed will be null a few first times, when this function will be called. Then if you scroll the list / galery Android will give you the same view, which was constructed earlier using this function, and you should use it to optimally construct the new view, based on the old one.
Also, you should store the references to child view somewhere.
To better undestand that, look at this code example (taken from Android Developers):
public View getView(int position, View convertView, ViewGroup parent) {
// A ViewHolder keeps references to children views to avoid unneccessary calls
// to findViewById() on each row.
ViewHolder holder;
// When convertView is not null, we can reuse it directly, there is no need
// to reinflate it. We only inflate a new View when the convertView supplied
// by ListView is null.
if (convertView == null) {
convertView = mInflater.inflate(R.layout.list_item_icon_text, null);
// Creates a ViewHolder and store references to the two children views
// we want to bind data to.
holder = new ViewHolder();
holder.text = (TextView) convertView.findViewById(R.id.text);
holder.icon = (ImageView) convertView.findViewById(R.id.icon);
convertView.setTag(holder);
} else {
// Get the ViewHolder back to get fast access to the TextView
// and the ImageView.
holder = (ViewHolder) convertView.getTag();
}
// Bind the data efficiently with the holder.
holder.text.setText(DATA[position]);
holder.icon.setImageBitmap((position & 1) == 1 ? mIcon1 : mIcon2);
return convertView;
}
On getView() method normally you check if convertView is null, and if it isn't you just rewrite the fields in the View adapting it to the data you get based on the position instead of creating a new View (from inflation or whatever method you want).
Hope it helped,
JQCorreia
getView() has a second parameter as view(convertView).This convertView is the view which is returned from previous iteration.For the first iteration it will be null and adapter will create (instance) view.When it is done with creating required layout,the view will be returned to its caller.This returned value will be available as 2nd parameter from the next iteration onwards.So one can decide to reuse previously returned view instead of recreating by looking at this parameter.Thus Android achives re-usability feature while creating multiple list items.
Since convertView will always be null you should implement your own algorithm of caching and reusing items.
This is my implementation of Gallery adapter
public View getView(int position, View convertView, ViewGroup parent) {
int arrPosition = position % VIEW_CHACHE_SIZE;
ImageView imageView;
mCursor.moveToPosition(position);
if (parent.getHeight() > 0 && layoutParams.height == 0) {
layoutParams = new Gallery.LayoutParams(parent.getWidth() / VISIBLE_IMAGES_COUNT, (int) (parent.getHeight() * IMAGE_HEIGHT_COEFICIENT));
viewsList[0].setLayoutParams(layoutParams);
}
if (convertView != null) {
Log.i("GALLERY", "convert view not null");
}
// check views cache
if (viewsList[arrPosition] == null) {
imageView = new ImageView(mContext);
imageView.setPadding(3, 3, 3, 3);
viewsList[arrPosition] = imageView;
} else {
imageView = viewsList[arrPosition];
if (position == arrPosition) {
if (imageView.getDrawable().equals(imagesList.get(position))) {
return imageView;
}
}
}
// check images cache
if (imagesList.get(position) != null) {
imageView.setImageDrawable(imagesList.get(position));
} else {
byte[] photo = mCursor.getBlob(mCursor.getColumnIndex(DataProxy.PHOTO_COLUMN));
imagesList.put(position, new BitmapDrawable(BitmapFactory.decodeByteArray(photo, 0, photo.length)));
imageView.setImageDrawable(imagesList.get(position));
}
imageView.setScaleType(ImageView.ScaleType.FIT_CENTER);
imageView.setLayoutParams(layoutParams);
return imageView;
}
.........................................................
private SparseArray<Drawable> imagesList = new SparseArray<Drawable>();
private ImageView[] viewsList = new ImageView[VIEW_CHACHE_SIZE];
private Gallery.LayoutParams layoutParams = new LayoutParams(0, 0);
private static final int VIEW_CHACHE_SIZE = 4;