Common Memory Leaks in Wiki Apps: Causes and Fixes

Wiki apps accumulate memory pressure differently than typical CRUD applications. The core problem: unbounded object graphs tied to navigation history and content rendering. Three architectural pattern

May 24, 2026 · 4 min read · Common Issues

What Causes Memory Leaks in Wiki Apps

Wiki apps accumulate memory pressure differently than typical CRUD applications. The core problem: unbounded object graphs tied to navigation history and content rendering. Three architectural patterns create the majority of leaks:

1. Fragment/Activity retention via static references — Wiki apps often cache rendered HTML, parsed wikitext ASTs, or image bitmaps in singleton ContentCache or ImageLoader instances. When a Fragment holding a WebView or RecyclerView gets destroyed but the cache retains a reference to its Context or View, the entire view hierarchy leaks.

2. Listener/observer registration without cleanup — Real-time collaboration features (WebSocket listeners, Firebase ValueEventListener, RxJava Disposable) frequently outlive their UI scope. A PageEditFragment registering a TextWatcher on a shared DocumentModel but never unregistering on onDestroyView() keeps the fragment alive.

3. WebView JavaScript bridge leaks — Wiki apps rendering content via WebView with @JavascriptInterface callbacks create cross-language reference cycles. The WebView holds a reference to the Java object; the Java object holds a closure referencing the Activity. Neither GC can collect the cycle.

4. Image pipeline bitmap pooling misuse — Glide/Coil/Fresco bitmap pools are designed for reuse, but wiki apps loading high-resolution diagrams (PlantUML, Mermaid, math formulas) often disable pooling or set inBitmap constraints incorrectly, causing native memory bloat that doesn't show in Java heap.

---

Real-World Impact

MetricTypical Wiki App BaselineWith Unfixed Leaks
Crash-free sessions (Play Console)99.2%94–96% (OOM crashes)
Avg. session length12 min4–6 min (app killed by LMK)
1-star "crash" reviews / month2–315–40
DAU retention (day 7)38%22%
Support tickets / 1k users318

Users don't report "memory leak." They report: "App closes when I open 5 pages," "Scrolling lags after 10 minutes," "Phone gets hot reading long articles." On Play Store, these become "keeps crashing" 1-star reviews. For subscription-based wikis (Notion-style, Confluence Cloud mobile), each 1% retention drop = ~$12k–$45k MRR depending on tier.

---

5–7 Specific Manifestations in Wiki Apps

1. WebView History Stack Retention

Symptom: Opening 20+ articles via internal links → OOM on 21st.

Root cause: WebView maintains full forward/back history with rendered DOM snapshots. clearHistory() doesn't release native memory.

Detection: adb shell dumpsys meminfo shows dalvik-webview native heap growing 15–30 MB per navigation.

2. ImageSpan / DynamicDrawableSpan Leak in TextView

Symptom: Long articles with inline images (formulas, emoji, diagrams) cause lag after scrolling.

Root cause: SpannableString holding ImageSpan referencing DrawableBitmapContext. TextView recycling in RecyclerView doesn't clear spans.

Detection: LeakCanary shows TextViewSpannableStringImageSpanBitmapDrawableContext chain.

3. Wikitext Parser AST Cache Leak

Symptom: Editing 5+ pages sequentially → ANR on 6th edit.

Root cause: ParserCache (LRU map) stores DocumentNode ASTs keyed by page ID. Nodes hold references to ParseContextProjectSettingsApplication. Cache eviction policy misses WeakReference cleanup.

Detection: Heap dump shows 500+ DocumentNode instances retained by static ParserCache.INSTANCE.

4. Real-time Sync Disposable Leak

Symptom: Collaborative editing session > 15 min → UI freezes, battery drain.

Root cause: PageCollabManager.subscribeToChanges() returns Disposable stored in ViewModel. ViewModel cleared but Disposable not disposed because onCleared() not called (process death).

