Tencent/QMUI_Android · error · IllegalStateException
Call the method must be in main thread: %s
Error message
Call the method must be in main thread: %s
What it means
Utils.assertInMainThread() is a debugging guard used by QMUI APIs that touch view state or fragment transactions which are only safe on the UI thread. When Looper.myLooper() != Looper.getMainLooper(), it throws IllegalStateException naming the offending call site (extracted from the stack trace) so the developer can see which method was called off the main thread.
Source
Thrown at arch/src/main/java/com/qmuiteam/qmui/arch/Utils.java:103
}
}
@SuppressLint("PrivateApi") Method convertToTranslucent = Activity.class.getDeclaredMethod("convertToTranslucent",
translucentConversionListenerClazz, ActivityOptions.class);
convertToTranslucent.setAccessible(true);
convertToTranslucent.invoke(activity, null, options);
} catch (Throwable ignore) {
}
}
public static void assertInMainThread() {
if (Looper.myLooper() != Looper.getMainLooper()) {
StackTraceElement[] elements = Thread.currentThread().getStackTrace();
String methodMsg = null;
if (elements != null && elements.length >= 4) {
methodMsg = elements[3].toString();
}
throw new IllegalStateException("Call the method must be in main thread: " + methodMsg);
}
}
static void modifyOpForStartFragmentAndDestroyCurrent(FragmentManager fragmentManager,
final QMUIFragment fragment,
final boolean useNewTransitionConfigWhenPop,
final QMUIFragment.TransitionConfig transitionConfig){
findAndModifyOpInBackStackRecord(fragmentManager, -1, new Utils.OpHandler() {
@Override
public boolean handle(Object op) {
Field cmdField = null;
try {
cmdField = Utils.getOpCmdField(op);
cmdField.setAccessible(true);
int cmd = (int) cmdField.get(op);
if (cmd == 1) {
if (useNewTransitionConfigWhenPop) {
Field popEnterAnimField = Utils.getOpPopEnterAnimField(op);View on GitHub (pinned to 026e7d4866)
Solutions
- Move the call to the main thread: runOnUiThread(...), activity.runOnUiThread, Handler(Looper.getMainLooper()).post(...), or withContext(Dispatchers.Main).
- For RxJava, add .observeOn(AndroidSchedulers.mainThread()) before the QMUI call.
- Ensure AsyncTask results manipulate fragments in onPostExecute/onProgressUpdate (which are main-thread).
- Audit the method named in the exception message for off-main call sites and wrap it in a main-thread dispatcher.
Example fix
// before
executor.execute(() -> QMUISwipeBackActivityManager.getInstance().getCurrentActivity());
// after
executor.execute(() -> runOnUiThread(() ->
QMUISwipeBackActivityManager.getInstance().getCurrentActivity())); Defensive patterns
Strategy: validation
Validate before calling
if (Looper.myLooper() != Looper.getMainLooper()) {
// hop to main thread before invoking QMUI APIs
new Handler(Looper.getMainLooper()).post(() -> qmuiCall());
return;
}
qmuiCall(); Type guard
void runOnMain(Runnable r) {
if (Looper.myLooper() == Looper.getMainLooper()) r.run();
else new Handler(Looper.getMainLooper()).post(r);
} Try / catch
try {
qmuiCall();
} catch (IllegalStateException e) {
if (e.getMessage() != null && e.getMessage().startsWith("Call the method must be in main thread")) {
new Handler(Looper.getMainLooper()).post(this::qmuiCall);
} else throw e;
} Prevention
- Wrap QMUI fragment/view APIs with runOnUiThread or Dispatchers.Main
- Add .observeOn(AndroidSchedulers.mainThread()) on Rx chains touching QMUI
- Never touch fragments from worker threads or binder callbacks
When it happens
Trigger: Invoking any QMUI API that internally calls assertInMainThread() — e.g. fragment transition helpers or latest-visit operations — from a background thread, worker (HandlerThread/coroutine on Dispatchers.Default/IO), or a binder/callback thread.
Common situations: Updating fragments after a network callback on a background thread, running fragment operations inside coroutines without Dispatchers.Main, RxJava/AsyncTask callbacks that forgot to hop back to the main thread, and tests running logic on non-UI threads.
Related errors
- Can not perform LatestVisitRecord, %s must be annotated by L
- Fragment(%s) not attached to Activity.
- Can not find the fragment container provider.
- requestCode can not be %s
- %s did not call through to super.shouldPreventSwipeBack()
AI-assisted analysis of Tencent/QMUI_Android@026e7d4866 (2026-09-06).
Data as JSON: /api/errors/18b6435e987122eb.
Report an issue: GitHub.