SuperAPI AI Directory

kuikly-recomposition-analyzer

Coding & development

View repository

Updated 10-15 23:58

About this skill

Kuikly Recomposition Analyzer

Three-phase funnel analysis of KuiklyUI Compose DSL recomposition performance issues: report screening → frames deep dive → source code confirmation.

Threshold configuration

Read references/config.md to get the default thresholds. User-specified parameters in the request can override (e.g. scopeCountThreshold=10)。

Workflow

Phase 0 — Get logs

Read references/log-format.md to understand the log field format. Obtain in priority order profiler_report.json and profiler_frames.jsonl:

  1. User provides path directly → read with the Read tool
  2. Check the conventional path in the current directory:./profiler_logs/、./profiler_report.json
  3. Automatically pull from the device → read references/log-retrieval.md and execute the corresponding platform command
  4. All fail → output guidance:

    No profiler logs found. Please collect data first:

    1. Call RecompositionProfiler.start() in the code to start recording, and after the operation is complete call stop()
    2. Or click "Start" in the Profiler Overlay panel to record, click "Stop" after the operation is complete, then click "Get Report"
    3. After collection is complete, tell me the file path, or provide the App package name and I will pull it

Phase 1 — Data health check

  • totalFrames < minFramesThreshold(default 30) → alert, ask whether to continue
  • totalRecompositions == 0 → indicate no recomposition records
  • filteredNames non-empty → declare the excluded components in the report

Phase 2 — Report screening

Prerequisite steps (must be executed first): Sort all non-noScope components in descending order by recompositionCount and list the TOP 20.Any recompositionCount > 50 component must enter the report, no matter how low the total time is. This step prevents high-frequency but low-time components from being missed when subsequently sorted by total time.

Read references/detection-rules.md, iterate over composables[]:

Condition Handling
noScopeRecompositions == recompositionCount Categorize intonormal recomposition list, skip subsequent analysis
maxDurationMs > singleRecompDurationThreshold(default 10ms) Regardless of the number of recompositions, must be output to the report, enter Phase 3 deep dive
scopeDistribution If a certain key count > scopeCountThreshold mark as suspicious, enter Phase 3
paramChangeFrequency["#N"] / recompositionCount > paramChangeRateThreshold Mark RULE-C suspicious (parameter type must be determined in conjunction with source code)
triggerStates[i].readers.length > stateReadersThreshold Mark RULE-B suspicious

Phase 3 — Frames deep dive + source code confirmation

Note:profiler_frames.jsonl is in JSONL format (one independent JSON object per line), not a single JSON file. It must be read and parsed line by line; the entire file cannot be parsed as JSON. Use the Read tool to read it and then process line by line.

If the analysis target is an item component inside LazyList/LazyGrid/Pager, read references/lazylist-rules.md to understand the difference between item closure reconstruction and business component skip.

Read profiler_frames.jsonlline by line, and route by the type field (frame / touch_context / scroll_context)。

Frame-level check:

For each frame that exceeds the time threshold, handle it according to the following process:

  1. First determine whether it is normal:

    • Compare against scroll_context: if the frame immediately follows a scroll event, and the events in the frame are mainly noScope(first composition) → classify asnormal rendering overhead, briefly explain the reason in the report, and do not proceed to subsequent analysis
    • If the number of in-frame events is large but the vast majority are noScope → same as above, this is normal batch first composition when a list slides in
  2. After confirming it is a real issue, perform in-frame root cause analysis:

    • Find the composable event with the longest duration within the frame (durationMs the largest few)
    • Check whether these components are cascade-triggered by the same State (triggerStates the same)
    • Perform complete chain-of-thought reasoning (Steps 1-5, same suspect workflow) for the component with the highest duration
  3. Each real issue frame in the report must include:

    • frame duration + number of in-frame events
    • Judgment conclusion (normal / problematic) and reason
    • If problematic: names, durations, and triggering States of the top 1-3 components by duration
    • Root cause analysis (refer to chain-of-thought reasoning Steps 2-4)
    • Optimization suggestions (provide when there is a specific direction; when it cannot be determined, explain what information needs to be supplemented)
  • Single composable_recomposed.durationMs > durationThreshold → proceed to chain-of-thought reasoning
  • Multiple components in the same frame triggered by the same State → cascade suspicion, analyze the write timing of that State

Context-assisted judgment (when touch/scroll is available):

  • If a certain scope recomposes > 3 times between touchBegin~touchEnd → mark "one tap triggers N recompositions, suspected optimizable"
  • scroll_context index changes + item recompositions ≈ number slid in → classify as normal
  • scroll_context index unchanged + item recomposition → mark "recomposition not caused by scrolling, needs analysis"

Source code confirmation (only for confirmed suspects):

For each suspect, perform in-depth analysis according to the following chain-of-thought reasoning steps (do not skip):

Step 1 — Locate the code Take sourceLocation(format FileName.kt:行号),use Glob "**/<FileName>.kt" to locate the file, read the function declaration and 30 lines of surrounding code.

