Direct answer
A file-descriptor leak is a resource-lifecycle problem, not the same thing as a Java heap leak or a model-memory increase. Android apps may open descriptors for model artifacts, user-selected content, decoded media, databases, sockets, pipes, or native runtime files. A task can finish visibly while one of those handles remains open. Repeating that task may increase the process descriptor inventory even when heap usage looks stable. Diagnose by recording a baseline, executing one fixed task, and comparing open-descriptor observations after each acquisition, failure, cancellation, and cleanup boundary. Start with an ownership map. For every API that returns a Closeable or descriptor-backed object, record who receives ownership, who is responsible for closing it, and which wrapper also owns the underlying descriptor. Android’s ParcelFileDescriptor documentation describes it as Closeable and provides auto-close stream wrappers; if a wrapper is used, understand whether closing the wrapper closes the descriptor to avoid both leaks and double-close assumptions. For imported content, provider-backed URIs and file streams can have distinct lifetimes. For native inference, record whether the runtime opens its own files; do not assume application code controls all internal descriptors. Use a repeatable sequence: capture a descriptor baseline, open the model or import input, run preprocessing and inference, cancel or trigger a controlled exception, close the owning stream/session, and measure again. Repeat many times with the same fixture and record the count or identifiable handle categories at each checkpoint. Do not hard-code a supposedly universal maximum descriptor count; process limits and runtime behavior vary. A stable count that rises and then returns may indicate expected temporary handles, while a monotonic increase after cleanup suggests an ownership path to investigate. Confirm the same pattern on the exact app build and device used for any conclusion. StrictMode can help detect Closeable objects finalized without being closed when configured in a debug/test policy. It is a development tripwire, not a complete descriptor inventory or proof that every native handle is covered. Pair it with targeted descriptor snapshots and injected failure paths. Close resources deterministically with structured ownership patterns such as try-with-resources or Kotlin use where appropriate; do not wait for garbage collection to close an open file. Test early returns and exceptions immediately after each acquisition so a success-only test cannot hide a missing close. Keep leak evidence privacy-safe. Record the task stage, handle category, count delta, request ID, and cleanup result; avoid logging private file paths, imported document names, prompts, transcripts, or audio content. If a descriptor points to a user-selected URI, retain only a redacted provider category or test-fixture ID. The fact card for Cove lifecycle describes model-loading states and cleanup behavior, while Android memory tools can observe process behavior; neither proves a descriptor leak exists in a particular release. This page is a measurement procedure, not a claim about current Cove failures or a fixed safe threshold.
Repeated imports gradually stop opening model or media files
An Android app can import a document or audio clip, run local inference, and return to an idle screen. After repeated success and cancellation paths, a later open fails, but the heap profile is not increasing and the team has no descriptor lifecycle trace.
Decision
Track descriptor ownership and counts across a fixed repeated task, including exception and cancellation exits, before blaming memory pressure or the model runtime.
Expected outcome
The team can identify which acquisition lacks deterministic close behavior or record that no monotonic descriptor growth was observed for the tested configuration.
Evidence to collect
The claim becomes reviewable only when the following evidence is attached to the same artifact and test run:
- Descriptor inventory by stage
- Repeated task and cancellation fixtures
- Close, failure, and recovery results
A device badge must come from an exact device, OS, artifact, runtime, backend, and workload record, not parameter-count intuition.
Implementation workflow
- Inventory every descriptor-producing stage. List model file access, content-provider streams, camera/audio sources, temporary files, database cursors, sockets, pipes, and native runtime handles used by the task. Mark whether each owner is app code, a library, or the platform.
- Write an ownership and close contract. For each resource, identify the creator, current owner, transfer point, wrapper behavior, close action, and error path. Confirm whether closing a wrapper also closes its underlying ParcelFileDescriptor; avoid relying on finalization.
- Capture a baseline and repeat a fixed task. Record an initial descriptor count or category snapshot, execute the same import/inference fixture, then sample after each stage and after cleanup. Repeat success, failure, cancellation, and retry while keeping the app build and input constant.
- Inject exits immediately after acquisition. Force parsing failure, model initialization error, cancellation, navigation, and storage exceptions after each open call. Assert the acquired resource closes exactly once and that the next task can open its inputs.
- Use diagnostic checks and redact evidence. Enable StrictMode leaked-Closeable detection only in a controlled debug/test policy, and compare it with descriptor snapshots because native handles may not be covered. Store stage/count metadata, not personal paths or file contents.
Keep each transition observable. A failure should identify the stage, artifact, runtime, and recovery action without logging private user content.
Acceptance criteria
- Every descriptor-producing resource has a named owner, transfer rule, and deterministic close path on success and failure.
- Repeated fixed-task runs include cancellation and exception paths and record descriptor observations before and after cleanup.
- A possible leak is identified by reproducible post-cleanup growth, not a guessed universal descriptor threshold or heap graph alone.
- Diagnostics omit user content, private paths, and imported filenames; any conclusion is scoped to the tested build and device.
Decision table
| Dimension | Choice or evidence | How to interpret it |
|---|---|---|
| Managed heap profile | Inspect Java/Kotlin object allocations and retention | Useful for heap leaks; stable heap does not establish that file descriptors are closed. |
| StrictMode leaked-Closeable check | Detect selected closable objects finalized without close in debug/test | Good tripwire for some Java resources, but not a complete inventory of native descriptors. |
| Descriptor lifecycle trace | Compare process descriptor observations around fixed stages | Directly targets handle growth; requires careful redaction and exact configuration notes. |
Failure patterns to prevent
- Measuring only successful tasks
- Relying on process death for cleanup
Also prevent silent fallback, unpinned artifacts, missing cancellation, and conclusions that combine unlike configurations. Store unsuccessful runs alongside successful ones.
Minimum reproducibility record
| Layer | Record |
|---|---|
| Device | Manufacturer, model, chipset, RAM class, operating-system build |
| Software | Application version and git commit |
| Model | Family, variant, revision, format, file length, hash, quantization |
| Runtime | Name, revision, requested backend, observed backend evidence |
| Workload | Fixture revision, input hash, prompt hash, output policy |
| Outcome | Completed, failed, cancelled, fallback, and privacy-safe diagnostics |
Release checklist
- ☐ The primary query is answered without an unsupported number.
- ☐ Every artifact and runtime is pinned.
- ☐ The representative task and failure policy are explicit.
- ☐ Lifecycle, cancellation, cleanup, and fallback are tested.
- ☐ User-content and network boundaries are documented.
- ☐ Result wording applies only to the recorded configuration.
- ☐ The page links to raw method or evidence when results are added.
Sources and related evidence
- Android Memory Profiler
- Android StrictMode leaked-Closeable detection
- Android ParcelFileDescriptor ownership and close API
Chinese deployment and troubleshooting content is organized in the .奇连 AI 端侧专题.
Frequently asked questions
Does a flat Java heap prove the app has no file-descriptor leak?
No. Descriptors and native handles are separate resources. Track descriptor observations and ownership independently from managed heap and process-memory metrics.
Will StrictMode find every native file descriptor leak?
No. Its leaked-Closeable detector is a development aid for selected closable objects. Pair it with stage-level descriptor snapshots and native/runtime evidence where available.
Can I wait for garbage collection to close an imported file?
Do not rely on that. Close the owning stream or ParcelFileDescriptor deterministically on success, exception, cancellation, and early return; finalization timing is not a resource contract.