Tencent/matrix · error · AssertionError
must be init in main thread!
Error message
must be init in main thread!
What it means
UIThreadMonitor.init(config) throws an AssertionError when invoked from any thread other than the main (UI) thread. The monitor installs a Looper printer and observes frame callbacks on the main Looper, so initialization off the main thread is unsupported and would produce broken instrumentation.
Solutions
- Call init on the main thread, e.g. in Activity/Application.onCreate or via Handler(Looper.getMainLooper()).post(...)
- Move TracePlugin initialization into the main app entry point
- If init is triggered asynchronously, route it through the main Looper before calling
Example fix
// before
new Thread(() -> UIThreadMonitor.get().init(config)).start();
// after
new Handler(Looper.getMainLooper()).post(() ->
UIThreadMonitor.get().init(config)); Defensive patterns
Strategy: validation
Validate before calling
if (Looper.myLooper() != Looper.getMainLooper()) throw new IllegalStateException("call init on main thread"); Type guard
boolean isMainThread() { return Looper.myLooper() == Looper.getMainLooper(); } Try / catch
Runnable init = () -> UIThreadMonitor.get().init(config); if (isMainThread()) init.run(); else new Handler(Looper.getMainLooper()).post(init);
Prevention
- Initialize Matrix plugins inside Application.onCreate
- Route any async init through the main Looper
- Never initialize UI-coupled monitors from worker threads
- Document main-thread requirements in your init wrapper
When it happens
Trigger: Calling UIThreadMonitor.get().init(config) from a worker thread, background executor, or a non-main handler thread instead of the application main thread (e.g. inside Application.onCreate is fine, but inside an AsyncTask.doInBackground is not).
Common situations: Initializing Matrix/TracePlugin from a background thread at app startup; test harnesses initializing the monitor on a test thread; libraries wrapping Matrix init in their own thread pools.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- Should not call this from main thread!
- <getTag()> is not yet init!
- sBuffer == null
- origin == this
- Matrix init, Matrix should not be null.
AI-assisted analysis of Tencent/matrix@3b8293bd65 (2026-09-08).
Data as JSON: /api/errors/6c76c0078931786d.
Report an issue: GitHub.
Appendix: source
Thrown at matrix/matrix-android/matrix-trace-canary/src/main/java/com/tencent/matrix/trace/core/UIThreadMonitor.java:111
private long[] queueCost = new long[CALLBACK_LAST + 1];
private static final int DO_QUEUE_DEFAULT = 0;
private static final int DO_QUEUE_BEGIN = 1;
private static final int DO_QUEUE_END = 2;
private boolean isInit = false;
public static UIThreadMonitor getMonitor() {
return sInstance;
}
public boolean isInit() {
return isInit;
}
public void init(TraceConfig config) {
this.config = config;
if (Thread.currentThread() != Looper.getMainLooper().getThread()) {
throw new AssertionError("must be init in main thread!");
}
boolean historyMsgRecorder = config.historyMsgRecorder;
boolean denseMsgTracer = config.denseMsgTracer;
LooperMonitor.register(new LooperMonitor.LooperDispatchListener(historyMsgRecorder, denseMsgTracer) {
@Override
public boolean isValid() {
return isAlive;
}
@Override
public void dispatchStart() {
super.dispatchStart();
UIThreadMonitor.this.dispatchBegin();
}
@OverrideView on GitHub (pinned to 3b8293bd65)