asLody/VirtualApp · error · IllegalStateException
VirtualCore.startup() must called in main thread.
Error message
VirtualCore.startup() must called in main thread.
What it means
VirtualCore.startup() hooks the app's main ActivityThread, PackageManager, and other main-thread-bound structures, so it must run on the Android main (UI) thread. If called from a worker thread it throws this IllegalStateException immediately, before any hooks are installed.
Solutions
- Move the startup() call to Application.onCreate() on the main thread
- If async init is required, post the startup call to the main Handler: new Handler(Looper.getMainLooper()).post(...)
- Never wrap startup in background executors; the check is deliberate because hooking must happen before other main-thread activity
Example fix
// before
new Thread(() -> { VirtualCore.get().startup(this); }).start();
// after
@Override public void onCreate() { super.onCreate(); VirtualCore.get().startup(this); } Defensive patterns
Strategy: validation
Validate before calling
if (Looper.myLooper() == Looper.getMainLooper()) { VirtualCore.get().startup(context); } else { new Handler(Looper.getMainLooper()).post(() -> VirtualCore.get().startup(context)); } Try / catch
try { VirtualCore.get().startup(ctx); } catch (IllegalStateException e) { new Handler(Looper.getMainLooper()).post(() -> { try { VirtualCore.get().startup(ctx); } catch (Throwable t) { /* fatal */ } }); } Prevention
- Always call startup() from Application.onCreate()
- Never initialize VA engine from background threads or coroutines on IO dispatchers
- Check Looper.myLooper() before engine init if the call path is uncertain
When it happens
Trigger: Calling VirtualCore.get().startup(context) from a background thread, AsyncTask, IntentService, or any thread whose Looper differs from Looper.getMainLooper().
Common situations: Initializing the VA engine inside a coroutine, RxJava scheduler, or delayed Handler on a non-main looper; calling startup lazily from a background init SDK.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
AI-assisted analysis of asLody/VirtualApp@666fefcb5d (2026-09-09).
Data as JSON: /api/errors/c1f531c50e5d8636.
Report an issue: GitHub.
Appendix: source
Thrown at VirtualApp/lib/src/main/java/com/lody/virtual/client/core/VirtualCore.java:177
}
public PackageManager getPackageManager() {
return context.getPackageManager();
}
public String getHostPkg() {
return hostPkgName;
}
public PackageManager getUnHookPackageManager() {
return unHookPackageManager;
}
public void startup(Context context) throws Throwable {
if (!isStartUp) {
if (Looper.myLooper() != Looper.getMainLooper()) {
throw new IllegalStateException("VirtualCore.startup() must called in main thread.");
}
VASettings.STUB_CP_AUTHORITY = context.getPackageName() + "." + VASettings.STUB_DEF_AUTHORITY;
ServiceManagerNative.SERVICE_CP_AUTH = context.getPackageName() + "." + ServiceManagerNative.SERVICE_DEF_AUTH;
this.context = context;
mainThread = ActivityThread.currentActivityThread.call();
unHookPackageManager = context.getPackageManager();
hostPkgInfo = unHookPackageManager.getPackageInfo(context.getPackageName(), PackageManager.GET_PROVIDERS);
IPCBus.initialize(new IServerCache() {
@Override
public void join(String serverName, IBinder binder) {
ServiceCache.addService(serverName, binder);
}
@Override
public IBinder query(String serverName) {
return ServiceManagerNative.getService(serverName);
}
});View on GitHub (pinned to 666fefcb5d)