0

I'm having a memory leak in a cursor Loader. This is the log of the memory Leak: For this line:

* references com.carlos.capstone.adapters.SuggestionsAdapter.mContext

I deduce that the leak is happening in the "handler" of the SuggestionAdapter and because int the log there are references to a Cursor. For some reason a Cursor cannot be closed but I don't know why.

This is the LeakCanary log:

03-24 20:11:29.929 9939-12585/com.carlos.capstone D/LeakCanary: In com.carlos.capstone:1.0:1.
03-24 20:11:29.929 9939-12585/com.carlos.capstone D/LeakCanary: * com.carlos.capstone.MainActivity has leaked:
03-24 20:11:29.929 9939-12585/com.carlos.capstone D/LeakCanary: * GC ROOT android.app.ActivityThread$ApplicationThread.this$0
03-24 20:11:29.929 9939-12585/com.carlos.capstone D/LeakCanary: * references android.app.ActivityThread.mActivities
03-24 20:11:29.929 9939-12585/com.carlos.capstone D/LeakCanary: * references android.util.ArrayMap.mArray
03-24 20:11:29.929 9939-12585/com.carlos.capstone D/LeakCanary: * references array java.lang.Object[].[1]
03-24 20:11:29.929 9939-12585/com.carlos.capstone D/LeakCanary: * references android.app.ActivityThread$ActivityClientRecord.activity
03-24 20:11:29.929 9939-12585/com.carlos.capstone D/LeakCanary: * references com.carlos.capstone.MainActivity.mFragments
03-24 20:11:29.929 9939-12585/com.carlos.capstone D/LeakCanary: * references android.support.v4.app.FragmentController.mHost
03-24 20:11:29.929 9939-12585/com.carlos.capstone D/LeakCanary: * references android.support.v4.app.FragmentActivity$HostCallbacks.mAllLoaderManagers
03-24 20:11:29.929 9939-12585/com.carlos.capstone D/LeakCanary: * references android.support.v4.util.SimpleArrayMap.mArray
03-24 20:11:29.929 9939-12585/com.carlos.capstone D/LeakCanary: * references array java.lang.Object[].[1]
03-24 20:11:29.929 9939-12585/com.carlos.capstone D/LeakCanary: * references android.support.v4.app.LoaderManagerImpl.mLoaders
03-24 20:11:29.929 9939-12585/com.carlos.capstone D/LeakCanary: * references android.support.v4.util.SparseArrayCompat.mValues
03-24 20:11:29.930 9939-12585/com.carlos.capstone D/LeakCanary: * references array java.lang.Object[].[2]
03-24 20:11:29.930 9939-12585/com.carlos.capstone D/LeakCanary: * references android.support.v4.app.LoaderManagerImpl$LoaderInfo.mData
03-24 20:11:29.930 9939-12585/com.carlos.capstone D/LeakCanary: * references android.content.ContentResolver$CursorWrapperInner.mCursor
03-24 20:11:29.930 9939-12585/com.carlos.capstone D/LeakCanary: * references android.database.sqlite.SQLiteCursor.mDataSetObservable
03-24 20:11:29.930 9939-12585/com.carlos.capstone D/LeakCanary: * references android.database.DataSetObservable.mObservers
03-24 20:11:29.930 9939-12585/com.carlos.capstone D/LeakCanary: * references java.util.ArrayList.array
03-24 20:11:29.930 9939-12585/com.carlos.capstone D/LeakCanary: * references array java.lang.Object[].[0]
03-24 20:11:29.930 9939-12585/com.carlos.capstone D/LeakCanary: * references android.support.v4.widget.CursorAdapter$MyDataSetObserver.this$0

03-24 20:11:29.930 9939-12585/com.carlos.capstone D/LeakCanary: * references com.carlos.capstone.adapters.SuggestionsAdapter.mContext

03-24 20:11:29.930 9939-12585/com.carlos.capstone D/LeakCanary: * leaks com.carlos.capstone.MainActivity instance
03-24 20:11:29.930 9939-12585/com.carlos.capstone D/LeakCanary: * Reference Key: c38cc8de-15d0-4c6a-8a72-7e759cb7be0a
03-24 20:11:29.930 9939-12585/com.carlos.capstone D/LeakCanary: * Device: LGE google Nexus 5 hammerhead
03-24 20:11:29.930 9939-12585/com.carlos.capstone D/LeakCanary: * Android Version: 5.1.1 API: 22 LeakCanary: 1.3.1
03-24 20:11:29.930 9939-12585/com.carlos.capstone D/LeakCanary: * Durations: watch=6704ms, gc=151ms, heap dump=6915ms, analysis=24691m