Detection: adb shell dumpsys activity broadcasts shows CompositeDisposable size growing per session.

5. Offline-First Queue Retention

Symptom: App killed after background sync of 200+ pending edits.

Root cause: WorkManager ListenableWorker holds List with full wikitext content. setForegroundAsync() keeps worker alive; List never cleared after Result.success().

Detection: WorkManager diagnostics show WORKER_RUNNING > 30 min with 200 MB heap.

6. Mermaid/PlantUML Renderer Native Leak

Symptom: Opening 10 diagram-heavy pages → crash in libskia.so.

Root cause: Skia/Chromium renderer called via JNI for SVG output. PictureRecorder/Canvas not close()'d after toPicture(). Native memory untracked by JVM GC.

Detection: Debug.getNativeHeapAllocatedSize() spikes 80 MB per diagram; not visible in hprof.

7. Search Index TokenStream Leak

Symptom: Full-text search slows down after 50 queries; heap grows 200 MB.

Root cause: Lucene TokenStream / TokenFilter chain not close()'d after IndexSearcher.search(). CachingTokenFilter holds AttributeSourceCharTermAttributechar[] buffers.

Detection: jcmd GC.heap_dump + MAT shows CharTermAttributeImpl dominating retained set.

---

How to Detect Memory Leaks

Automated CI Detection


# .github/workflows/memory-leak-check.yml
- name: Run monkey test with leak detection
  run: |
    python -m susatest.agent \
      --apk app/build/outputs/apk/debug/app-debug.apk \
      --personas curious,power_user,impatient \
      --duration 600 \
      --leak-detection enabled \
      --output junit:leak-results.xml

SUSA's impatient persona rapidly navigates back/forward 200+ times; curious opens every link in a 500-page wiki. Leak detection hooks into ActivityLifecycleCallbacks + LeakCanary ObjectWatcher to assert zero retained Activity/Fragment instances post-navigation.

Local Tooling Stack

ToolPurposeWiki-Specific Config
LeakCanary 2.12+Auto-detect retained View/ContextLeakAssertions.assertNoLeaks() in onDestroy() of PageFragment
Android Studio ProfilerNative + Java heapRecord "Open 30 articles → back to home" flow; filter dalvik-webview
Perfetto / adb shell perfettoSystem-wide memory pressureTrace lmk_kill events correlated with WebView native heap
MAT (Eclipse Memory Analyzer)Heap dump dominator treeOQL: SELECT * FROM instanceof android.webkit.WebView WHERE retainedHeap > 10M
SUSA CLIAutonomous explorationsusatest explore --apk app.apk --personas adversarial,accessibility --max-steps 500 --detect-leaks

What to Look For

---

How to Fix Each Example

1. WebView History Stack


// PageWebViewClient.kt
override fun onPageFinished(view: WebView, url: String) {
    super.onPageFinished(view, url)
    // Clear forward history; keep only last 3 for back navigation
    if (view.copyBackForwardList().getCurrentIndex() > 3) {
        val db = view.getDatabase() // internal API via reflection
        db?.execSQL("DELETE FROM history WHERE id < ?", 
            arrayOf(view.copyBackForwardList().getItemAtIndex(0).id))
    }
}

// In Fragment.onDestroyView()
webView.destroy() // Critical: releases native renderer
webView = null

Alternative: Use ChromeCustomTabs for external links; reserve WebView only for editable preview.

2. ImageSpan Leak


// WikiTextView.kt
fun setWikiSpans(spannable: Spannable) {
    // Clear existing spans BEFORE setting new ones
    val existingSpans = spannable.getSpans(0, spannable.length, ImageSpan::class.java)
    existingSpans.forEach { span ->
        (span.drawable as? BitmapDrawable)?.bitmap?.recycle()
        spannable.removeSpan(span)
    }
    // Use Weak

Test Your App Autonomously

Upload your APK or URL. SUSA explores like 10 real users — finds bugs, accessibility violations, and security issues. No scripts.

Try SUSA Free