-
Notifications
You must be signed in to change notification settings - Fork 174
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[SES-1936] Fix memory leaks #1493
Changes from all commits
75e53c8
c7c0519
90f0cae
c1d82cc
31f4de2
c0128b8
2d7f23a
3533548
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,12 +2,9 @@ package org.thoughtcrime.securesms.home | |
|
||
import android.Manifest | ||
import android.app.NotificationManager | ||
import android.content.BroadcastReceiver | ||
import android.content.ClipData | ||
import android.content.ClipboardManager | ||
import android.content.Context | ||
import android.content.Intent | ||
import android.content.IntentFilter | ||
import android.os.Build | ||
import android.os.Bundle | ||
import android.text.SpannableString | ||
|
@@ -18,13 +15,14 @@ import androidx.core.view.isVisible | |
import androidx.lifecycle.Lifecycle | ||
import androidx.lifecycle.lifecycleScope | ||
import androidx.lifecycle.repeatOnLifecycle | ||
import androidx.localbroadcastmanager.content.LocalBroadcastManager | ||
import androidx.recyclerview.widget.LinearLayoutManager | ||
import androidx.recyclerview.widget.RecyclerView | ||
import dagger.hilt.android.AndroidEntryPoint | ||
import kotlinx.coroutines.Dispatchers | ||
import kotlinx.coroutines.flow.collect | ||
import kotlinx.coroutines.flow.collectLatest | ||
import kotlinx.coroutines.flow.filter | ||
import kotlinx.coroutines.flow.filterNotNull | ||
import kotlinx.coroutines.flow.onEach | ||
import kotlinx.coroutines.launch | ||
import kotlinx.coroutines.withContext | ||
|
@@ -81,7 +79,6 @@ import org.thoughtcrime.securesms.util.IP2Country | |
import org.thoughtcrime.securesms.util.disableClipping | ||
import org.thoughtcrime.securesms.util.push | ||
import org.thoughtcrime.securesms.util.show | ||
import org.thoughtcrime.securesms.util.themeState | ||
import java.io.IOException | ||
import java.util.Locale | ||
import javax.inject.Inject | ||
|
@@ -99,7 +96,6 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), | |
|
||
private lateinit var binding: ActivityHomeBinding | ||
private lateinit var glide: GlideRequests | ||
private var broadcastReceiver: BroadcastReceiver? = null | ||
|
||
@Inject lateinit var threadDb: ThreadDatabase | ||
@Inject lateinit var mmsSmsDatabase: MmsSmsDatabase | ||
|
@@ -205,18 +201,10 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), | |
// Set up empty state view | ||
binding.createNewPrivateChatButton.setOnClickListener { showNewConversation() } | ||
IP2Country.configureIfNeeded(this@HomeActivity) | ||
startObservingUpdates() | ||
|
||
// Set up new conversation button | ||
binding.newConversationButton.setOnClickListener { showNewConversation() } | ||
// Observe blocked contacts changed events | ||
val broadcastReceiver = object : BroadcastReceiver() { | ||
override fun onReceive(context: Context, intent: Intent) { | ||
binding.recyclerView.adapter!!.notifyDataSetChanged() | ||
} | ||
} | ||
this.broadcastReceiver = broadcastReceiver | ||
LocalBroadcastManager.getInstance(this).registerReceiver(broadcastReceiver, IntentFilter("blockedContactsChanged")) | ||
Comment on lines
-213
to
-219
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This piece of code is useless: it doesn't update any data but asking The "blocked contact" will actually be updated through the change to the thread itself. I have tested and it still works without this code here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah, it looked like there's no other usage of |
||
|
||
// subscribe to outdated config updates, this should be removed after long enough time for device migration | ||
lifecycleScope.launch { | ||
|
@@ -227,6 +215,27 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), | |
} | ||
} | ||
|
||
// Subscribe to threads and update the UI | ||
lifecycleScope.launch { | ||
repeatOnLifecycle(Lifecycle.State.STARTED) { | ||
homeViewModel.threads | ||
.filterNotNull() // We don't actually want the null value here as it indicates a loading state (maybe we need a loading state?) | ||
.collectLatest { threads -> | ||
val manager = binding.recyclerView.layoutManager as LinearLayoutManager | ||
val firstPos = manager.findFirstCompletelyVisibleItemPosition() | ||
val offsetTop = if(firstPos >= 0) { | ||
manager.findViewByPosition(firstPos)?.let { view -> | ||
manager.getDecoratedTop(view) - manager.getTopDecorationHeight(view) | ||
} ?: 0 | ||
} else 0 | ||
homeAdapter.data = threads | ||
if(firstPos >= 0) { manager.scrollToPositionWithOffset(firstPos, offsetTop) } | ||
setupMessageRequestsBanner() | ||
updateEmptyState() | ||
} | ||
} | ||
} | ||
|
||
lifecycleScope.launchWhenStarted { | ||
launch(Dispatchers.IO) { | ||
// Double check that the long poller is up | ||
|
@@ -385,52 +394,20 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), | |
ConfigurationMessageUtilities.syncConfigurationIfNeeded(this@HomeActivity) | ||
} | ||
} | ||
|
||
// If the theme hasn't changed then start observing updates again (if it does change then we | ||
// will recreate the activity resulting in it responding to changes multiple times) | ||
if (currentThemeState == textSecurePreferences.themeState() && !homeViewModel.getObservable(this).hasActiveObservers()) { | ||
startObservingUpdates() | ||
} | ||
} | ||
|
||
override fun onPause() { | ||
super.onPause() | ||
ApplicationContext.getInstance(this).messageNotifier.setHomeScreenVisible(false) | ||
|
||
homeViewModel.getObservable(this).removeObservers(this) | ||
} | ||
|
||
override fun onDestroy() { | ||
val broadcastReceiver = this.broadcastReceiver | ||
if (broadcastReceiver != null) { | ||
LocalBroadcastManager.getInstance(this).unregisterReceiver(broadcastReceiver) | ||
} | ||
super.onDestroy() | ||
EventBus.getDefault().unregister(this) | ||
} | ||
// endregion | ||
|
||
// region Updating | ||
private fun startObservingUpdates() { | ||
homeViewModel.getObservable(this).observe(this) { newData -> | ||
val manager = binding.recyclerView.layoutManager as LinearLayoutManager | ||
val firstPos = manager.findFirstCompletelyVisibleItemPosition() | ||
val offsetTop = if(firstPos >= 0) { | ||
manager.findViewByPosition(firstPos)?.let { view -> | ||
manager.getDecoratedTop(view) - manager.getTopDecorationHeight(view) | ||
} ?: 0 | ||
} else 0 | ||
homeAdapter.data = newData | ||
if(firstPos >= 0) { manager.scrollToPositionWithOffset(firstPos, offsetTop) } | ||
setupMessageRequestsBanner() | ||
updateEmptyState() | ||
} | ||
|
||
ApplicationContext.getInstance(this@HomeActivity).typingStatusRepository.typingThreads.observe(this) { threadIds -> | ||
homeAdapter.typingThreadIDs = (threadIds ?: setOf()) | ||
} | ||
Comment on lines
-429
to
-431
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have moved this change detection into |
||
} | ||
|
||
private fun updateEmptyState() { | ||
val threadCount = (binding.recyclerView.adapter)!!.itemCount | ||
binding.emptyStateContainer.isVisible = threadCount == 0 && binding.recyclerView.isVisible | ||
|
@@ -441,7 +418,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), | |
if (event.recipient.isLocalNumber) { | ||
updateProfileButton() | ||
} else { | ||
homeViewModel.tryUpdateChannel() | ||
homeViewModel.tryReload() | ||
} | ||
} | ||
|
||
|
@@ -612,7 +589,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), | |
private fun setConversationPinned(threadId: Long, pinned: Boolean) { | ||
lifecycleScope.launch(Dispatchers.IO) { | ||
storage.setPinned(threadId, pinned) | ||
homeViewModel.tryUpdateChannel() | ||
homeViewModel.tryReload() | ||
} | ||
} | ||
|
||
|
@@ -688,7 +665,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), | |
button(R.string.yes) { | ||
textSecurePreferences.setHasHiddenMessageRequests() | ||
setupMessageRequestsBanner() | ||
homeViewModel.tryUpdateChannel() | ||
homeViewModel.tryReload() | ||
} | ||
button(R.string.no) | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove and inline?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This actually can't be inlined. As the syntax
val appContext = applicationContext
is equivalent toval appContext = this.getApplicationContext()
, if you moveapplicationContext
into the lambda, you movethis.getApplicationContext()
there hence movingthis
into the lambda, wherethis
is where the memory leak comes fromThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
looks like
loadFileContents
is used once, synchronously in the next line, soActivity Context
was fine.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you look at
MnemonicCodec.encode
, it creates a newLanguage
and callsLanguage.loadWordSet
, whereLanguage
instance is stored in static cache, in whichloadFileContents
is a field of.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One of those dodgy "Putting everything you can think of into cache" scenarios