ADFA-2829 | Move initialization tasks to background threads#940
ADFA-2829 | Move initialization tasks to background threads#940
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughRefactors startup and threading: adds a single-thread background executor, offloads disk/JNI/font I/O to background threads, marshals UI updates back to the main thread, tightens lifecycle guards, binds a handler to an explicit Looper, and converts one directory creation to a coroutine on Dispatchers.IO. Changes
Sequence Diagram(s)sequenceDiagram
participant Activity as TermuxActivity
participant Executor as TermuxExecutor
participant Main as MainThread
participant Disk as Disk/Preferences
participant JNI as JNI Loader
Activity->>Executor: executeInBackground(load prefs + preload JNI)
Executor->>Disk: read preferences from disk
Executor->>JNI: Class.forName("com.termux.terminal.JNI")
Executor-->>Main: executeOnMain(completeOnCreate(savedInstanceState))
Main->>Activity: completeOnCreate(...)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
termux/termux-app/src/main/java/com/termux/app/TermuxActivity.java (1)
246-307:⚠️ Potential issue | 🟠 Major
completeOnCreatereplaysonStartlogic but notonResume— lifecycle callbacks may be permanently skipped.When the background preferences load is slow, the lifecycle proceeds:
onStart→onResume→ both return early becausemPreferences == null. WhencompleteOnCreatefinally runs on the main thread, it replaysonStartlogic (Lines 302-306) via theif (mIsVisible)guard, butonResumelogic is never replayed. This means:
mTermuxTerminalSessionActivityClient.onResume()(bell sound pool setup) is skipped.mTermuxTerminalViewClient.onResume()is skipped.- Crash notification check is skipped.
feedbackButtonManager.loadFabPosition()is skipped.These will only run the next time the activity is resumed (e.g., after switching away and back), which may never happen.
Proposed fix — also replay onResume logic in completeOnCreate
if (mIsVisible) { if (mTermuxTerminalSessionActivityClient != null) mTermuxTerminalSessionActivityClient.onStart(); if (mTermuxTerminalViewClient != null) mTermuxTerminalViewClient.onStart(); if (mPreferences.isTerminalMarginAdjustmentEnabled()) addTermuxActivityRootViewGlobalLayoutListener(); + + // Replay onResume logic that was skipped while mPreferences was null + if (mTermuxTerminalSessionActivityClient != null) mTermuxTerminalSessionActivityClient.onResume(); + if (mTermuxTerminalViewClient != null) mTermuxTerminalViewClient.onResume(); + TermuxExecutor.executeInBackground(() -> TermuxCrashUtils.notifyAppCrashFromCrashLogFile(this, LOG_TAG)); + if (feedbackButtonManager != null) feedbackButtonManager.loadFabPosition(); }
🤖 Fix all issues with AI agents
In
`@termux/termux-app/src/main/java/com/termux/app/terminal/TermuxTerminalSessionActivityClient.java`:
- Around line 495-523: The background task mutates shared UI/state on a worker
thread; move all color and UI mutations into the main-thread callback: keep file
I/O and props loading in the TermuxExecutor.executeInBackground lambda (load
Properties and determine newTypeface), then inside TermuxExecutor.executeOnMain
run TerminalColors.COLOR_SCHEME.updateWith(props),
session.getEmulator().mColors.reset() (guard nulls), call
updateBackgroundColor(), and set the typeface via
mActivity.getTerminalView().setTypeface(newTypeface); preserve the existing
null/finishing checks and exception handling around these operations.
In `@termux/termux-app/src/main/java/com/termux/app/TermuxActivity.java`:
- Around line 226-244: The savedInstanceState Bundle is captured and used
asynchronously which risks it being recycled; before calling
TermuxExecutor.executeInBackground extract whatever values completeOnCreate
needs (or create a lightweight copy/serializable Map) from savedInstanceState on
the current thread, then pass those extracted values into the lambda and finally
call completeOnCreate with the extracted data on the main thread; update
references around TermuxExecutor.executeInBackground, completeOnCreate, and the
savedInstanceState capture so mPreferences/mIsInvalidState logic remains the
same but no longer relies on the original Bundle after the async gap.
- Around line 697-703: onCreateNewSession is dispatching addNewSession to a
background thread (via TermuxExecutor.executeInBackground) but addNewSession (in
TermuxTerminalSessionActivityClient) performs UI work; move that call to the
main thread instead — either remove TermuxExecutor.executeInBackground and call
mTermuxTerminalSessionActivityClient.addNewSession(...) directly, or replace
executeInBackground with the main-thread helper (e.g.
TermuxExecutor.executeOnMain) so the entire addNewSession body runs on the UI
thread.
In
`@termux/termux-shared/src/main/java/com/termux/shared/termux/settings/properties/TermuxSharedProperties.java`:
- Around line 46-60: The field mSharedProperties can be updated inside
loadTermuxPropertiesFromDisk but is read by getProperties, getPropertyValue, and
getInternalProperties without synchronization, allowing readers to see a stale
reference; mark the field mSharedProperties as volatile to establish the
required happens-before visibility so updates made in
loadTermuxPropertiesFromDisk are visible to unsynchronized readers (leave
getInternalPropertyValue's synchronized usage as-is); update the declaration of
mSharedProperties accordingly.
In
`@termux/termux-shared/src/main/java/com/termux/shared/termux/TermuxExecutor.java`:
- Around line 20-30: The execute method in TermuxExecutor currently lacks a
null-check for the backgroundTask parameter; update TermuxExecutor.execute to
validate backgroundTask before submitting to backgroundExecutor (throw
IllegalArgumentException or return early) and ensure you still post
mainThreadCallback via mainHandler in the finally block only if
mainThreadCallback != null; reference the execute(...) method, the
backgroundTask and mainThreadCallback parameters, and the
backgroundExecutor/mainHandler usage when locating and fixing the code.
🧹 Nitpick comments (4)
termux/termux-shared/src/main/java/com/termux/shared/termux/TermuxExecutor.java (1)
9-9: Single-thread executor serializes all background work across the app.Multiple callers submit independent tasks (JNI preload, preferences loading, properties loading, crash-log checking, font/color loading, session creation). With a single-thread executor, all of these are serialized — a slow JNI
Class.forNamewill delay preferences loading, which in turn delayscompleteOnCreate. Consider using a small fixed-size thread pool (e.g.,Executors.newFixedThreadPool(2)) or at least a cached thread pool so truly independent tasks can overlap.termux/termux-shared/src/main/java/com/termux/shared/termux/settings/properties/TermuxSharedProperties.java (1)
54-59: Move logging calls outside the synchronized block.
dumpPropertiesToLog()anddumpInternalPropertiesToLog()perform potentially slow I/O-backed logging while holding the intrinsic lock. Since the field assignment is already complete by line 56, move the dumps outside thesynchronizedblock to reduce lock contention.Proposed fix
synchronized (this) { mPropertiesFile = propertiesFile; mSharedProperties = loadedProperties; - dumpPropertiesToLog(); - dumpInternalPropertiesToLog(); } + dumpPropertiesToLog(); + dumpInternalPropertiesToLog();termux/termux-app/src/main/java/com/termux/app/TermuxActivity.java (2)
548-555:reloadPropertiesfires a background reload that is redundant with the constructor's synchronous load.
mPropertiesis initialized at Line 217 viaTermuxAppSharedProperties.getProperties(), and the constructor ofTermuxSharedPropertiesalready callsloadTermuxPropertiesFromDisk()synchronously (Line 40 of TermuxSharedProperties.java). ThenreloadProperties()on Line 218 immediately queues another background load. The first background load is redundant.Also, the main-thread callback
mTermuxTerminalViewClient.onReloadProperties()is guarded by a null check, but sincemTermuxTerminalViewClientis set later incompleteOnCreate(viasetTermuxTerminalViewAndClients()), this callback will always be a no-op during the initial call fromonCreate. Consider whetherreloadProperties()should only be called when an actual reload is needed (e.g., fromreloadActivityStyling).
608-618:executeOnMainalways re-posts even when already on the main thread — introduces unnecessary asynchrony.
setCurrentSessionandonResetTerminalSessionare wrapped withTermuxExecutor.executeOnMain(...), meaning they are always deferred to the next main-thread message loop iteration, even when called from the main thread. This changes the execution from synchronous to asynchronous, which can introduce subtle ordering issues (e.g., a session switch not yet visible when the next line of the caller executes).Consider posting only when not already on the main thread:
Proposed conditional dispatch
+private static void runOnMainThread(Runnable task) { + if (Looper.myLooper() == Looper.getMainLooper()) { + task.run(); + } else { + TermuxExecutor.executeOnMain(task); + } +} + `@Override` public void setCurrentSession(TerminalSession session) { - TermuxExecutor.executeOnMain(() -> super.setCurrentSession(session)); + runOnMainThread(() -> super.setCurrentSession(session)); }
...ux/termux-app/src/main/java/com/termux/app/terminal/TermuxTerminalSessionActivityClient.java
Show resolved
Hide resolved
...hared/src/main/java/com/termux/shared/termux/settings/properties/TermuxSharedProperties.java
Show resolved
Hide resolved
termux/termux-shared/src/main/java/com/termux/shared/termux/TermuxExecutor.java
Show resolved
Hide resolved
a930c08 to
0b8e569
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@termux/termux-app/src/main/java/com/termux/app/TermuxActivity.java`:
- Around line 547-556: reloadActivityStyling currently calls reloadProperties()
which performs asynchronous disk I/O via TermuxExecutor.execute, then
immediately reads mProperties on the main thread causing a race and stale
values; change reloadProperties()/its caller so the styling reads occur only
after the background load completes: either make reloadProperties accept a
completion Runnable (or return a Future) and invoke reloadActivityStyling’s
property reads from that completion callback, or move the code in
reloadActivityStyling that accesses mProperties into the main-thread callback
currently passed to TermuxExecutor.execute (the lambda that calls
mTermuxTerminalViewClient.onReloadProperties), ensuring all reads of mProperties
happen after mProperties.loadTermuxPropertiesFromDisk() finishes.
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
termux/termux-app/src/main/java/com/termux/app/terminal/TermuxTerminalSessionActivityClient.java (1)
363-391:⚠️ Potential issue | 🟠 MajorInconsistent main-thread marshaling in
addNewSession.
setCurrentSession(newTerminalSession)at line 387 directly performs UI operations (attaching sessions, scrolling the list, updating background color) withoutexecuteOnMain, while the dialog (line 368) and drawer close (line 389) are wrapped. This is inconsistent:
- If
addNewSessionis always called on the main thread, the twoexecuteOnMainwrappers are unnecessary and subtly change timing (deferring execution to the next handler message).- If it may be called off the main thread, then
setCurrentSessionmust also be wrapped, otherwise it will crash.Additionally, the
executeOnMainlambda at line 368 lacks an activity-state guard (mActivity.isFinishing()), which can cause aBadTokenExceptionif the activity finishes before the postedRunnableexecutes.Proposed fix — add guard to dialog lambda
TermuxExecutor.executeOnMain(() -> { + if (mActivity == null || mActivity.isFinishing()) return; new AlertDialog.Builder(mActivity).setTitle(R.string.title_max_terminals_reached).setMessage(R.string.msg_max_terminals_reached) .setPositiveButton(android.R.string.ok, null).show(); });
🧹 Nitpick comments (2)
termux/termux-shared/src/main/java/com/termux/shared/termux/settings/properties/TermuxSharedProperties.java (2)
46-60: Move logging outside thesynchronizedblock to reduce lock-hold time.
dumpPropertiesToLog()anddumpInternalPropertiesToLog()iterate over all properties, build strings, and write logs — all while holding the monitor. This unnecessarily blocks concurrent readers (e.g.,getInternalPropertyValue) during what could be a slow I/O path.Since
loadedPropertiesis a local variable that won't be mutated by other threads, you can safely log after releasing the lock:Suggested fix
synchronized (this) { mPropertiesFile = propertiesFile; mSharedProperties = loadedProperties; - dumpPropertiesToLog(); - dumpInternalPropertiesToLog(); } + dumpPropertiesToLog(); + dumpInternalPropertiesToLog();Note:
dumpPropertiesToLogcallsgetProperties(true)which reads thevolatilemSharedProperties, so it will see the just-published value without needing the lock.
157-179:synchronizedon the whole method is correct for the compound read, but beware of disk I/O under the lock.The
synchronizedis needed for the check-then-act on lines 160–164 — good. However, whencachedisfalse(line 177),mSharedProperties.getProperty(key, false)reads directly from the properties file while holding the intrinsic lock onthis. This blocks all othergetInternalPropertyValuecallers (andloadTermuxPropertiesFromDisk) for the duration of that disk read.If the
cached == falsepath is rarely used, this is acceptable. If it's called frequently, consider reading the property outside the lock:Possible restructuring
public synchronized Object getInternalPropertyValue(String key, boolean cached) { + if (!cached) { + // Read from disk outside the lock to avoid blocking other callers + String propValue = mSharedProperties.getProperty(key, false); + return getInternalTermuxPropertyValueFromValue(mContext, key, propValue); + } Object value; - if (cached) { - value = mSharedProperties.getInternalProperty(key); - if (value != null || mSharedProperties.getInternalProperties().containsKey(key)) { - return value; - } else { - value = getInternalTermuxPropertyValueFromValue(mContext, key, null); - Logger.logWarn(LOG_TAG, "The value for \"" + key + "\" not found in SharedProperties cache, force returning default value: `" + value + "`"); - return value; - } - } else { - return getInternalTermuxPropertyValueFromValue(mContext, key, mSharedProperties.getProperty(key, false)); - } + value = mSharedProperties.getInternalProperty(key); + if (value != null || mSharedProperties.getInternalProperties().containsKey(key)) { + return value; + } + value = getInternalTermuxPropertyValueFromValue(mContext, key, null); + Logger.logWarn(LOG_TAG, "The value for \"" + key + "\" not found in SharedProperties cache, force returning default value: `" + value + "`"); + return value; }Actually, the non-cached path doesn't need the lock at all since it just reads from disk and derives a value — there's no compound check on shared state. Moving it before the
synchronized(or splitting into two methods) would eliminate unnecessary contention.
d2cbe97 to
54a1b7d
Compare
54a1b7d to
1b8d0cd
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
termux/termux-app/src/main/java/com/termux/app/terminal/TermuxTerminalSessionActivityClient.java (1)
363-391:⚠️ Potential issue | 🟠 Major
addNewSessionis called from a background thread but performs thread-unsafe operations.This method is invoked via
TermuxExecutor.executeInBackgroundfromTermuxActivity.onCreateNewSession(line 707). While the dialog (line 368) and drawer close (line 389) are now correctly dispatched to the main thread, these operations still run on the background thread without thread-safety guarantees:
- Line 367:
service.getTermuxSessionsSize()— reads mutable service state- Line 373:
mActivity.getCurrentSession()— reads the terminal view's current session (UI state)- Line 383:
service.createTermuxSession(...)— mutates the sessions listThe safest fix is to run
addNewSessionentirely on the main thread and remove theexecuteInBackgroundwrapper inTermuxActivity.onCreateNewSession, since this method doesn't perform heavy I/O.Proposed fix in TermuxActivity.onCreateNewSession (lines 704-710)
protected void onCreateNewSession(boolean isFailsafe, String sessionName, String workingDirectory) { if (mTermuxTerminalSessionActivityClient == null) return; - - TermuxExecutor.executeInBackground(() -> { - mTermuxTerminalSessionActivityClient.addNewSession(isFailsafe, sessionName, workingDirectory); - }); + mTermuxTerminalSessionActivityClient.addNewSession(isFailsafe, sessionName, workingDirectory); }And the
executeOnMainwrappers for the dialog and drawer can then be removed as they'd already be on the main thread:Simplify addNewSession once it runs on main thread
if (service.getTermuxSessionsSize() >= MAX_SESSIONS) { - TermuxExecutor.executeOnMain(() -> { - new AlertDialog.Builder(mActivity).setTitle(R.string.title_max_terminals_reached).setMessage(R.string.msg_max_terminals_reached) - .setPositiveButton(android.R.string.ok, null).show(); - }); + new AlertDialog.Builder(mActivity).setTitle(R.string.title_max_terminals_reached).setMessage(R.string.msg_max_terminals_reached) + .setPositiveButton(android.R.string.ok, null).show(); } else { ... - TermuxExecutor.executeOnMain(() -> mActivity.getDrawer().closeDrawers()); + mActivity.getDrawer().closeDrawers(); }
🤖 Fix all issues with AI agents
In
`@termux/termux-shared/src/main/java/com/termux/shared/termux/TermuxExecutor.java`:
- Around line 12-13: executeInBackground currently forwards task directly to
backgroundExecutor.execute without validating null; add the same null-check
behavior used in execute() so that if task (the parameter to
executeInBackground) is null you skip calling backgroundExecutor.execute (and
optionally log or return) instead of letting backgroundExecutor.execute throw a
NullPointerException; locate the executeInBackground method in TermuxExecutor
and mirror the guard used in execute() to validate the Runnable before invoking
backgroundExecutor.execute.
🧹 Nitpick comments (3)
termux/termux-shared/src/main/java/com/termux/shared/termux/settings/properties/TermuxSharedProperties.java (1)
46-60: Clean local-variable-then-swap pattern for thread-safe loading.Building
loadedPropertieslocally and assigning it atomically inside thesynchronizedblock ensures the published object is fully initialized. Thevolatilewrite in line 56 provides happens-before to unsynchronized readers.One minor observation:
dumpPropertiesToLog()anddumpInternalPropertiesToLog()(lines 57–58) are called inside thesynchronized(this)block. These methods callgetProperties(true)andgetInternalProperties()which readmSharedProperties— this works correctly since the reference was just assigned. However, the logging could be slow if the properties map is large, which would hold the monitor longer than necessary. Consider moving the dump calls outside thesynchronizedblock.♻️ Move logging outside the synchronized block
synchronized (this) { mPropertiesFile = propertiesFile; mSharedProperties = loadedProperties; - dumpPropertiesToLog(); - dumpInternalPropertiesToLog(); } + dumpPropertiesToLog(); + dumpInternalPropertiesToLog();Since
mSharedPropertiesisvolatile, readers outside the lock already see the latest reference, and the dump methods only read the reference — they don't need to hold the lock.termux/termux-shared/src/main/java/com/termux/shared/termux/TermuxExecutor.java (2)
8-14: Single-thread executor serializes all background work — potential bottleneck.
Executors.newSingleThreadExecutor()means every call toexecuteInBackgroundandexecuteis queued behind all previous tasks. If any task blocks (e.g., slow disk I/O on first launch, font loading), subsequent tasks (JNI preload, preferences loading, crash log check) will be delayed.This is a reasonable simplification to avoid concurrency bugs, but be aware that the serialized queue could negate some of the startup-time benefits of moving work off the main thread. If startup latency is still a concern, consider a small fixed-size pool (e.g., 2–3 threads) or at least document this design choice.
25-32: Uncaught exceptions inexecuteInBackgroundare silently swallowed.When a task submitted via
executeInBackgroundthrows an unchecked exception, the single-thread executor catches it internally, terminates that thread, and creates a replacement. The exception is never logged or propagated. Theexecutemethod has the same issue for the background task (though the finally block correctly ensures the callback runs).Consider wrapping with a try-catch that at minimum logs the failure:
Proposed fix for executeInBackground
public static void executeInBackground(Runnable task) { + if (task == null) return; - backgroundExecutor.execute(task); + backgroundExecutor.execute(() -> { + try { + task.run(); + } catch (Exception e) { + android.util.Log.e("TermuxExecutor", "Background task failed", e); + } + }); }
termux/termux-shared/src/main/java/com/termux/shared/termux/TermuxExecutor.java
Show resolved
Hide resolved
1b8d0cd to
604f411
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
termux/termux-app/src/main/java/com/termux/app/terminal/TermuxTerminalSessionActivityClient.java (1)
363-391:⚠️ Potential issue | 🟠 MajorThread-safety concern:
addNewSessionbody still runs on a background thread.This method is invoked from
TermuxActivity.onCreateNewSessionviaTermuxExecutor.executeInBackground. While the UI operations (AlertDialog, closeDrawers, setCurrentSession) have been correctly marshaled to the main thread, several intermediate operations still execute on the background thread:
- Line 364:
mActivity.getTermuxService()— reads mutable activity state- Line 367:
service.getTermuxSessionsSize()— reads session list- Line 373:
mActivity.getCurrentSession()— accessesTerminalViewfrom a background thread (View access off the UI thread)- Line 383:
service.createTermuxSession(...)— modifies the sessions listThe
createTermuxSessioncall may be appropriate on background if it does I/O, but the View access on Line 373 and unsynchronized reads of the session list could introduce subtle races. Consider wrapping the entireaddNewSessionbody inexecuteOnMainand only offloading the actual subprocess creation if needed.
🤖 Fix all issues with AI agents
In
`@termux/termux-shared/src/main/java/com/termux/shared/termux/TermuxExecutor.java`:
- Around line 17-19: The method executeOnMain currently calls
mainHandler.post(task) without guarding against a null Runnable which causes a
NullPointerException; update executeOnMain to check for a null task (like the
existing executeInBackground/execute methods) and return early (or log) if task
is null instead of calling mainHandler.post, ensuring mainHandler.post is only
invoked with a non-null Runnable.
🧹 Nitpick comments (2)
termux/termux-shared/src/main/java/com/termux/shared/termux/TermuxExecutor.java (1)
9-9: Single-thread executor serializes all background work — verify this is intentional.
newSingleThreadExecutor()means all background tasks across the entire app (JNI preload, preferences loading, font/color loading, crash log checking, session creation) are queued and run sequentially. If any task blocks or runs long, all subsequent tasks are delayed.If parallelism is desired for independent tasks (e.g., JNI preload shouldn't block preferences loading), consider a small fixed thread pool. If strict ordering is intentional (e.g., to avoid concurrent disk access), then this is fine.
termux/termux-app/src/main/java/com/termux/app/TermuxActivity.java (1)
554-563: Stale property reads inreloadActivityStylingafter asyncreloadProperties.
reloadProperties()(Line 554) dispatchesloadTermuxPropertiesFromDisk()to a background thread, butreloadActivityStyling()(Line 1045) calls it and then immediately readsmPropertieson Lines 1050 and 1055. Since the background task hasn't completed yet, these reads return the old values.If this is an accepted design trade-off (properties are consistent but possibly stale until the callback fires), the behavior is safe but the UI may briefly show outdated styling until the next styling trigger. Consider chaining the styling reads into the main-thread callback of
reloadPropertiesfor correctness, or document this as intentional.Also applies to: 1045-1056
termux/termux-shared/src/main/java/com/termux/shared/termux/TermuxExecutor.java
Show resolved
Hide resolved
604f411 to
442f435
Compare
Introduces TermuxExecutor to offload blocking tasks and prevent startup ANRs.
Prevents lost onResume side-effects when onResume runs before preferences are loaded.
…`executeInBackground` method
442f435 to
36e29c3
Compare
Description
This PR optimizes the application startup and lifecycle handling by moving heavy initialization tasks and disk I/O operations off the main thread.
Key Changes:
completeOnCreate) once preferences are loaded.TerminalActivity) to background threads.TermuxSharedPropertiesto ensure thread safety during asynchronous loading.Details
Before changes
Screen.Recording.2026-02-06.at.5.02.34.PM.mov
After changes
Screen.Recording.2026-02-06.at.4.50.53.PM.mov
Ticket
ADFA-2829
Observation
This change significantly reduces the work done on the main thread during
onCreateandonResume. Please verify that the terminal initializes correctly and that no race conditions occur during the first run or configuration changes.