I'm using a RecycleView with a Gridlayoutmanager. My app loads a lot of items when the user scrolls down.
LinkedList has a good performance when adding new elements, while my ArrayList would need to get constantly resized.
But I'm not sure about what RecycleView does in the background which would work better with an ArrayList and/or a LinkedList.
My adapter would be:
public class PhotosAdapter extends RecyclerView.Adapter<PhotosAdapter.PhotosViewHolder> {
private Context context;
private List<Photo> items;
public PhotosAdapter(Context context, List<Photo> items) {
this.context = context;
this.items = items;
}
//other code here
public void addAll(List<Photo> newItems) {
int beforeSize = items.size()-1;
items.addAll(newItems);
notifyItemRangeInserted(beforeSize, newItems.size());
}
}
So when I create a new empty adapter I can either do this:
new PhotosAdapter(getContext(), new ArrayList<Photo>());
or this:
new PhotosAdapter(getContext(), new LinkedList<Photo>());
And when adding new elements simply:
adapter.addAll(myPhotos);
So would a LinkedList work better in this case? What about RecycleView's optimalized scrolling? Does that work better with an ArrayList or a LinkedList?
Now the first question should be are you optimizing prematurely? Is this a critical part of your app and are you having performance problems?
Anyway ArrayLists will give you better performance in most situations. I'd recommend using it as default and only using linked lists if you want to insert data into the middle of the list.
Yes, ArrayLists need to resize the array when they get too big, but in most cases this won't offset the advantages you get.
Remember that get(int index) is O(n) when using LinkedLists vs O(1) when using ArrayLists. If you're really concerned about adding lots of elements often, you can give the ArrayList a large initial capacity so it won't have to resize too often.
Check out this talk from Bjarne Stroustrup if you're interested. https://www.youtube.com/watch?v=YQs6IC-vgmo
It doesn't matter. Plus, you can always just try both of them and measure the performance.
More likely, the issue lies with something else:
Are you retrieving the data on a background thread?
Are new items being added in between existing items? Or only at the end? If it's only at the end you could try pre-fetching the items when the user is close to the bottom. (Again, by firing off a background thread and listening for a callback)
Are your child views complicated?
Since the code mentions using List, I'm guessing it's a recyclerView with images. The problem could be bitmaps being constantly allocated (and deallocated) memory.
In any case, I recommend profiling. A difference as minuscule as using ArrayList instead of LinkedList doesn't matter in all but the most extreme cases. See what your garbage collector is doing. Look at which method takes the longest to run, try to find what's slowing down your app the most.
Related
I am using a list view to display user submitted comments. This is naturally backed by an ArrayAdapter for displaying. Now there can be many, many comments and I don't want that all the thousand comments are preserved in memory.
Do I really have to worry about this or does Android take care of this itself? If not, how can I best do this. The only strategy I came up with when a new comment is added:
check size of the list
remove one comment if it is > threshold
add new comment
clear adapter
reassign list to adapter
I disagree with the other answer. Sounds like you will be better off subclassing BaseAdapter to write your own custom adapter solution. It's a bit more work but would be the preferable solution. The moment you need to modify the behavior for one of the mutating methods of an ArrayAdapter is the moment you open yourself up to possible problems. It's also not recommended to track the list used to construct your ArrayAdapter backed solution. For one, there's no guarantee that the list reference remains the same...and in fact can change. So it makes keeping your list in sync with ArrayAdapter rather difficult.
Also, I may be wrong, but I believe re-assigning a sublist to original list will fail. Check out this post. You'd be better off just calling items.remove(#); to remove an item.
But otherwise, the general solution would be (within your custom adapter) is to always check the size of your list when adding. If over the threshold, remove an item.
On your constructor, slice the list if it exceeds the limit.
public MyAdapter(List<Item> items) {
this.items = items;
sliceItems();
}
void sliceItems() {
//sort items first
if (this.items.size() > limit) {
this.items = this.items.subList(0, limit);
}
}
public void addItem(Item item) {
this.items.add(item);
sliceItems();
}
To update a Listview we can do following two things:
1) Create a new ArrayAdapter and bind it to ListView everytime the data is updated.
this.listView.setAdapter(new ArrayAdapter(getActivity(), data));
or
2) Update the contents of ArrayAdapter which is already binded to listview:
this.arrayadapter.clear();
for (Data item : data)
this.arrayadapter.add(item);
this.arrayadapter.notifyDataSetChanged();
So which method is more expensive?
The way you are doing it, the second one is definitely more expensive. ArrayAdapter calls notifyDataSetChanged() internally in clear() as well as in add(). You could change it to
this.arrayadapter.setNotifyOnChange(false);
this.arrayadapter.clear();
for (Data item : data)
this.arrayadapter.add(item);
this.arrayadapter.setNotifyOnChange(true);
this.arrayadapter.notifyDataSetChanged();
But even if you it this way, both calls contain a synchronized block which makes them slow. So even though you are creating a new ArrayAdapter in the first method, I would say it is faster.
You could further optimize it by using a custom adapter with a setItems(data) method that just replaces the internal data list.
I'd becareful with the accepted answers final conclusion. While SimonSays makes valid points about the iteration and adding...there's a lot that happens with setting a new adapter he fails to recognize. Which makes it hard to say if it's actually any better then the suggested for...each approach.
Setting a new adapter will cause the ListView to flush out all it's recycled view's. It'll also have to re-measure all the new views coming in...which leads to the getView() being called 3-4 times per item for several of the positions...if not all positions. (Varies with platform). So even though there's a sync block on adding, you'll probably see the getView method invoked far less times with possibility of using recycled views.
Basically, knowing which is better is hard and depends greatly on use case. I'd vote to stick with the for...each approach simply for better readability.
after some advice really. My app fills a list view on load using a mediastore cursor. This is pulling music linked to user defined folder, which in most cases will be all of their stored music. I have one beta tester that is using an Archos Tablet with approximately 10000 songs on it, running android 2.2. While performance for most users is pretty slick, I wanted to improve the experience for users such as this.
The current process:
User loads app.
App finds default folder
App populates list view with music within and below that folder
User moves to a folder further down the tree, list view is repopulated based on the selected folder
User moves again....list is repopulated based on the selected folder...
So what I'm wondering is this - is it faster/more efficient to use the following process:
User loads app
App finds default folder
app populates list view with music within and below that folder
user moves to a folder within the tree, THE LIST IS FILTERED TO THAT FOLDER
if the user moves higher up the tree than the default data (i.e. potential for new files), the list view is repopulated, but only in this circumstance.
So basically,my questions is "how does filtering compare to repopulation?"
A very good question. Let me try to answer this.
Filtering is actually repopulation the ListView, whereas you create/get a new collection and tell the Adapter it's content has changed by calling notifyDataSetChanged.
The 'heavy' work for a listView is that getView call in it's adapter. I've tested this myself, and if you inflate a new View every time getView is called, the performance drops. Heavenly.
The ListView's adapter is built so that already inflated views can be re-used, which tackles above named problem. Besides, only visible views are loaded, so it's not like the Adapter is going to create 10000 views if you tell it's collection is 10000 items big.
notifyDataSetChanged will tell the adapter to rebuild the listviews content, but it still contains previously inflated views. So here is a big performance win.
So my advice for you is, when you are using the same 'row layout' to just repopulate the ListView using notifyDataSetChanged. I've implemented this multiple times myself without noticing any UI performance issues. Just make sure to do the filtering of your collection an a background thread. (AsyncTask comes in handy here).
One last tip: Do you have any phone thats quite old? Or someone you know does? Find the slowest phone you can and test your application on it for performance. I have a HTC Legend myself, which is outdated and slow if f*ck, but perfect for performance testing. If it runs on my (old) phone, it runs on any phone.
Pseudo code sample if your applications flow:
public class FolderListActivity extends Activity implements OnItemSelected {
// NOTE: THIS IS PSEUDO CODE
private ListView listView
private Adapter adapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstaceState);
// setContentView here
listView = (ListView)findViewById(R.id.your_listview_here);
listView.setOnItemSelectedListener(this);
}
public class AsyncLoadMusicLocationTask extends AsyncTask<Void, Void, List<String>> {
public List<String> doInBackground(Void... params) {
// Load the information here, this happens in the background
// using that cursor, i'm not sure what kind of things you are using
// So I assumed a List of Strings
}
#Override
public void onPostExecute(List<String> result) {
// Here we have our collection that was retrieved in a background thread
// This is on the UI thread
// Create the listviews adapter here
adapter = new Adapter(result, and other parameters);
listView.setAdapter(adapter);
}
}
#Override
public void onItemSelect(Some params, not sure which) {
// THIS SHOULD BE DONE ON THE BACKGROUND THE PREVENT UI PERFORMANCE ISSUES
List<String> collection = adapter.getObjects();
for (int i = 0; i < collection.size(); i++) {
// Filter here
}
// this method will most probably not exist, so you will need to implement your own Adapter class
adapter.setObjects(collections);
adapter.notifyDataSetChanged();
}
}
I have a ListFragment backed by an ArrayAdapter that gets populated by a Loader. When the user clicks on one of the items, I want to pass a reference to the selected item, as well as the rest of the list items to another fragment. My question is how should I get all of the items from the adapter? Here are the possibilities that I see:
1. Keep a reference to the backing List
Create the adapter like so:
List<DomainObject> items = new ArrayList<DomainObject>();
listAdapter = new ArrayAdapter<DomainObject>(getActivity(), R.layout.mine, items);
and then simply pass items or a copy of it to the next activity.
The downside I see of this is that I'm relying on the undocumented fact that the same list that I pass to the constructor contains the items later on.
2. Iterate through the adapter
When an item is clicked, iterate through the adapter and build up the list. This seems like an unnecessary amount of work. The items are contained in a List in the adapter and I'm manually copying each item to a new list.
3. Keep a separate list of items when adding to adapter
Before adding an item to the adapter, add it to a separate list that I maintain in the fragment. This is also wasteful as the list of items is copied in the ArrayAdapter and the fragment.
I'm a little late to the game, but I've run up against a similar issue.
One way to deal with #1 would be to maintain the reference to the list within a subclass of ArrayAdapter, so that your reuse is controlled by the adapter object.
Something like:
public class DomainAdapter extends ArrayAdapter<DomainObject> {
private final List<DomainObject> items;
public DomainAdapter(Context context, List<DomainObject> items) {
super(context, R.layout.mine, items);
this.items = items;
}
public List<DomainObject> getItems() {
return items;
}
}
The solution that I've gone with in the meantime is just to not use ArrayAdapter. In cases where you're fighting against this API, it seems like it's better just to use the less fully-featured (and complex) BaseAdapter. You can read more about the decision to go with BaseAdapter instead of ArrayAdapter in this article: Android Adapter Good Practices.
A quick test says that method 1 works. It seems the quickest and cleanest, but since it is undocumented you may want to test it across the intended platforms and whenever they update in case the underlying structure of ArrayAdapter changes.
I am using compile SDK version 22 and min SDK Version 10.
The best method is to "keep a reference to the List" BUT not passing "items" variable/parameter to the Constructor:
List<DomainObject> items = new ArrayList<DomainObject>();
listAdapter = new ArrayAdapter<DomainObject>(getActivity(), R.layout.mine);
In this way you only instantiate the ArrayList as an empty array and you will have to manage YOUR list by yourself.
I think first method is best way to do this.
I dont think, Data would be original for the Another Activity. because, You would pass items through bundle, so the object is written on bundle first and then in next Activity we read from bundle.
However, if you are using some other way to pass the list, use list.clone() to create new Object, instead of passing original one.
I use a custom list adapter and ArrayList for my ListView. This solution was good enough but now I need to use Map of ArrayLists, something like this:
TreeMap<String, ArrayList<ItemsModel>>
where ItemsModel is a Java bean. Earlier I used to populate this ArrayList it that way:
itemsDataArrayList.add(itemModel)
Now I faced some difficulties with Map interface. First, I don't know how to populate my new Map structure: I suppose this
mapInstance.put(itemModel.getItemName.toString(), itemsDataArrayList)
won't work because itemsDataArrayList is the list of elements, not a certain element.
Second, I'm not sure how to properly declare this map instance in my ItemsAdapter class. When I was using just ArrayList it was very simple. Some examples would be very helpful.
What I recommend you look into / try is creating your own BaseAdapter. When you override this class it will give you all the functions you need to override to populate the list view.
This has the advantage of giving you complete control of what is put into the listview and how each view is created.
Then after you get it working I recommend looking into the ViewHolder design pattern along with recycling views which is a great way of improving efficiency when scrolling the listview.
What you are really looking for seem to be a MultiMap. To your first Question - your attemp was quite good, you can only put ArrayLists as values into your TreeMap.
The Problem with this might be, that if you want to add some ItemsModel to your Map, you first need to get the List of the key, and then add the ItemsModel to that list. Additionally you need to ensure, that this list for this particular key exist, and if not, create it.
Example:
String key = "hi";
ArrayList keyList = mapInstance.get(key);
if (keyList == null) {
keyList = new ArrayList();
mapInstance.put(key, keyList);
}
keyList.add(itemsModelInstance);
A get()/contains() and so on may be somehow equal. I'd suggest you build your own Multimap<?,?> Implementation or just take an existing one, like the one from Guava (link above).
Kind regards,
avi