Tinker Android Hot-Fix Library

repository·dev·Indexed 12 days ago

https://github.com/tencent/tinker

A hot-fix solution for Android that enables developers to update dex files, libraries, and resources without a full APK reinstallation. It includes the tinker-patch-gradle-plugin for integration and specialized components like TinkerClassLoader and TinkerResourcePatcher for runtime patching. Version 1.9.1 supports dex, library, and resource updates via ShareConstants.TINKER_ENABLE_ALL.

Tokens
2.1K
Snippets
7
Records
8
Agent score
96%

What's inside Tinker

  1. Build a patch for Ark support

    dev

    If you are using Ark, you can build a patch using the provided shell script. The old argument is the absolute path to the buggy APK (not compiled by Ark), and new is the absolute path to the fixed APK (not compiled by Ark).

    bash build_patch_dexdiff.sh old=xxx new=xxx
  2. Configure TinkerApplication and ApplicationLifeCycle

    dev

    Tinker requires a specific application structure. You must separate your application lifecycle logic from the Application class itself.

    1. Create a LifeCycle class: Subclass DefaultApplicationLike (instead of Application) to hold your application's initialization logic.
    2. Create the Application class: Subclass TinkerApplication. Since TinkerApplication is abstract and has no default constructor, you must provide a no-arg constructor that calls super() with the appropriate tinkerFlags and the full class name of your LifeCycle class.

    Tinker Flags: Use ShareConstants.TINKER_ENABLE_ALL to support dex, library, and resource updates. Other options include dex-only or library-only.

    Alternatively, you can use the @DefaultLifeCycle annotation from tinker-android-anno to automate the generation of the TinkerApplication class.

    // Manual implementation
    public class SampleApplication extends TinkerApplication {
        public SampleApplication() {
          super(
            ShareConstants.TINKER_ENABLE_ALL,
            "tinker.sample.android.app.SampleApplicationLike");
        }
    }
    
    // Recommended: Using @DefaultLifeCycle annotation
    @DefaultLifeCycle(
        application = "tinker.sample.android.app.SampleApplication",
        flags = ShareConstants.TINKER_ENABLE_ALL
    )
    public class SampleApplicationLike extends DefaultApplicationLike {
        // ... implementation
    }
  3. Install Tinker in an Android project

    dev

    To integrate Tinker, you must first add the Gradle plugin to your root build.gradle file, then apply the plugin and add the necessary dependencies to your app-level app/build.gradle file.

    // 1. Root build.gradle
    buildscript {
        dependencies {
            classpath ('com.tencent.tinker:tinker-patch-gradle-plugin:1.9.1')
        }
    }
    
    // 2. app/build.gradle
    dependencies {
        // optional, helps generate the final application
        provided('com.tencent.tinker:tinker-android-anno:1.9.1')
        // tinker's main Android lib
        compile('com.tencent.tinker:tinker-android-lib:1.9.1')
    }
    
    apply plugin: 'com.tencent.tinker.patch'
  4. Configure Ark patch paths

    dev

    To use Ark-compiled patches, you must specify the patch path and name in your configuration. You can do this via tinker_config.xml for tinker-cli or via the ark block in app/build.gradle for Gradle.

    <!-- For tinker-cli in tinker_config.xml -->
    <issue id="arkHot">
       <path value="arkHot"/>
       <name value="patch.apk"/>
    </issue>
    // For Gradle in app/build.gradle
    ark {
       path = "arkHot"
       name = "patch.apk"
    }
  5. Understand Tinker limitations and known issues

    dev

    Be aware of the following limitations when using Tinker:

    1. AndroidManifest.xml: You cannot dynamically update the AndroidManifest.xml (e.g., you cannot add new Android Components via a patch).
    2. Device Compatibility: Some Samsung models running Android 21 are not supported.
    3. Google Play Policy: Due to the Google Play Developer Distribution Agreement, you cannot use Tinker to dynamically update your APK if you are distributing via Google Play.
  6. Initialize resource patching with isResourceCanPatch

    dev

    Before applying resource patches, you must call isResourceCanPatch(Context context) to prepare the internal Android framework structures. This method uses reflection to locate critical fields like mResDir, mPackages, and mResourcePackages within ActivityThread and LoadedApk. It also prepares the AssetManager reflection hooks required for the subsequent patching process.

    // Call this first to prepare the environment
    TinkerResourcePatcher.isResourceCanPatch(context);
  7. Use TinkerClassLoader to load patched dex files

    dev

    The TinkerClassLoader is a specialized PathClassLoader used to facilitate hot-patching in Android applications. It allows the application to load patched .dex files at runtime by prioritizing them over the original application classes.

    When searching for a class or resource, TinkerClassLoader follows this lookup order:

    1. The system class loader.
    2. The patched dex files (via injectDexPath).
    3. The mOriginAppClassLoader (the original application's class loader).

    Note: The constructor is package-private (TinkerClassLoader(...)), implying that it is intended to be instantiated by the Tinker framework's internal loader components rather than directly by end-users in typical application code.

    // Note: The constructor is package-private in the source.
    // It is used by the framework to initialize the patched environment.
    TinkerClassLoader loader = new TinkerClassLoader(
        dexPath,           // Path to the patched dex files
        optimizedDir,       // Directory for optimized dex files
        libraryPath,        // Path to native libraries
        originAppClassLoader // The original application ClassLoader
    );
  8. Apply resource patches with monkeyPatchExistingResources

    dev

    Use monkeyPatchExistingResources(Context context, String externalResourceFile, boolean isReInject) to inject updated resources into the running application.

    • context: The application context.
    • externalResourceFile: The absolute path to the patched APK file containing the new resources.
    • isReInject: If true, the method will skip the heavy logic of creating new AssetManager instances and instead re-applies the existing patch (useful for handling lifecycle events like screen rotation on certain Android versions).

    This method performs several critical tasks:

    1. Updates LoadedApk to point to the new resource directory.
    2. Creates a new AssetManager pointing to the externalResourceFile.
    3. Iterates through all existing Resources objects in the system and replaces their AssetManager with the new one.
    4. Handles manufacturer-specific issues (like MIUI) by clearing mTypedArrayPool caches.
    5. Installs a ResourceInsuranceHandlerCallback to ensure patches persist through activity lifecycle changes.
    // 1. Prepare the patcher
    TinkerResourcePatcher.isResourceCanPatch(context);
    
    // 2. Apply the patch
    String patchPath = "/data/user/0/com.example/files/patch.apk";
    TinkerResourcePatcher.monkeyPatchExistingResources(context, patchPath, false);