Step 2 — Understand the data signals Answer: this component's scopeDistribution shows which scope is repeatedly triggered?triggerStates shows which State is driving it?paramChangeFrequency Which parameter changes every time? Write out the specific values (e.g., "scope=223833166 triggered 61 times, average duration 0.75ms").

If paramChangeFrequency shows a certain parameter changing frequently,you must first determine the nature of the change:

  • business data is genuinely changing(e.g., coordinates differ each frame while scrolling, list content updates when paging) → the root cause is the write logic, not a type stability issue
  • data content has not changed but the reference has changed(a new instance is passed each time, the value is the same but === are not equal) → only then is it a type stability or object creation issue

The root causes of the two are completely different and must not be confused.

Step 3 — Trace the root cause Combined with the code, answer: who writes this State? At what timing is it written? Why is it triggered on every recomposition? Find the real "writer" (not the "reader"). If the State is written in LaunchedEffect / onGloballyPositioned / snapshotFlow and other side effects, explain the specific triggering timing.

For CompositionLocal subtree recomposition, additionally answer: is the value passed to CompositionLocalProvider a new instance or a cached instance?CompositionLocalProvider Use === reference comparison; even if the content is the same, passing a new instance each time triggers recomposition of the entire subtree. The root cause may be "a copy()/new object is created on every recomposition", not necessarily type instability.

General process for determining the root cause of parameter changes(applicable to any high paramChangeFrequency case):

  1. Read the source code to find the call site of the parameter—who is passing this parameter?
  2. Is what is passed a newly created object (copy()、listOf(), lambda) or a stable reference (singleton,remember cache)?
  3. If it is a newly created object: check whether it is necessary to create a new one every time, or whether remember caching can be used
  4. If it is a stable reference but is still determined to have changed: only then consider type stability (whether there is var、List, cross-module type)

RULE-C special: when RULE-C is hit, additionally execute

Read references/stability-rules.md to understand the complete stability determination rules, then:

  • Read the source code, and in declaration order map #N to the specific parameter names and types
  • First determine the nature of the change: is the parameter value genuinely different each time (business data is changing)? Or is the value the same but a new instance is passed each time (references are not equal)? The former is not a stability issue; only the latter requires considering type stability
  • @Stable/@Immutable annotations override compiler inference: for a class with the annotation, the compiler trusts that it is stable and will not judge it unstable because of var/List . If @Stable is added but the parameter still changes 100% of the time, the real reason is "a new instance is passed each time", not a type inference issue
  • Note the bug of direct assignment with @Stable + var: skip will happen, but the UI will not update (showing stale data), which is more dangerous than "not skipping"
  • If it is confirmed to be "creating a new instance repeatedly with the same value", then choose a solution by type; see references/optimization-patterns.md
  • When Strong Skipping is enabled, it is forbidden to suggest manually writing remember { { ... } } to wrap a lambda—writing it manually is redundant. If the lambda parameter still changes frequently, the problem lies in the stability of the variables captured by the lambda

Step 4 — Assess the scope of impact Answer: how many components subscribe to this State (readers)? Do all of these components really need to recompose every time the State changes? Which can be skipped? Which must respond?

Step 5 — Propose solutions and explain the trade-offs Read references/optimization-patterns.md to get the optimization solution for the corresponding rule. Give 1-2 specific optimization solutions; each solution must:

  • Provide a before/after code comparison
  • Explain why this change can solve the problem (explain from the perspective of the Compose runtime mechanism)
  • Explain possible side effects or caveats
  • If there are multiple solutions, explain which one is recommended and in what scenarios to choose another

Before recommending adding @Stable/@Immutable annotations, the following two checks must be passed; if either is not satisfied, do not recommend it:

  1. Do the class's properties satisfy the annotation's promise (@Immutable = never changes after construction;@Stable = changes are only notified through MutableState)? If it contains var direct assignment, skip will still occur (the annotation makes the compiler trust it), butthe UI will not update(Compose does not know the value changed), which will cause UI bugs
  2. Will the caller reuse the instance or pass the same reference? If every time copy()/new/listOf() a new object is created and passed in, the annotation cannot make skip occur

If the two checks do not pass, do not recommend adding an annotation; instead, from thehow the caller passes parametersorhow the data model is designedperspective, provide a solution.

If you encounter a situation where analysis is limited (unable to locate the source code, parameter indices cannot be mapped, etc.), read references/known-limitations.md to confirm whether it is a known limitation, and handle it according to the limitation description.

Phase 4 — Output report

Read references/report-template.md, and generate:

  1. Conversation summary: data overview + TOP 3 issues
  2. Markdown report:recomp-analysis-YYYYMMDD-HHmm.md, including data overview, list of normal recompositions, issue diagnosis (in descending order of severity), and filter configuration declaration. For severity rating and sorting rules, see references/detection-rules.md:Total duration(recomposition count × average single duration) is the first sorting dimension; issues with many occurrences but extremely low single duration are ranked after issues with truly high duration.

