更新 我解决了我的问题,但无法弄清楚导致它的原因。我发现问题出现在查询数据库的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;
}
}
}