I am developing an Android app that primarily focuses on:
Fetching details from an API.
Receiving updates via MQTT.
Displaying content based on the received data, including:
Videos using ExoPlayer .
Images using Glide .
Websites and YouTube videos using WebView .
Sending displayed content details to OpenSearch.
When the app starts, the memory usage is around 200 MB as observed in the Android Profiler. However, after running the app for several hours, the memory usage keeps increasing. The profiler shows no memory leaks (0 leaks
), but the "Others" category in the memory graph continues to grow over time. I have attached the dominator tree from the heap dump which I have taken after my app running for 3 hours.
How can I identify what is causing the increase in the "Others" category in the Android Profiler?
Are there any best practices for managing memory in apps that use ExoPlayer, Glide, WebView, and MQTT?
Could this issue be related to native memory allocations (e.g., bitmaps, file handles) rather than Java heap memory?
Any guidance or suggestions on how to debug and resolve this issue would be greatly appreciated!
@SuppressLint("SetTextI18n", "SetJavaScriptEnabled")
@OptIn(UnstableApi::class)
fun startLoop() {
// If this partition has video content initially, set up ExoPlayer and attach PlayerView
if (hasVideoInitially) {
val loadControl = DefaultLoadControl.Builder()
.setBufferDurationsMs(
2000, 2000, 1000, 2000
)
.build()
val renderersFactory = DefaultRenderersFactory(context)
.setEnableDecoderFallback(true)
exoPlayer =
ExoPlayer.Builder(context, renderersFactory)
.setLoadControl(loadControl)
.build()
playerView = PlayerView(context).apply {
useController = false
setShutterBackgroundColor(Color.TRANSPARENT)
layoutParams = FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT
)
}
playerView?.player = exoPlayer
partitionContainer.addView(playerView)
}
// Create an ImageView for images/PDF
imageView = ImageView(context).apply {
layoutParams = FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT
)
scaleType = ImageView.ScaleType.FIT_XY
visibility = GONE
}
partitionContainer.addView(imageView)
// Create a WebView for HTML content
webView = WebView(context).apply {
layoutParams = FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT
)
visibility = GONE
settings.javaScriptEnabled = true
settings.domStorageEnabled = true
settings.cacheMode = WebSettings.LOAD_NO_CACHE
settings.mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW
WebView.setWebContentsDebuggingEnabled(true)
}
partitionContainer.addView(webView)
// Create a "No Content" TextView but hide it initially
noContentTextView = TextView(context).apply {
text = "No content to play."
textSize = 18f
setTextColor(Color.CYAN)
gravity = Gravity.CENTER
visibility = GONE
}
partitionContainer.addView(noContentTextView)
// Kick off the main loop
scope.launch {
while (isActive) {
try {
// Take a "snapshot" of the current contents under the lock
val snapshot = contentLock.withLock { contentList }
if (snapshot.isEmpty()) {
// If we've *never* played content before, it means we're just waiting for content
// so do nothing special. If we have played content before, show "No content."
if (hasEverPlayedContent) {
// Show the "No content" message
noContentTextView?.visibility = VISIBLE
noContentTextView?.bringToFront()
}
delay(2000)
} else if (snapshot.size == 1 && snapshot[0].contentType.lowercase(Locale.ROOT) == "widget") {
// Display the widget once
displayWidgetIndefinitely(snapshot[0])
// Then break out of the while loop or block the loop.
break
} else {
// Hide "No content" message if it was visible
noContentTextView?.visibility = GONE
// Loop over the snapshot
for (item in snapshot) {
if (!isActive) break // If our scope got canceled mid-loop
if (!hasFiredFirstContentPlayed) {
hasFiredFirstContentPlayed = true
onFirstContentPlayed?.onFirstContentPlayed()
}
hasEverPlayedContent = true
displayContent(item)
}
}
} catch (e: Exception) {
FirebaseCrashlytics.getInstance().recordException(e)
}
}
}
}
I am developing an Android app that primarily focuses on:
Fetching details from an API.
Receiving updates via MQTT.
Displaying content based on the received data, including:
Videos using ExoPlayer .
Images using Glide .
Websites and YouTube videos using WebView .
Sending displayed content details to OpenSearch.
When the app starts, the memory usage is around 200 MB as observed in the Android Profiler. However, after running the app for several hours, the memory usage keeps increasing. The profiler shows no memory leaks (0 leaks
), but the "Others" category in the memory graph continues to grow over time. I have attached the dominator tree from the heap dump which I have taken after my app running for 3 hours.
How can I identify what is causing the increase in the "Others" category in the Android Profiler?
Are there any best practices for managing memory in apps that use ExoPlayer, Glide, WebView, and MQTT?
Could this issue be related to native memory allocations (e.g., bitmaps, file handles) rather than Java heap memory?
Any guidance or suggestions on how to debug and resolve this issue would be greatly appreciated!
@SuppressLint("SetTextI18n", "SetJavaScriptEnabled")
@OptIn(UnstableApi::class)
fun startLoop() {
// If this partition has video content initially, set up ExoPlayer and attach PlayerView
if (hasVideoInitially) {
val loadControl = DefaultLoadControl.Builder()
.setBufferDurationsMs(
2000, 2000, 1000, 2000
)
.build()
val renderersFactory = DefaultRenderersFactory(context)
.setEnableDecoderFallback(true)
exoPlayer =
ExoPlayer.Builder(context, renderersFactory)
.setLoadControl(loadControl)
.build()
playerView = PlayerView(context).apply {
useController = false
setShutterBackgroundColor(Color.TRANSPARENT)
layoutParams = FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT
)
}
playerView?.player = exoPlayer
partitionContainer.addView(playerView)
}
// Create an ImageView for images/PDF
imageView = ImageView(context).apply {
layoutParams = FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT
)
scaleType = ImageView.ScaleType.FIT_XY
visibility = GONE
}
partitionContainer.addView(imageView)
// Create a WebView for HTML content
webView = WebView(context).apply {
layoutParams = FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT
)
visibility = GONE
settings.javaScriptEnabled = true
settings.domStorageEnabled = true
settings.cacheMode = WebSettings.LOAD_NO_CACHE
settings.mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW
WebView.setWebContentsDebuggingEnabled(true)
}
partitionContainer.addView(webView)
// Create a "No Content" TextView but hide it initially
noContentTextView = TextView(context).apply {
text = "No content to play."
textSize = 18f
setTextColor(Color.CYAN)
gravity = Gravity.CENTER
visibility = GONE
}
partitionContainer.addView(noContentTextView)
// Kick off the main loop
scope.launch {
while (isActive) {
try {
// Take a "snapshot" of the current contents under the lock
val snapshot = contentLock.withLock { contentList }
if (snapshot.isEmpty()) {
// If we've *never* played content before, it means we're just waiting for content
// so do nothing special. If we have played content before, show "No content."
if (hasEverPlayedContent) {
// Show the "No content" message
noContentTextView?.visibility = VISIBLE
noContentTextView?.bringToFront()
}
delay(2000)
} else if (snapshot.size == 1 && snapshot[0].contentType.lowercase(Locale.ROOT) == "widget") {
// Display the widget once
displayWidgetIndefinitely(snapshot[0])
// Then break out of the while loop or block the loop.
break
} else {
// Hide "No content" message if it was visible
noContentTextView?.visibility = GONE
// Loop over the snapshot
for (item in snapshot) {
if (!isActive) break // If our scope got canceled mid-loop
if (!hasFiredFirstContentPlayed) {
hasFiredFirstContentPlayed = true
onFirstContentPlayed?.onFirstContentPlayed()
}
hasEverPlayedContent = true
displayContent(item)
}
}
} catch (e: Exception) {
FirebaseCrashlytics.getInstance().recordException(e)
}
}
}
}
Share
Improve this question
edited Mar 7 at 10:27
karthik vs
asked Mar 7 at 7:36
karthik vskarthik vs
12 bronze badges
2
- what view is you show content? recyclerview or ohter? Did you called Exoplayer.release() and Webview.destory()? – 卡尔斯路西法 Commented Mar 7 at 9:14
- I show content from list of contents in Loop. – karthik vs Commented Mar 7 at 10:14
1 Answer
Reset to default 0well,the partitionContainer
view only addView
and not called removeView
,
jvm can't recycle object.
if constantly addview ,the memory usage wall continue to increase.
if you called removeView
in other function,did you called Exoplayer.release() and Webview.destory() while call removeView
?
I can't foound Other type in your picture,and can you add app starts memory usage picture?