CursorAdapter MemoryLeak

时间:2015-08-19 20:41:23

标签: android memory-leaks android-cursoradapter

更新 我解决了我的问题,但无法弄清楚导致它的原因。我发现问题出现在查询数据库的AsyncTask上。我删除了类并实现了CursorLoader。 Sayonara记忆泄漏。 我一直在维护这个问题,因为我无法发现造成问题的原因。

我多次检查了我的活动和我的适配器,无法找到臭名昭着的泄漏。我在MAT中分析了哑巴并得出结论,Leak在ItemHolder的ImageView中,可能在Adapter中。我对MAT不是很熟悉,也无法取得更多进展。

我尝试了一切(至少我是这么认为的)。 WeakReferences,EventBus,清理数据,但Leak仍然存在,而且由于我处理多个ImageView,问题非常严重。

谢谢!

活动

public class PhotoListActivity extends AppCompatActivity{

private static final String TAG = "PhotoListActivity";

private static final int REQUEST_IMAGE_CAP  = 111;

private String              mCurrentPhotoPath;
private CoordinatorLayout   coordinatorLayout;
private ProgressBar         progressBar;

private GridView grid;
private GridAdapter gridAdapter;

@Override
protected void onStart() {
    super.onStart();
    EventBus.getDefault().register(this);
}

@Override
protected void onStop() {
    super.onStop();
    gridAdapter.swapCursor(null);
    grid = null;
    gridAdapter = null;
    if ( loadPhotos != null )
    {
        if ( loadPhotos.getStatus() == AsyncTask.Status.RUNNING )
            loadPhotos.cancel( true );
        loadPhotos = null;
    }
    EventBus.getDefault().unregister(this);
}

@Override
protected void onPause() {
    if ( loadPhotos != null )
    {
        if ( loadPhotos.getStatus() == AsyncTask.Status.RUNNING )
            loadPhotos.cancel( true );
        loadPhotos = null;
    }

    super.onPause();
}

@Override
protected void onDestroy() {
    super.onDestroy();
}

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_photo_list);

    coordinatorLayout   = (CoordinatorLayout) findViewById(R.id.coordinatorlayout);
    progressBar         = (ProgressBar) findViewById(R.id.loader);

    gridAdapter         = new GridAdapter(this, null);
    grid                = (GridView) findViewById(R.id.grid);
    grid.setAdapter( gridAdapter);

    // Loading Photos
    loadPhotos();

    if ( savedInstanceState != null )
    {
        alarmSet = savedInstanceState.getBoolean(STATE_ALARM);
        alarmCanceled   = savedInstanceState.getBoolean(STATE_CANCELED);
    }
    if ( !alarmCanceled )
        setAlarm();

}

private AlarmManager mAlarmManager;
private Intent mNotificationReceiverIntent;
private PendingIntent mNotificationReceiverPendingIntent;
private static final long INITIAL_ALARM_DELAY = 2 * 60 * 1000L;

private boolean alarmSet;
private boolean alarmCanceled;

private void setAlarm()
{
    mAlarmManager   = (AlarmManager) getSystemService( ALARM_SERVICE );

    mNotificationReceiverIntent = new Intent(
            getApplicationContext(), AlarmReceiver.class
    );
    mNotificationReceiverPendingIntent  = PendingIntent.getBroadcast(
            getApplicationContext(), 0, mNotificationReceiverIntent, PendingIntent.FLAG_CANCEL_CURRENT
    );

    // Set repeating Alarm
    mAlarmManager.setRepeating(
            AlarmManager.RTC_WAKEUP,
            System.currentTimeMillis(),
            INITIAL_ALARM_DELAY,
            mNotificationReceiverPendingIntent
    );
    alarmSet    = true;
    alarmCanceled = false;

    Log.d(TAG, "Alarm set");

}

private void cancelAlarm()
{
    Log.d(TAG, "Alarm Canceled");

    mAlarmManager.cancel(mNotificationReceiverPendingIntent);
    alarmSet = false;
    alarmCanceled = true;
    Snackbar.make(
            coordinatorLayout,
            "Alarm Canceled", Snackbar.LENGTH_LONG)
            .setAction("UNDO", new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    setAlarm();
                }
            }).show();
}