Report writing specifications (must be followed):

  1. Frame statistics in the data overview: write only the frame count, not the percentage. For example, "slow frames: 14 frames", not "14 frames (7.4%)".
  2. Issue descriptions must not use internal terminology: terms such as RULE-A、RULE-B、RULE-C、RULE-SCOPE must not appear in issue descriptions. Describe them in language users can understand, for example "this component is re-rendered on every scroll" instead of "hit RULE-B". Rule identifiers may only appear in thefilter configuration declarationsection.
  3. Context descriptions must distinguish the trigger source: when describing the number of recompositions, it must be clear whether it is "one click triggers N recompositions" or "N frames of scrolling cumulatively trigger M recompositions"; the two must not be mixed. If it is a cumulative total across multiple frames, state "during X frames of scrolling, this component recomposed N times in total".
  4. Each issue must include two key data points: ① the number of recompositions triggered by the same scope (or the total number of recompositions); ② the average single duration (avgDurationMs). If either data point is missing, mark it as "insufficient data, unable to assess severity".
  5. Issue analysis must be in-depth: the root cause analysis must explain "who is writing this State, at what timing it is written, and why it is triggered frequently"; it cannot merely say "State changes cause recomposition". Optimization suggestions must provide a code comparison before and after the change, and explain why this change is effective; they cannot merely give a conclusion.
  6. When the cause is unknown, state it directly and provide troubleshooting guidance: if a certain issue (such as abnormal single duration) cannot be traced to its root cause through existing logs and source code, do not guess or give vague conclusions. Directly write: "The current logs are insufficient to determine the root cause; further troubleshooting is recommended", and provide specific troubleshooting suggestions, for example:
    • Add duration instrumentation inside the component function body (measureTimeMillis) pinpoint which sub-operation is slow
    • or directly say, "You can tell me, and I'll help you do a deeper analysis"
  7. Components with a high recomposition count but low total time must not be omitted: all components that hit the detection rules must appear in the report; do not skip them just because their total time is low. For components with an obviously high recomposition count (e.g., >50 times) but extremely low per-recomposition time (<0.5ms):
    • still include them in the report, marking the severity as "low"
    • explain the recomposition count and average time
    • if no in-depth analysis was done, clearly note "per-recomposition time is extremely low, no in-depth analysis yet, but the recomposition count is high, worth attention"
    • for those with an extremely high count (e.g., >100 times), even if the time is low, a brief root-cause analysis should be done (at least explain what State is driving it and whether the recomposition count can be reduced)
  8. Respect existing @Stable/@Immutable annotations and do not question their accuracy:
    • class is already annotated with @Stable or @Immutable → the compiler trusts it to be stable,do not say "the annotation is inaccurate" or "the annotation is invalid"
    • class contains Map/List/lambda properties but is already annotated with @Immutable → the annotation overrides the compiler's inference; this is the developer's intentional design, not an error
    • When Strong Skipping is already enabled,@Stable class contains a lambda property → the lambda is automatically remember, the reference is stable,do not say "the stability of the lambda reference depends on the caller"
    • If a class parameter with an added annotation still changes frequently, the problem lies inthe caller passing in a new instance every time, not in the annotation. The analysis direction is how the caller passes parameters, not questioning the annotation
  9. When recommending the use of remember to cache objects, the dependencies must be analyzed:
    • Forbiddento directly recommend a keyless remember {}, unless it has been confirmed that object creation does not depend on any external state
    • If the factory function may internally read CompositionLocal (such as theme color, font size, dark mode) → remember must have the correct key (such as remember(isDarkTheme) { markdownColor() }), otherwise the configuration will not update after a theme switch, causing UI bugs
    • Ifcannot be confirmedthe factory function's internal dependencies (the source code was not read) → do not recommend remember, but instead recommend "check whether the function depends on external state such as the theme before deciding whether to cache"
    • Incorrect example:val colors = remember { markdownColor() } — if markdownColor() reads dark mode, the color will not update after switching the theme
    • Correct example:val isDark = isAppInDarkTheme(); val colors = remember(isDark) { markdownColor() }

Form C: Focus on a specific page

When the user says "I only want to look at page XX":

Please click the "Reset" button in the profiler panel, operate through the target page once, and then let me analyze it.

References

  • references/config.md — configurable threshold defaults
  • references/log-format.md — report.json / frames.jsonl field descriptions
  • references/log-retrieval.md — pull commands for each platform (adb/xcrun/hdc)
  • references/detection-rules.md — Detailed logic of the detection rules
  • references/lazylist-rules.md — LazyList item recomposition analysis rules (closure re-creation vs business component skip, avoiding erroneous conclusions)
  • references/stability-rules.md — Compose stability rules (empirically verified): compiler inference rules, skip conditions, annotation effective/dangerous scenarios, performance differences in the Profiler
  • references/optimization-patterns.md — The optimization solution and code example corresponding to each rule
  • references/known-limitations.md — Known limitations (paramChanges index has no parameter names, unstable type scope re-creation, etc.)
  • references/report-template.md — Markdown report template