Tencent/QMUI_Android · error · IllegalAccessError
don't call #onEnterAnimationEnd() directly
Error message
don't call #onEnterAnimationEnd() directly
What it means
QMUIFragment.onEnterAnimationEnd() is a framework-dispatched lifecycle callback run when the enter animation finishes; it flips mEnterAnimationStatus to END, updates isInEnterAnimationLiveData to false and runs delayed render runnables. Like its Start counterpart, a mCalled guard throws IllegalAccessError when it is invoked directly, because running it twice would prematurely clear animation state and re-run delayed renderables.
Source
Thrown at arch/src/main/java/com/qmuiteam/qmui/arch/QMUIFragment.java:1408
* may not be call.
* @param animation
*/
protected void onEnterAnimationStart(@Nullable Animator animation) {
if (mCalled) {
throw new IllegalAccessError("don't call #onEnterAnimationStart() directly");
}
mCalled = true;
mEnterAnimationStatus = ANIMATION_ENTER_STATUS_STARTED;
isInEnterAnimationLiveData.setValue(true);
}
/**
* may not be call.
* @param animation
*/
protected void onEnterAnimationEnd(@Nullable Animator animation) {
if (mCalled) {
throw new IllegalAccessError("don't call #onEnterAnimationEnd() directly");
}
mCalled = true;
mEnterAnimationStatus = ANIMATION_ENTER_STATUS_END;
isInEnterAnimationLiveData.setValue(false);
notifyDelayRenderRunnableList();
}
private void notifyDelayRenderRunnableList(){
if (mDelayRenderRunnableList != null) {
ArrayList<Runnable> list = mDelayRenderRunnableList;
mDelayRenderRunnableList = null;
if (!list.isEmpty()) {
for (Runnable runnable : list) {
runnable.run();
}
}
}
}View on GitHub (pinned to 026e7d4866)
Solutions
- Delete the direct call and rely on QMUI's internal onAnimationEnd dispatch.
- Override onEnterAnimationEnd() in your subclass to run custom end-of-animation logic, calling super first.
- If you must react to animation completion externally, attach your own Animator listener to the animation you started, not to this callback.
- Use isInEnterAnimationLiveData observers instead of forcing the callback.
Example fix
// before
animator.addListener(new AnimatorListenerAdapter() {
@Override public void onAnimationEnd(Animator a) { fragment.onEnterAnimationEnd(a); }
});
// after
@Override
protected void onEnterAnimationEnd(@Nullable Animator animation) {
super.onEnterAnimationEnd(animation);
// custom logic here
} Defensive patterns
Strategy: validation
Validate before calling
if (fragment instanceof QMUIFragment && !fragment.isAdded()) {
// framework will dispatch onEnterAnimationEnd itself; do not call manually
} Try / catch
try {
// framework-managed transition
} catch (IllegalAccessError e) {
Log.w(TAG, "lifecycle hook invoked directly", e);
} Prevention
- Attach custom AnimatorListeners instead of duplicating onAnimationEnd behavior
- Call super.onEnterAnimationEnd() only from within an override
- Use LiveData observers rather than forcing the callback
When it happens
Trigger: Calling fragment.onEnterAnimationEnd(animator) manually from app code, calling it from a custom AnimatorListener to 'speed up' the transition, or any second invocation since mCalled is only checked, never reset here.
Common situations: Custom animation completion listeners that duplicate framework behavior, migration from plain Fragment code where onEnterAnimationEnd didn't exist, and test harnesses invoking protected hooks directly.
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
- don't call #onEnterAnimationStart() directly
- View is not attached to window
- can not perform $action when running.
- Can not perform LatestVisitRecord, %s must be annotated by L
- Fragment(%s) not attached to Activity.
AI-assisted analysis of Tencent/QMUI_Android@026e7d4866 (2026-09-06).
Data as JSON: /api/errors/c982787d07760331.
Report an issue: GitHub.