While switching from LogInActivity
to CatalogActivity
twice and finally returning to LogInActivity
I have noticed through the Memory Monitor that GC
is not de-allocating all the memory it should, therefore a leak is present.
When the app started ( atLogInActivity
) the initial allocated memory was 40MB and as I cycled through the mentioned Activities, the final allocated memory was around 60MB ( atLogInActivity
).
Therefore around 20MB of precious memory remain allocated and used without purpose, this obviously being caused by a memory leak.
With the help of this post, while investigating using the CLI command:
adb shell dumpsys meminfo my_package_name
I noticed that after the Activities were cycled through, there were 3 instances of Activities and more of Context, as you can see in this picture
As this answer suggested, I used
intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
but the leak still occurred.
What can be causing my memory leaks? Are the Activities indeed leaking? And how can I fix it?
LogInActivity
public class LogInActivity extends AppCompatActivity {
protected EditText emailEditText;
protected EditText passwordEditText;
protected Button logInButton;
protected TextView signUpTextView;
private FirebaseAuth mFirebaseAuth;
private CallbackManager callbackManager;
public static final String TAG = "LogInActivity";
private DatabaseReference mBookRef;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_log_in);
if (CheckConnection.isNetworkConnected(this)) {
// Initialize FirebaseAuth
mFirebaseAuth = FirebaseAuth.getInstance();
mBookRef = FirebaseDatabase
.getInstance()
.getReference();
signUpTextView = (TextView) findViewById(R.id.signUpText);
emailEditText = (EditText) findViewById(R.id.emailField);
passwordEditText = (EditText) findViewById(R.id.passwordField);
logInButton = (Button) findViewById(R.id.loginButton);
logInButton.setTransformationMethod(null);
callbackManager = CallbackManager.Factory.create();
LoginButton loginButton = (LoginButton) findViewById(R.id.facebook_login_button);
loginButton.setReadPermissions(Arrays.asList("email", "user_hometown", "public_profile"));
// Register your callback//
loginButton.registerCallback(callbackManager,
// If the login attempt is successful, then call onSuccess and pass the LoginResult//
new FacebookCallback<LoginResult>() {
@Override
public void onSuccess(LoginResult loginResult) {
// Print the user’s ID and the Auth Token to Android Studio’s Logcat Monitor//
Log.d(TAG, "User ID: " +
loginResult.getAccessToken().getUserId() + "\n" +
"Auth Token: " + loginResult.getAccessToken().getToken());
signInWithFacebook(loginResult, loginResult.getAccessToken());
}
// If the user cancels the login, then call onCancel//
@Override
public void onCancel() {
}
// If an error occurs, then call onError//
@Override
public void onError(FacebookException exception) {
}
});
signUpTextView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(LogInActivity.this, SignUpActivity.class);
startActivity(intent);
}
});
logInButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String email = emailEditText.getText().toString();
String password = passwordEditText.getText().toString();
email = email.trim();
password = password.trim();
if (email.isEmpty() || password.isEmpty()) {
AlertDialog.Builder builder = new AlertDialog.Builder(LogInActivity.this);
builder.setMessage(R.string.login_error_message)
.setTitle(R.string.login_error_title)
.setPositiveButton(android.R.string.ok, null);
AlertDialog dialog = builder.create();
dialog.show();
} else {
mFirebaseAuth.signInWithEmailAndPassword(email, password)
.addOnCompleteListener(LogInActivity.this, new OnCompleteListener<AuthResult>() {
@SuppressWarnings("ThrowableResultOfMethodCallIgnored")
@Override
public void onComplete(@NonNull Task<AuthResult> task) {
if (task.isSuccessful()) {
Intent intent = new Intent(LogInActivity.this, CatalogActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(intent);
} else {
AlertDialog.Builder builder = new AlertDialog.Builder(LogInActivity.this);
if (task.getException() != null) {
builder.setMessage(task.getException().getMessage())
.setTitle(R.string.login_error_title)
.setPositiveButton(android.R.string.ok, null);
AlertDialog dialog = builder.create();
dialog.show();
}
}
}
});
}
}
});
} else {
AlertDialog.Builder builder = new AlertDialog.Builder(LogInActivity.this, R.style.MyDialogTheme);
builder.setMessage("Please check your internet connection and retry!")
.setTitle(R.string.login_error_title).setCancelable(false)
.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Intent intent = getIntent();
finish();
startActivity(intent);
}
});
AlertDialog dialog = builder.create();
dialog.show();
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
callbackManager.onActivityResult(requestCode, resultCode, data);
}
private void signInWithFacebook(final LoginResult loginResulted, AccessToken token) {
Log.d(TAG, "signInWithFacebook:" + token);
AuthCredential credential = FacebookAuthProvider.getCredential(token.getToken());
mFirebaseAuth.signInWithCredential(credential)
.addOnCompleteListener(this, new OnCompleteListener<AuthResult>() {
@Override
public void onComplete(@NonNull Task<AuthResult> task) {
Log.d(TAG, "signInWithCredential:onComplete:" + task.isSuccessful());
// If sign in fails, display a message to the user. If sign in succeeds
// the auth state listener will be notified and logic to handle the
// signed in user can be handled in the listener.
if (!task.isSuccessful()) {
Log.w(TAG, "signInWithCredential", task.getException());
Toast.makeText(LogInActivity.this, "Authentication failed.",
Toast.LENGTH_SHORT).show();
} else {
final String uid = task.getResult().getUser().getUid();
String userName = task.getResult().getUser().getDisplayName();
GraphRequest request = GraphRequest.newMeRequest(
loginResulted.getAccessToken(),
new GraphRequest.GraphJSONObjectCallback() {
@Override
public void onCompleted(JSONObject object, GraphResponse response) {
Log.v("LoginActivity", response.toString());
// GET TOWN AND WRITE ON FIREBASE DATABASE
try {
JSONObject hometown = object.getJSONObject("hometown");
String town = hometown.getString("name");
mBookRef.child("user-books").child(uid).child("userLocation").setValue(town);
} catch (JSONException e) {
Log.e("BOOK_TRADE", "UNEXPECTED JSON EXCEPTION", e);
}
}
});
Bundle parameters = new Bundle();
parameters.putString("fields", "name,hometown");
request.setParameters(parameters);
request.executeAsync();
mBookRef.child("user-books").child(uid).child("userName").setValue(userName);
Intent intent = new Intent(LogInActivity.this, CatalogActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
startActivity(intent);
finish();
}
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
unbindDrawables(findViewById(R.id.activity_log_in_root_view));
System.gc();
}
private void unbindDrawables(View view) {
if (view.getBackground() != null)
view.getBackground().setCallback(null);
if (view instanceof ImageView) {
ImageView imageView = (ImageView) view;
imageView.setImageBitmap(null);
} else if (view instanceof ViewGroup) {
ViewGroup viewGroup = (ViewGroup) view;
for (int i = 0; i < viewGroup.getChildCount(); i++)
unbindDrawables(viewGroup.getChildAt(i));
if (!(view instanceof AdapterView))
viewGroup.removeAllViews();
}
}
}
CatalogActivity
public class CatalogActivity extends AppCompatActivity {
private ViewPager mViewPager;
private SectionsPageAdapter mSectionsPageAdapter;
private FirebaseAuth mFirebaseAuth;
private Context context;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
context = this;
overridePendingTransition(R.anim.slidein, R.anim.slideout);
setContentView(R.layout.activity_catalog);
mFirebaseAuth = FirebaseAuth.getInstance();
mSectionsPageAdapter = new SectionsPageAdapter(getSupportFragmentManager());
mViewPager = (ViewPager) findViewById(R.id.container);
setupViewPager(mViewPager);
TabLayout tabLayout = (TabLayout) findViewById(R.id.tabs);
tabLayout.setupWithViewPager(mViewPager);
int tabIcon1 = R.drawable.ic_collections_24dp;
int tabIcon2 = R.drawable.ic_book_black_24dp;
int tabIcon3 = R.drawable.ic_chat__24dp;
if (tabLayout.getTabAt(0) != null) {
tabLayout.getTabAt(0).setIcon(tabIcon1);
}
if (tabLayout.getTabAt(1) != null) {
tabLayout.getTabAt(1).setIcon(tabIcon2);
tabLayout.getTabAt(1).getIcon().setAlpha(128);
}
if (tabLayout.getTabAt(2) != null) {
tabLayout.getTabAt(2).setIcon(tabIcon3);
tabLayout.getTabAt(2).getIcon().setAlpha(128);
}
tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
int tabIconColor = ContextCompat.getColor(context, R.color.colorAccent);
if (tab.getIcon() != null) {
tab.getIcon().setColorFilter(tabIconColor, PorterDuff.Mode.SRC_IN);
tab.getIcon().setAlpha(255);
}
}
@Override
public void onTabUnselected(TabLayout.Tab tab) {
int tabIconColor = ContextCompat.getColor(context, R.color.grey);
if (tab.getIcon() != null) {
tab.getIcon().setColorFilter(tabIconColor, PorterDuff.Mode.SRC_IN);
tab.getIcon().setAlpha(128);
}
}
@Override
public void onTabReselected(TabLayout.Tab tab) {
}
});
ViewServer.get(this).addWindow(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
ViewServer.get(this).removeWindow(this);
}
private void setupViewPager(ViewPager viewPager) {
SectionsPageAdapter adapter = new SectionsPageAdapter(getSupportFragmentManager());
adapter.addFragment(new CatalogFragment());
adapter.addFragment(new MyBooksfragment());
adapter.addFragment(new MyChatsFragment());
viewPager.setAdapter(adapter);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.chat_menu, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.sign_out_menu) {
LoginManager.getInstance().logOut();
mFirebaseAuth.signOut();
loadLogInView();
}
return super.onOptionsItemSelected(item);
}
private void loadLogInView() {
Intent intent = new Intent(this, LogInActivity.class);
// intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(intent);
}
}
Note: I use this (unorthodox) method to clean any bitmap-related memory leaks in my fragments:
private void unbindDrawables(View view) {
if (view.getBackground() != null)
view.getBackground().setCallback(null);
if (view instanceof ImageView) {
ImageView imageView = (ImageView) view;
imageView.setImageBitmap(null);
} else if (view instanceof ViewGroup) {
ViewGroup viewGroup = (ViewGroup) view;
for (int i = 0; i < viewGroup.getChildCount(); i++)
unbindDrawables(viewGroup.getChildAt(i));
if (!(view instanceof AdapterView))
viewGroup.removeAllViews();
}
}