Tencent/tinker · error · TinkerRuntimeException

tinkerFlag is already set.

Error message

tinkerFlag is already set.

What it means

Builder.tinkerFlags(int) may only be called once. The builder tracks whether status was already assigned (status != -1) and throws on a second call to prevent ambiguous flag configuration (e.g., one call enabling only dex, another enabling only so).

Source

Thrown at tinker-android/tinker-android-lib/src/main/java/com/tencent/tinker/lib/tinker/Tinker.java:367

            if (context == null) {
                throw new TinkerRuntimeException("Context must not be null.");
            }
            this.context = context;
            this.mainProcess = TinkerServiceInternals.isInMainProcess(context);
            this.patchProcess = TinkerServiceInternals.isInTinkerPatchServiceProcess(context);
            this.patchDirectory = SharePatchFileUtil.getPatchDirectory(context);
            if (this.patchDirectory == null) {
                ShareTinkerLog.e(TAG, "patchDirectory is null!");
                return;
            }
            this.patchInfoFile = SharePatchFileUtil.getPatchInfoFile(patchDirectory.getAbsolutePath());
            this.patchInfoLockFile = SharePatchFileUtil.getPatchInfoLockFile(patchDirectory.getAbsolutePath());
            ShareTinkerLog.w(TAG, "tinker patch directory: %s", patchDirectory);
        }

        public Builder tinkerFlags(int tinkerFlags) {
            if (this.status != -1) {
                throw new TinkerRuntimeException("tinkerFlag is already set.");
            }
            this.status = tinkerFlags;
            return this;
        }

        public Builder tinkerLoadVerifyFlag(Boolean verifyMd5WhenLoad) {
            if (verifyMd5WhenLoad == null) {
                throw new TinkerRuntimeException("tinkerLoadVerifyFlag must not be null.");
            }
            if (this.tinkerLoadVerifyFlag != null) {
                throw new TinkerRuntimeException("tinkerLoadVerifyFlag is already set.");
            }
            this.tinkerLoadVerifyFlag = verifyMd5WhenLoad;
            return this;
        }

        public Builder loadReport(LoadReporter loadReporter) {
            if (loadReporter == null) {

View on GitHub (pinned to 1b7ea02c23)

Solutions

  1. Set tinkerFlags exactly once, computing the final mask before calling the builder.
  2. Compose flags in a variable (dex | native | res as needed) rather than calling the setter repeatedly.
  3. Create a fresh Builder per initialization instead of reusing a stored one.

Example fix

// before
builder.tinkerFlags(TINKER_DEX);
builder.tinkerFlags(TINKER_NATIVE); // throws

// after
int flags = ShareConstants.TINKER_DEX | ShareConstants.TINKER_NATIVE;
builder.tinkerFlags(flags);
Defensive patterns

Strategy: validation

Validate before calling

int flags = 0;
if (dexEnabled) flags |= ShareConstants.TINKER_DEX;
if (nativeEnabled) flags |= ShareConstants.TINKER_NATIVE;
if (resEnabled) flags |= ShareConstants.TINKER_RESOURCES;
builder.tinkerFlags(flags); // single call with the composed mask

Prevention

When it happens

Trigger: Chaining or repeating tinkerFlags(...) twice on the same Builder instance — commonly when one shared builder is reused across init paths, or a helper method adds flags in addition to explicit call-site configuration.

Common situations: A base init method sets tinkerFlags(TINKER_DEX) and a feature module calls tinkerFlags(TINKER_DEX | TINKER_NATIVE) on the same builder; copy-pasted config blocks; builders stored as fields and reused after orientation/process re-entry.

Related errors


AI-assisted analysis of Tencent/tinker@1b7ea02c23 (2026-08-14). Data as JSON: /api/errors/b6ef6fceb7790f21. Report an issue: GitHub.