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

  1. Move the call to the main thread: runOnUiThread(...), activity.runOnUiThread, Handler(Looper.getMainLooper()).post(...), or withContext(Dispatchers.Main).
  2. For RxJava, add .observeOn(AndroidSchedulers.mainThread()) before the QMUI call.
  3. Ensure AsyncTask results manipulate fragments in onPostExecute/onProgressUpdate (which are main-thread).
  4. 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

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


AI-assisted analysis of Tencent/QMUI_Android@026e7d4866 (2026-09-06). Data as JSON: /api/errors/18b6435e987122eb. Report an issue: GitHub.