A single line of code is causing memory leaks in my app. I've debugged more than 200 times but still cannot find the reason of the leak. I've compared the Content Provider implementation with other that i have and the code is the same. The app works well,except when i rotate the device the app LeakCanary detects a Leak. The Loaders seems to be well constructed also. Any help would be highly appreciated.
This is the line of code causing the memory leak:(if I comment out this line of code my memory leaks disapperas, obviously my list is then not filled with data)
mAdapterIndexes.swapCursor(data);
This is the fragment where I use the Loader:
public class FragmentAmerica extends Fragment implements LoaderManager.LoaderCallbacks<Cursor> {
private NonScrollListView mNewsListView;
private NonScrollListView mIndexesListView;
private ScrollView mScrollView;
private boolean mRotationDone=false;
private static final String KEY_POSX="x_pos";
private static final String KEY_POSY="y_pos";
private static final int NEWS_LOADER=0;
private static final int INDEXES_LOADER=1;
private RssNewsAdapter mAdapter;
private IndexesAdapter mAdapterIndexes;
// private IndexesAdapter mAdapterIndexes;
private static final String selectionNews =CapstoneContract.NewsEntity.REGION + " =?";
private static final String[] selectionArgsNews =new String[]{"AMERICA"};
private static final String selectionIndexes=CapstoneContract.IndexesEntity.REGION + " =?";
private static final String[] selectionArgsIndexes=new String[]{"AMERICA"};
private static final String sortByDateDesc = CapstoneContract.NewsEntity.DATE + " DESC";
private int x;
private int y;
LinearListView.OnItemClickListener mListener = new LinearListView.OnItemClickListener() {
@Override
public void onItemClick(LinearListView parent, View view, int position,long id) {
final TextView tvLink,tvTitle;
tvLink= (TextView) view.findViewById(R.id.txtUrlLink);
tvTitle= (TextView) view.findViewById(R.id.txtTitulo);
Uri uri=null;
uri = Uri.parse(tvLink.getText().toString());
//customization possibilities
CustomTabsIntent customTabsIntent = new CustomTabsIntent.Builder()
.setToolbarColor(ContextCompat.getColor(getActivity(),R.color.colorPrimary))
.setShowTitle(true)
.build();
CustomTabActivityHelper.openCustomTab(getActivity(), customTabsIntent,uri,
//in case the user doen't have chromium v 45 installed, offer an alternative
//browser experience with WebView
new CustomTabActivityHelper.CustomTabFallback() {
@Override
public void openUri(Activity activity, Uri uri) {
Intent intent=new Intent(getActivity(),DetailsNewsActivity.class);
intent.putExtra("url",tvLink.getText());
intent.putExtra("title",tvTitle.getText());
startActivity(intent);
}
});
}
};
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
//getActivity().getSupportLoaderManager().initLoader(INDEXES_LOADER, null, this);
getLoaderManager().initLoader(NEWS_LOADER,null,this);
//getActivity().getSupportLoaderManager().initLoader(NEWS_LOADER, null, this);
getLoaderManager().initLoader(INDEXES_LOADER,null,this);
super.onActivityCreated(savedInstanceState);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view=inflater.inflate(R.layout.fragment_america,container,false);
mNewsListView = (NonScrollListView) view.findViewById(R.id.newsList);
mIndexesListView= (NonScrollListView) view.findViewById(R.id.indexesList);
mScrollView= (ScrollView) view.findViewById(R.id.scrollViewAm);
mScrollView.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
@Override
public void onScrollChanged() {
x=mScrollView.getScrollX();
Log.d("VILLANUEVA","x pos:"+mScrollView.getScrollX()+",y pos:"+mScrollView.getScrollY());
y=mScrollView.getScrollY();
}
});
mAdapter =new RssNewsAdapter(getActivity(),null,NEWS_LOADER);
mAdapterIndexes=new IndexesAdapter(getActivity(),null,INDEXES_LOADER);
//set both adapters
mIndexesListView.setAdapter(mAdapterIndexes);
mNewsListView.setAdapter(mAdapter);
// mNewsListView.setOnItemClickListener(mListener);
if(savedInstanceState==null) {
} else {
mRotationDone=true;
//scroll to saved position
mScrollView.scrollTo(savedInstanceState.getInt(KEY_POSX),savedInstanceState.getInt(KEY_POSY));
}
Toast.makeText(getActivity(),"onCreateViewAmerica",Toast.LENGTH_SHORT).show();
return view;
}
@Override
public void onResume() {
super.onResume();
Toast.makeText(getActivity(),"onResumeAmerica",Toast.LENGTH_SHORT).show();
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(KEY_POSX,mScrollView.getScrollX());
outState.putInt(KEY_POSY,mScrollView.getScrollY());
}
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
if (id==INDEXES_LOADER) {
return new CursorLoader(getActivity().getApplicationContext(),
CapstoneContract.IndexesEntity.CONTENT_URI,null,selectionIndexes,selectionArgsIndexes,null);
} else if(id==NEWS_LOADER) {
return new CursorLoader(getActivity().getApplicationContext(),
CapstoneContract.NewsEntity.CONTENT_URI, null, selectionNews, selectionArgsNews, sortByDateDesc);
}
return null;
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
switch(loader.getId()) {
case INDEXES_LOADER:
mAdapterIndexes.swapCursor(data); //if i comment out this line
//line of code the app works fine
break;
case NEWS_LOADER:
mAdapter.swapCursor(data);
break;
}
// mScrollView.postDelayed(new Runnable() {
// @Override
// public void run() {
// mScrollView.scrollTo(x,y);
// }
// },200);
Log.d("VILLANUEVA","move to x pos:"+x+",y pos:"+y);
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
switch(loader.getId()) {
case NEWS_LOADER:
mAdapter.swapCursor(null);
break;
case INDEXES_LOADER:
mAdapterIndexes.swapCursor(null);
break;
}
}
@Override
public void onDestroy() {
super.onDestroy();
// ExampleApplication application = (ExampleApplication) getActivity().getApplicationContext();
// application.mustDie(this);
}
}
This is the query methond in the Content Provider:
@Nullable
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
Cursor retCursor=null;
final int match = mUriMatcher.match(uri);
switch (match) {
case NEWS:
retCursor=mDbHelper.getReadableDatabase().query(
CapstoneContract.NewsEntity.TABLE_NAME,
projection,
selection,
selectionArgs,
null, //group by
null, //having
sortOrder);
break;
case INDEX:
retCursor=mDbHelper.getReadableDatabase().query(
CapstoneContract.IndexesEntity.TABLE_NAME,
projection,
selection,
selectionArgs,
null,
null,
sortOrder);
break;
default: throw new UnsupportedOperationException("Unknown Uri" + uri);
}
retCursor.setNotificationUri(getContext().getContentResolver(),uri);
return retCursor;
}
CanaryLeak log:
02-06 12:22:41.195 7809-8716/com.carlos.capstone D/LeakCanary: In com.carlos.capstone:1.0:1.
02-06 12:22:41.195 7809-8716/com.carlos.capstone D/LeakCanary: * com.carlos.capstone.MainActivity has leaked:
02-06 12:22:41.195 7809-8716/com.carlos.capstone D/LeakCanary: * GC ROOT static android.view.WindowManagerGlobal.sDefaultWindowManager
02-06 12:22:41.195 7809-8716/com.carlos.capstone D/LeakCanary: * references android.view.WindowManagerGlobal.mViews
02-06 12:22:41.195 7809-8716/com.carlos.capstone D/LeakCanary: * references java.util.ArrayList.array
02-06 12:22:41.195 7809-8716/com.carlos.capstone D/LeakCanary: * references array java.lang.Object[].[0]
02-06 12:22:41.195 7809-8716/com.carlos.capstone D/LeakCanary: * references com.android.internal.policy.impl.PhoneWindow$DecorView.mAttachInfo
02-06 12:22:41.195 7809-8716/com.carlos.capstone D/LeakCanary: * references android.view.View$AttachInfo.mScrollContainers
02-06 12:22:41.195 7809-8716/com.carlos.capstone D/LeakCanary: * references java.util.ArrayList.array
02-06 12:22:41.195 7809-8716/com.carlos.capstone D/LeakCanary: * references array java.lang.Object[].[1]
02-06 12:22:41.195 7809-8716/com.carlos.capstone D/LeakCanary: * references com.carlos.capstone.customcomponents.NonScrollListView.mAdapter
02-06 12:22:41.195 7809-8716/com.carlos.capstone D/LeakCanary: * references com.carlos.capstone.adapters.IndexesAdapter.mCursor
02-06 12:22:41.195 7809-8716/com.carlos.capstone D/LeakCanary: * references android.content.ContentResolver$CursorWrapperInner.mCursor
02-06 12:22:41.195 7809-8716/com.carlos.capstone D/LeakCanary: * references android.database.sqlite.SQLiteCursor.mDataSetObservable
02-06 12:22:41.195 7809-8716/com.carlos.capstone D/LeakCanary: * references android.database.DataSetObservable.mObservers
02-06 12:22:41.195 7809-8716/com.carlos.capstone D/LeakCanary: * references java.util.ArrayList.array
02-06 12:22:41.195 7809-8716/com.carlos.capstone D/LeakCanary: * references array java.lang.Object[].[0]
02-06 12:22:41.195 7809-8716/com.carlos.capstone D/LeakCanary: * references android.widget.CursorAdapter$MyDataSetObserver.this$0
02-06 12:22:41.195 7809-8716/com.carlos.capstone D/LeakCanary: * references com.carlos.capstone.adapters.IndexesAdapter.mContext
02-06 12:22:41.195 7809-8716/com.carlos.capstone D/LeakCanary: * leaks com.carlos.capstone.MainActivity instance
02-06 12:22:41.195 7809-8716/com.carlos.capstone D/LeakCanary: * Reference Key: 08e1a3e5-78cd-4f09-8cc7-1c307201d763
02-06 12:22:41.195 7809-8716/com.carlos.capstone D/LeakCanary: * Device: Genymotion generic Google Nexus 5 - 5.1.0 - API 22 - 1080x1920 vbox86p
02-06 12:22:41.195 7809-8716/com.carlos.capstone D/LeakCanary: * Android Version: 5.1 API: 22 LeakCanary: 1.3.1
02-06 12:22:41.195 7809-8716/com.carlos.capstone D/LeakCanary: * Durations: watch=6235ms, gc=114ms, heap dump=983ms, analysis=4339ms
02-06 12:22:41.195 7809-8716/com.carlos.capstone D/LeakCanary: * Details:
UPDATED with NonScrollListView class
public class NonScrollListView extends ListView {
public NonScrollListView(Context context) {
super(context);
}
public NonScrollListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public NonScrollListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int heightMeasureSpec_custom = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,
MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, heightMeasureSpec_custom);
ViewGroup.LayoutParams params = getLayoutParams();
params.height = getMeasuredHeight();
}
}
UPDATED 2 WITH SOLUTION:
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
mCursor=data;
switch(loader.getId()) {
case INDEXES_LOADER:
Log.d(LOG_TAG,"onLoadFinished INDEXES_LOADER");
mAdapterIndexes.swapCursor(mCursor);
break;
case NEWS_LOADER:
Log.d(LOG_TAG,"onLoadFinished NEWS_LOADER");
mAdapterNews.swapCursor(mCursor);
}
}
and the following:
public void onDestroy() {
if(mCursor!=null && !mCursor.isClosed()) {
mCursor.close();
}
super.onDestroy();
}
I have been through similar situation but not in the case of Loader. The main reason behind this leak is Cursor is still open while orientation is getting changed. In your code I could see the same reason where you are returning a cursor on your query method. Try to implement cursor.close() on orientation change and re-query once the orientation change is complete.