This is the structure of the onLoadFinished I have now:

public void onLoadFinished(Loader<Cursor> loader, Cursor data) {

        mCursor=data;
        mCursorSearchView=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);
                break;
            case SUGGESTIONS_LOADER:

                Log.d(LOG_TAG,"onLoadFinished SUGGESTION_LOADER");
                if (mSuggestionsAdapter != null) {
                    mSuggestionsAdapter.swapCursor(mCursorSearchView);
                }
        }

        mScrollView.postDelayed(new Runnable() {
            @Override
            public void run() {
                mScrollView.scrollTo(x, y);
            }
        }, 100);
    }

And the onDestroy():

  public void onDestroy() {
        Log.d(LOG_TAG,"FragmentRegion on Destroy");


        if(mCursor!=null && !mCursor.isClosed()) {
            mCursor.close();
        }
        if(mCursorSearchView!=null && !mCursorSearchView.isClosed()) {
            mCursorSearchView.close();
        }


        super.onDestroy();
    }

  @Override
    public void onLoaderReset(Loader<Cursor> loader) {
       switch(loader.getId()) {
            case INDEXES_LOADER:
               mAdapterIndexes.swapCursor(null);
                break;
            case NEWS_LOADER:
                mAdapterNews.swapCursor(null);
                break;
            case SUGGESTIONS_LOADER:
                mSuggestionsAdapter.swapCursor(null);
        }

    }

SuggestionAdapter:

public class SuggestionsAdapter extends CursorAdapter {
    public static final int COL_TICKER=1;
    public static final int COL_NAME=2;
    public static final int COL_EXCHANGE=3;
    public static final int COL_SECURITY_TYPE=4;


    public SuggestionsAdapter(Context context, Cursor c, int flags) {
        super(context, c, flags);
    }

    public static class ViewHolder{
        public TextView tvSymbol;
        public TextView tvName;
        public TextView tvStockExchange;

        public ViewHolder(View v) {
                tvSymbol= (TextView) v.findViewById(R.id.symbol);
                tvName= (TextView) v.findViewById(R.id.name);
                tvStockExchange= (TextView) v.findViewById(R.id.stockExchange);

        }
    }


    @Override
    public View newView(Context context, Cursor cursor, ViewGroup parent) {
        View v=LayoutInflater.from(context).inflate(R.layout.suggestion_item,parent,false);
        ViewHolder viewHolder=new ViewHolder(v);
        v.setTag(viewHolder);
        return v;
    }

    @Override
    public void bindView(View view, Context context, Cursor cursor) {
        ViewHolder viewHolder= (ViewHolder) view.getTag();
        viewHolder.tvSymbol.setText(cursor.getString(COL_TICKER));
        String name = Normalizer.normalize(cursor.getString(COL_NAME), Normalizer.Form.NFC);
        viewHolder.tvName.setText(name);
        viewHolder.tvStockExchange.setText(cursor.getString(COL_EXCHANGE));
    }
}

Thanks in advance

Carlos Hernández Gil
  • 1,853
  • 2
  • 22
  • 30

2 Answers2

0

Doug, I added the line:

 getLoaderManager().restartLoader(SUGGESTIONS_LOADER,null,this);

to

 if(savedInstanceState==null) {
            Bundle bundle=getArguments();
            mRegion=bundle.getString(getString(R.string.region));

        } else {
            mRegion=savedInstanceState.getString("region");
            x=savedInstanceState.getInt(KEY_POSX);
            y=savedInstanceState.getInt(KEY_POSY);
            Log.d(LOG_TAG,"xRotation:"+savedInstanceState.getInt(KEY_POSX)+",yRotation:"+savedInstanceState.getInt(KEY_POSY));
            getLoaderManager().restartLoader(NEWS_LOADER, null, this);
            getLoaderManager().restartLoader(INDEXES_LOADER, null, this);
            //here added the line


        }

and the leak is misteriously gone. Is there any explanation for that?

Carlos Hernández Gil
  • 1,853
  • 2
  • 22
  • 30
0

I use changeCusror() instead of .swapCursor ; Swapcursor leaves old Cursor open;