I am using Parse.com android sdk. Inside my TabsActivity.java
, I have a SearchFragment
which extends ListFragment
in order to populate a ListView with a Custom ParseQueryAdapter. Inside my custom adapter I declare a custom list row layout called search_list_item.xml
. This layout contains only a ParseImageView
.
My problem is that when I fast scroll down the list my logcat gets full of
I/dalvikvm-heap﹕ Grow heap (frag case) to ...MB for byte allocation
and the the listView returns to the initial position(that means it gets back to the first item). If on the other hand I slowly scroll the list I can get at the end of the items without this error. How could I fix this??
Additionally, if I use the default ParseQueryAdapter without customizing the rows with search_list_item.xml
I don't have such a problem.
Below I post some code which I think will be useful:
The code of search_list_item.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.parse.ParseImageView
android:id="@+id/ProfileImage"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</RelativeLayout>
The code of TabsActivity.Java
public class TabsActivity extends Activity implements SearchFragment.OnFragmentInteractionListener, ActionBar.TabListener {
/**
* The {@link android.support.v4.view.PagerAdapter} that will provide
* fragments for each of the sections. We use a
* {@link FragmentPagerAdapter} derivative, which will keep every
* loaded fragment in memory. If this becomes too memory intensive, it
* may be best to switch to a
* {@link android.support.v13.app.FragmentStatePagerAdapter}.
*/
SectionsPagerAdapter mSectionsPagerAdapter;
/**
* The {@link ViewPager} that will host the section contents.
*/
ViewPager mViewPager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_tabs);
// Set up the action bar.
final ActionBar actionBar = getActionBar();
actionBar.setLogo(R.drawable.logo_white);
actionBar.setDisplayShowTitleEnabled(false);
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
// Create the adapter that will return a fragment for each of the three
// primary sections of the activity.
mSectionsPagerAdapter = new SectionsPagerAdapter(getFragmentManager());
// Set up the ViewPager with the sections adapter.
mViewPager = (ViewPager) findViewById(R.id.tabs_pager);
mViewPager.setAdapter(mSectionsPagerAdapter);
// When swiping between different sections, select the corresponding
// tab. We can also use ActionBar.Tab#select() to do this if we have
// a reference to the Tab.
mViewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
@Override
public void onPageSelected(int position) {
actionBar.setSelectedNavigationItem(position);
}
});
// For each of the sections in the app, add a tab to the action bar.
actionBar.addTab(
actionBar.newTab()
.setText("Search")
.setTabListener(this));
actionBar.addTab(
actionBar.newTab()
.setText("Secind")
.setTabListener(this));
actionBar.addTab(
actionBar.newTab()
.setText("Third")
.setTabListener(this)
);
actionBar.addTab(
actionBar.newTab()
.setText("Fourth")
.setTabListener(this));
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.tabs, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
if (id == R.id.action_settings) {
}
return super.onOptionsItemSelected(item);
}
@Override
public void onTabSelected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {
// When the given tab is selected, switch to the corresponding page in
// the ViewPager.
mViewPager.setCurrentItem(tab.getPosition());
}
@Override
public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {
}
@Override
public void onTabReselected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {
}
/**
* A {@link FragmentPagerAdapter} that returns a fragment corresponding to
* one of the sections/tabs/wizard1.
*/
public class SectionsPagerAdapter extends FragmentPagerAdapter {
public SectionsPagerAdapter(FragmentManager fm) {
super(fm);
}
@Override
public Fragment getItem(int index) {
switch (index) {
case 0:
// Search fragment activity
return new SearchFragment();
case 1:
// Flirts fragment activity
return new SecondFragment();
case 2:
// Explore fragment activity
return new ThirdFragment();
case 3:
//Profile fragment activity
return new FourthFragment();
}
return null;
}
@Override
public int getCount() {
// Show 4 total wizard1.
return 4;
}
}
public void onFragmentInteraction(String id) {
// The user selected the headline of an article from the HeadlinesFragment
// Do something here to display that article
}
}
The code of SearchFragment.java
public class SearchFragment extends ListFragment {
private OnFragmentInteractionListener mListener;
private CustomDogAdapter mainAdapter;
/**
* Mandatory empty constructor for the fragment manager to instantiate the
* fragment (e.g. upon screen orientation changes).
*/
public SearchFragment() {
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mainAdapter = new CustomAdapter(this.getActivity());
// Set the ListActivity's adapter to be the PQA
mainAdapter.setAutoload(false);
mainAdapter.loadObjects();
setListAdapter(mainAdapter);
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mListener = (OnFragmentInteractionListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement OnFragmentInteractionListener");
}
}
@Override
public void onDetach() {
super.onDetach();
mListener = null;
}
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
super.onListItemClick(l, v, position, id);
if (null != mListener) {
// Notify the active callbacks interface (the activity, if the
// fragment is attached to one) that an item has been selected.
}
}
/**
* This interface must be implemented by activities that contain this
* fragment to allow an interaction in this fragment to be communicated
* to the activity and potentially other fragments contained in that
* activity.
* <p>
* See the Android Training lesson <a href=
* "http://developer.android.com/training/basics/fragments/communicating.html"
* >Communicating with Other Fragments</a> for more information.
*/
public interface OnFragmentInteractionListener {
// TODO: Update argument type and name
public void onFragmentInteraction(String id);
}
}
The code of CustomAdapter.java
public class CustomAdapter extends ParseQueryAdapter<ParseObject> {
public CustomAdapter(Context context) {
// Use the QueryFactory to construct a PQA that will only show
// Todos marked as high-pri
super(context, new ParseQueryAdapter.QueryFactory<ParseObject>() {
public ParseQuery create() {
ParseQuery query = new ParseQuery("Photo");
query.whereEqualTo("imageName", "profileImage");
// query.setCachePolicy(ParseQuery.CachePolicy.CACHE_ELSE_NETWORK);
return query;
}
});
}
// Customize the layout by overriding getItemView
@Override
public View getItemView(ParseObject object, View v, ViewGroup parent) {
if (v == null) {
v = View.inflate(getContext(), R.layout.search_list_item, null);
}
super.getItemView(object, v, parent);
// Add and download the image
ParseImageView image = (ParseImageView) v.findViewById(R.id.profileImage);
ParseFile photoFile = object.getParseFile("imageFile");
if (photoFile != null) {
image.setParseFile(photoFile);
image.loadInBackground();
// (new GetDataCallback() {
// @Override
// public void done(byte[] data, ParseException e) {
// // nothing to do
// }
// });
}
// // Add the title view
// TextView titleTextView = (TextView) v.findViewById(R.id.text1);
// titleTextView.setText(object.getString("title"));
//
// // Add a reminder of how long this item has been outstanding
// TextView timestampView = (TextView) v.findViewById(R.id.timestamp);
// timestampView.setText(object.getCreatedAt().toString());
return v;
}
}
I have studied a lot of times and followed these documentations from Parse.com
2) MealSpotting
Thank you very much for your time and sorry for the long post, just tried to be as explanatory as possible.
After some more general searching abot list views and their performance I stepped across a blog post which has a very interesting last paragraph which says:
Never set the height of a ListView to wrap_content. If you have all your data locally available, it might not seem so bad, but it becomes particularly troublesome when you don’t. If you use wrap_content on your ListView, this is what happens: The first getView call is done, convertView is null, and position 0 is loaded. Now, position 1 is loaded, but it is passed the View you just generated for position 0 as its convertView. Then position 2 is loaded with that same View, and so on. This is done to lay out the ListView since it has to figure out how tall it should be and you didn’t explicitly tell it. Once it has run through all of those positions, that View is passed back to position 0 for yet another getView call, and then position 1 and on are loaded with getView and no convertView. You’re going to end up seeing getView called two or three times as often as you would have expected. Not only does that suck for performance, but you can get some really confusing issues.
source: Read the last paragraph
I don't understand it exactly as I am new to development, but after reading that I decided to change layout height and width with predefined dp values and set android:scaleType="centerCrop"
. This solved the grow heap issue and scrolling became really smooth.
I hope that someone finds it helpful as I did, and if anyone can explain it more technically I would be very interested in reading it. Thank you very much in advance!