Tencent/tinker · error · TinkerRuntimeException

tinkerFlag is already set.

Error message

tinkerFlag is already set.

What it means

Tinker.Builder.tinkerFlags(int) is a set-once fluent setter: it throws TinkerRuntimeException("tinkerFlag is already set.") when this.status != -1, i.e. when the same builder has already had its flags configured. The guard catches accidental double-configuration from chaining or repeated calls, which would otherwise silently override the enabled-patch-type bitmask (dex, native lib, resources).

Source

Thrown at tinker-android/tinker-android-lib-no-op/src/main/java/com/tencent/tinker/lib/tinker/Tinker.java:316

            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. Decide the bitmask once and pass it as a parameter to a single tinkerFlags() call.
  2. If defaults plus overrides are needed, compute the final int (e.g. flags &= ~Tinker_ENABLE_NATIVE_LIBRARY) before calling the setter once.
  3. Never reuse a Builder instance; construct a fresh one per initialization.

Example fix

// before
builder.tinkerFlags(TinkerENABLE.ALL);
if (debugBuild) builder.tinkerFlags(TinkerENABLE.DEX_ONLY); // throws

// after
int flags = debugBuild ? TinkerENABLE.DEX_ONLY : TinkerENABLE.ALL;
builder.tinkerFlags(flags);
Defensive patterns

Strategy: validation

Validate before calling

// compute final flags once, then a single setter call
int flags = TinkerENABLE.ALL;
if (!nativePatchingEnabled) flags &= ~TinkerENABLE.NATIVE_LIBRARY;
builder.tinkerFlags(flags);

Prevention

When it happens

Trigger: Calling tinkerFlags(...) twice on the same Builder instance — e.g. shared configuration code applying a default bitmask and then caller-specific code applying another, or a copy-pasted chain segment duplicated during refactor.

Common situations: A common base method configures TinkerEnabled.ALL and a flavor-specific path then narrows it to TinkerDISABLE or DEX_ONLY; builder instances cached in a field and reused; merging two init code paths during cleanup.

Related errors


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