QueryPhotos loadPhotos;
private void loadPhotos()
{
    loadPhotos = new QueryPhotos(new WeakReference<Context>(getApplicationContext()));
    loadPhotos.execute();
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {

    // save and process photo
    if ( requestCode == REQUEST_IMAGE_CAP && resultCode == RESULT_OK )
    {
        // save image on DB
        saveCurrentPhoto();
    }

    super.onActivityResult(requestCode, resultCode, data);
}

/**
 * Insert photo taken in the DB
 */
private void saveCurrentPhoto()
{
    String photoName    = mCurrentPhotoPath.substring(mCurrentPhotoPath.lastIndexOf("/") + 1);
    String photoDate    = SimpleDateFormat.getDateInstance().format(new Date());

    mCurrentPhotoPath   = null;
}

/**
 * Receive call from InsertPhoto onPostExecute
 * @param id    New photo DB record
 */
public void photoInserted(long id) {
    Log.d(TAG, "DB Photo ID " + id );
    // TODO update List

    // Feedback user
    Snackbar
            .make(coordinatorLayout, "Photo Saved! ID: " + id, Snackbar.LENGTH_SHORT)
            .show();
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    // Inflate the menu; this adds items to the action bar if it is present.
    getMenuInflater().inflate(R.menu.menu_photo_list, menu);
    return true;
}

@Override
public boolean onPrepareOptionsMenu(Menu menu) {
    if ( alarmSet )
    {
        menu.findItem(R.id.action_cancelalarm).setEnabled(true);
        menu.findItem(R.id.action_setalarm).setEnabled(false);
    } else {
        menu.findItem(R.id.action_cancelalarm).setEnabled(false);
        menu.findItem(R.id.action_setalarm).setEnabled(true);
    }
    return super.onPrepareOptionsMenu(menu);
}

@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();

    switch ( id ) {
        case R.id.action_photo: {
            takePicture();
            return true;
        }
        case R.id.action_setalarm: {
            setAlarm();
            return true;
        }
        case R.id.action_cancelalarm:
            cancelAlarm();
            return true;
    }

    return super.onOptionsItemSelected(item);
}

private static final String STATE_ALARM = "state_alarm";
private static final String STATE_CANCELED  = "alarm_canceled";
@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putBoolean(STATE_ALARM, alarmSet );
    outState.putBoolean( STATE_CANCELED, alarmCanceled );
}

/**
 * Open a Photo App and return the result
 */
private void takePicture() {
    Intent takePicIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    if ( takePicIntent.resolveActivity(getPackageManager()) != null )
    {
        File imageFile  = createPhotoFile();
        if ( imageFile != null ) {
            takePicIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(imageFile));
            startActivityForResult(takePicIntent, REQUEST_IMAGE_CAP);
        }

    }
}

/**
 * Create a File image with a safe name
 * @return  File obj to save the Photo
 */
private File createPhotoFile() {
    String timeStamp     = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
    String imageFileName = "JPG_" + timeStamp;


    String externalStorage = Environment.getExternalStorageDirectory().toString();
    File storageDir = new File(externalStorage + "/DailySelfie");
    if ( !storageDir.exists() )
        storageDir.mkdir();
    try {
        File imageFile  = File.createTempFile(
                imageFileName,
                ".jpg",
                storageDir
        );
        mCurrentPhotoPath    = imageFile.getAbsolutePath();

        return imageFile;

    } catch (IOException e) {
        Log.e(TAG, "error createPhotoFile e:" + e.getMessage());
        return null;
    }

}


private void openDetail(int id, int position)
{
    Intent openDetail   = new Intent(PhotoListActivity.this, DetailActivity.class );
    openDetail.putExtra( DetailActivity.EXTRA_POSITION, position);
    startActivity( openDetail );
}


public void onEvent(QueryPhotos.QueryEvent queryEvent) {
    gridAdapter.swapCursor(queryEvent.getCursor());
}
public void onEvent(QueryPhotos.ProgressbarStatus visible) {
    if ( visible.isVisible() )
    {
        progressBar.setVisibility(View.VISIBLE);
    } else {
        progressBar.setVisibility(View.GONE);
    }

}

public void onEvent(SelfiesAdapter.OnSelfieClick selfieClick) {
    Log.d(TAG, "onSelfieClick selfie id: " + selfieClick.getSelfie().getId());
    // TODO Open Photo detail
    openDetail(  selfieClick.getSelfie().getId(), selfieClick.getPosition() );
}

public void onEvent(final SelfiesAdapter.OnSelfieLongClick selfieLongClick ) {
    final Selfie selfie = selfieLongClick.getSelfie();
    Log.d(TAG, "onSelfieLongClick selfie id: " + selfie.getId() );
    // open dialog to delete Selfie
    final AlertDialog.Builder deleteSelfie    = new AlertDialog.Builder( this );
    deleteSelfie.setTitle( "Do you want to delete Selfie " + selfie.getId() + " ?");
    deleteSelfie.setMessage(
            "Photo taken " + selfie.getDate()
    );
    deleteSelfie.setPositiveButton("Delete", new DialogInterface.OnClickListener() {
        @Override
        public void onClick(DialogInterface dialog, int which) {
            // Delete Selfie
            Log.d(TAG, " Delete Selfie " + selfie.getId());
           /* DeleteSelfie delete = new DeleteSelfie( selfie, selfieLongClick.getPosition() );
            delete.execute();*/
        }
    });

    deleteSelfie.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
        @Override
        public void onClick(DialogInterface dialog, int which) {
            dialog.dismiss();
        }
    });
    deleteSelfie.create().show();
}
}

适配器

public class GridAdapter extends CursorAdapter {

private static final String TAG = "GridAdapter";

private static class ViewHolder {
    private ImageView imgThumb;
    private TextView txtName;
    private ProgressBar loader;
    private RelativeLayout container;
}

private LayoutInflater inflater;

public GridAdapter(Context context, Cursor c) {
    super(context, c);
    this.inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}

public GridAdapter(Context context, Cursor c, boolean autoRequery) {
    super(context, c, autoRequery);
}

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


@Override
public void bindView(View view, Context context, Cursor cursor) {

    final Selfie selfie = new Selfie(cursor, cursor.getPosition());
    final ViewHolder holder = (ViewHolder) view.getTag();
    holder.txtName.setText(selfie.getName());

    // create thumbnail
    final ImageLoader imageLoader = ImageLoader.getInstance();

    final ImageLoadingListener imageLoadingListener = new ImageLoadingListener() {
        @Override
        public void onLoadingStarted(String imageUri, View view) {

        }

        @Override
        public void onLoadingFailed(String imageUri, View view, FailReason failReason) {

        }

        @Override
        public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
            holder.imgThumb.setImageBitmap(loadedImage);
            holder.loader.setVisibility(View.GONE);
            ((ImageView) view).setImageBitmap(loadedImage);
        }

        @Override
        public void onLoadingCancelled(String imageUri, View view) {

        }
    };
    imageLoader.displayImage(
            "file://" + selfie.getPath(),
            holder.imgThumb,
            null,
            imageLoadingListener,
            null);
}

@Override
public View newView(Context context, final Cursor cursor, ViewGroup parent) {
    View itemView = inflater.inflate(R.layout.viewholder_photolist, null);

    final ViewHolder holder = new ViewHolder();
    holder.container = (RelativeLayout) itemView.findViewById(R.id.container);
    holder.imgThumb = (ImageView) itemView.findViewById(R.id.img_thumbnail);
    holder.txtName = (TextView) itemView.findViewById(R.id.txt_name);
    holder.loader = (ProgressBar) itemView.findViewById(R.id.loader);

    itemView.setTag(holder);

    return itemView;
}
}

QueryPhotos

public class QueryPhotos extends AsyncTask<Void, Void, Cursor>
{
private static final String TAG = "QueryPhotos";

private DBSchema mDBHelper;
private SQLiteDatabase db;
private String[]        cols = {
        DBSchema.TB_PHOTO.ID,
        DBSchema.TB_PHOTO.NAME,
        DBSchema.TB_PHOTO.PATH_FULL,
        DBSchema.TB_PHOTO.DATE_CREATED
};
private String          orderBy = DBSchema.TB_PHOTO.ID +" ASC";

public QueryPhotos( WeakReference<Context> context ) {
    mDBHelper   = new DBSchema(context.get());
    db          = mDBHelper.getReadableDatabase();

}

@Override
protected Cursor doInBackground(Void... params) {
    try {
        publishProgress();

        return db.query( DBSchema.TABLE_PHOTOS,cols, null, null, null, null, orderBy, null );
    } catch (Exception e) {
        Log.e(TAG, "LoadPhotos query error e:" + e.getMessage());
        return null;
    }
}

@Override
protected void onProgressUpdate(Void... values) {
    // Loader
    EventBus.getDefault().post(new ProgressbarStatus(false));
    super.onProgressUpdate(values);
}

@Override
protected void onPostExecute(Cursor cursor) {
    if ( null != cursor )
    {
        // Realod Photo List
        Log.d(TAG, "LoadPhotos done");

        // TODO result via Event.Bus
        EventBus.getDefault().post(new ProgressbarStatus(false));
        EventBus.getDefault().post(new QueryEvent(cursor));
    }

    super.onPostExecute(cursor);
}

public class QueryEvent {
    private Cursor cursor;

    public Cursor getCursor() {
        return cursor;
    }

    public QueryEvent(Cursor cursor) {
        this.cursor = cursor;
    }
}

public class ProgressbarStatus {
    private boolean visible;

    public boolean isVisible() {
        return visible;
    }

    public ProgressbarStatus(boolean visible) {
        this.visible = visible;
    }
}
}

MAT分析 Problem Suspect

支配树 Dominator Tree

GC根源的路径,不包括WeakReferences enter image description here

0 个答案:

没有答案