TinkerSource

TinkerApplication.attachBaseContext

/**
 \* tinkerFlags, which types is supported
 \* dex only, library only, all support
 \* default: TINKER_ENABLE_ALL
 */
 private final int tinkerFlags;
 private final boolean tinkerLoadVerifyFlag;
 private final String delegateClassName;
 private final String loaderClassName;

private ITinkerInlineFenceBridge mBridge = null;
private static final String TINKER_LOADER_METHOD = "tryLoad";
@Override
 protected void attachBaseContext(Context base) {
   super.attachBaseContext(base);
   Thread.setDefaultUncaughtExceptionHandler(new TinkerUncaughtHandler(this));
   onBaseContextAttached(base);//main
 }
private void onBaseContextAttached(Context base) {
   try {
     final long applicationStartElapsedTime = SystemClock.elapsedRealtime();
     final long applicationStartMillisTime = System.currentTimeMillis();
     loadTinker();//main
     //补丁应用之后才初始化TinkerApplicationInlineFence类,并在其中反射构造SampleApplicationLike,达到修复application代码的目的
     mBridge = createInlineFence(tinkerFlags, delegateClassName,
         tinkerLoadVerifyFlag, applicationStartElapsedTime, applicationStartMillisTime,
         tinkerResultIntent);
     mBridge.attachBaseContext(this, base);
     //reset save mode
     if (useSafeMode) {
       ShareTinkerInternals.setSafeModeCount(this, 0);
     }
   }
}

loadTinker

private void loadTinker() {
   try {
     //reflect tinker loader, because loaderClass may be define by user!
     Class<?> tinkerLoadClass = Class.forName(loaderClassName, false, TinkerApplication.class.getClassLoader());
     Method loadMethod = tinkerLoadClass.getMethod(TINKER_LOADER_METHOD, TinkerApplication.class);
     Constructor<?> constructor = tinkerLoadClass.getConstructor();
     tinkerResultIntent = (Intent) loadMethod.invoke(constructor.newInstance(), this);
   } catch (Throwable e) {
     //has exception, put exception error code
     tinkerResultIntent = new Intent();
     ShareIntentUtil.setIntentReturnCode(tinkerResultIntent, ShareConstants.ERROR_LOAD_PATCH_UNKNOWN_EXCEPTION);
     tinkerResultIntent.putExtra(INTENT_PATCH_EXCEPTION, e);
   }
 }

TinkerLoader.tryLoad

class TinkerLoader extends AbstractTinkerLoader {
  /**
 * only main process can handle patch version change or incomplete
 */
 @Override
 public Intent tryLoad(TinkerApplication app) {
   Log.d(TAG, "tryLoad test test");
   Intent resultIntent = new Intent();

   long begin = SystemClock.elapsedRealtime();
   tryLoadPatchFilesInternal(app, resultIntent);
   long cost = SystemClock.elapsedRealtime() - begin;
   ShareIntentUtil.setIntentPatchCostTime(resultIntent, cost);
   return resultIntent;
 }
  
private void tryLoadPatchFilesInternal(TinkerApplication app, Intent resultIntent) {
//data dir, such as /data/data/tinker.sample.android/tinker
File patchDirectoryFile = SharePatchFileUtil.getPatchDirectory(app);

//tinker
String patchDirectoryPath = patchDirectoryFile.getAbsolutePath();

//tinker/patch.info
File patchInfoFile = SharePatchFileUtil.getPatchInfoFile(patchDirectoryPath);
patchInfo = SharePatchInfo.readAndCheckPropertyWithLock(patchInfoFile, patchInfoLockFile);

boolean isRemoveNewVersion = patchInfo.isRemoveNewVersion;
 // So far new version is not loaded in main process and other processes.
 // We can remove new version directory safely.
 if (mainProcess && isRemoveNewVersion) {
   Log.w(TAG, "found clean patch mark and we are in main process, delete patch file now.");
   String patchName = SharePatchFileUtil.getPatchVersionDirectory(newVersion);
   if (patchName != null) {
     String patchVersionDirFullPath = patchDirectoryPath + "/" + patchName;
     SharePatchFileUtil.deleteDir(patchVersionDirFullPath);
     SharePatchInfo.rewritePatchInfoFileWithLock(patchInfoFile, patchInfo, patchInfoLockFile);
  }
}

//tinker/patch-641e634c
String patchVersionDirectory = patchDirectoryPath + "/" + patchName;
 File patchVersionDirectoryFile = new File(patchVersionDirectory);
 if (!patchVersionDirectoryFile.exists()) {
   //we may delete patch info file
   ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_VERSION_DIRECTORY_NOT_EXIST);
   return;
 }

//tinker/patch-641e634c/patch-641e634c.apk
 final String patchVersionFileRelPath = SharePatchFileUtil.getPatchVersionFile(version);
 File patchVersionFile = (patchVersionFileRelPath != null ? new File(patchVersionDirectoryFile.getAbsolutePath(), patchVersionFileRelPath) : null);

ShareSecurityCheck securityCheck = new ShareSecurityCheck(app);
 int returnCode = ShareTinkerInternals.checkTinkerPackage(app, tinkerFlag, patchVersionFile, securityCheck);
 if (returnCode != ShareConstants.ERROR_PACKAGE_CHECK_OK) {

//now we can load patch jar
 if (!isArkHotRuning && isEnabledForDex) {
   boolean loadTinkerJars = TinkerDexLoader.loadTinkerJars(app, patchVersionDirectory, oatDex, resultIntent, isSystemOTA, isProtectedApp);//main
 }
}

TinkerDexLoader.loadTinkerJars

/**
 \* Load tinker JARs and add them to
 \* the Application ClassLoader.
 */
 @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
public static boolean loadTinkerJars(final TinkerApplication application, String directory, String oatDir, Intent intentResult, boolean isSystemOTA, boolean isProtectedApp) {

 BaseDexClassLoader classLoader = (BaseDexClassLoader) TinkerDexLoader.class.getClassLoader();
 if (classLoader != null) {
   Log.i(TAG, "classloader: " + classLoader.toString());
 } else {
   Log.e(TAG, "classloader is null");
   ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_CLASSLOADER_NULL);
   return false;
 }

File optimizeDir = new File(directory + "/" + oatDir);//main
......
  try {
   SystemClassLoaderAdder.installDexes(application, classLoader, optimizeDir, legalFiles, isProtectedApp);//main
 } catch (Throwable e) {
   Log.e(TAG, "install dexes failed");
   intentResult.putExtra(ShareIntentUtil.INTENT_PATCH_EXCEPTION, e);
   ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_LOAD_EXCEPTION);
   return false;
 }
 return true;

SystemClassLoaderAdder.installDexes

public static void installDexes(Application application, BaseDexClassLoader loader, File dexOptDir, List<File> files, boolean isProtectedApp)
   throws Throwable {
   Log.i(TAG, "installDexes dexOptDir: " + dexOptDir.getAbsolutePath() + ", dex size:" + files.size());
 
   if (!files.isEmpty()) {
     files = createSortedAdditionalPathEntries(files);
     ClassLoader classLoader = loader;
     if (Build.VERSION.SDK_INT >= 24 && !isProtectedApp) {
       classLoader = AndroidNClassLoader.inject(loader, application);
     }
     //because in dalvik, if inner class is not the same classloader with it wrapper class.
     //it won't fail at dex2opt
     if (Build.VERSION.SDK_INT >= 23) {
       V23.install(classLoader, files, dexOptDir);//main
     } else if (Build.VERSION.SDK_INT >= 19) {
       V19.install(classLoader, files, dexOptDir);
     } else if (Build.VERSION.SDK_INT >= 14) {
       V14.install(classLoader, files, dexOptDir);
     } else {
       V4.install(classLoader, files, dexOptDir);
     }

//install done
 sPatchDexCount = files.size();
 Log.i(TAG, "after loaded classloader: " + classLoader + ", dex size:" + sPatchDexCount);

 if (!checkDexInstall(classLoader)) {
   //reset patch dex
   SystemClassLoaderAdder.uninstallPatchDex(classLoader);
   throw new TinkerRuntimeException(ShareConstants.CHECK_DEX_INSTALL_FAIL);
 }
/**
 \* Installer for platform versions 23.
 */
 private static final class V23 {

   private static void install(ClassLoader loader, List<File> additionalClassPathEntries,
                 File optimizedDirectory)
     throws IllegalArgumentException, IllegalAccessException,
     NoSuchFieldException, InvocationTargetException, NoSuchMethodException, IOException {
     /* The patched class loader is expected to be a descendant of
     \* dalvik.system.BaseDexClassLoader. We modify its
     \* dalvik.system.DexPathList pathList field to append additional DEX
     \* file entries.
     */
     Field pathListField = ShareReflectUtil.findField(loader, "pathList");
     Object dexPathList = pathListField.get(loader);
     ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
     ShareReflectUtil.expandFieldArray(dexPathList, "dexElements", makePathElements(dexPathList,
       new ArrayList<File>(additionalClassPathEntries), optimizedDirectory,
       suppressedExceptions));//main
     if (suppressedExceptions.size() > 0) {
       for (IOException e : suppressedExceptions) {
         Log.w(TAG, "Exception in makePathElement", e);
         throw e;
     }}}

   /**
   \* A wrapper around
   \* {@code private static final dalvik.system.DexPathList#makePathElements}.
   */
   private static Object[] makePathElements(
     Object dexPathList, ArrayList<File> files, File optimizedDirectory,
     ArrayList<IOException> suppressedExceptions)
     throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
     Method makePathElements;
     try {
       makePathElements = ShareReflectUtil.findMethod(dexPathList, "makePathElements", List.class, File.class,
         List.class);
     } catch (NoSuchMethodException e) {
       Log.e(TAG, "NoSuchMethodException: makePathElements(List,File,List) failure");
       try {
         makePathElements = ShareReflectUtil.findMethod(dexPathList, "makePathElements", ArrayList.class, File.class, ArrayList.class);
       } catch (NoSuchMethodException e1) {
         Log.e(TAG, "NoSuchMethodException: makeDexElements(ArrayList,File,ArrayList) failure");
         try {
           Log.e(TAG, "NoSuchMethodException: try use v19 instead");
           return V19.makeDexElements(dexPathList, files, optimizedDirectory, suppressedExceptions);
         } catch (NoSuchMethodException e2) {
           Log.e(TAG, "NoSuchMethodException: makeDexElements(List,File,List) failure");
           throw e2;
         }
       }
     }
     return (Object[]) makePathElements.invoke(dexPathList, files, optimizedDirectory, suppressedExceptions);
   }
 }

TinkerApplicationInlineFence.attachBaseContext

interface ITinkerInlineFenceBridge {
 void attachBaseContext(TinkerApplication app, Context base);
 void onCreate(TinkerApplication app);
 void onConfigurationChanged(Configuration newConfig);
 void onTrimMemory(int level);
 void onLowMemory();
 void onTerminate();
 ClassLoader getClassLoader(ClassLoader cl);
 Context getBaseContext(Context base);
 AssetManager getAssets(AssetManager assets);
 Resources getResources(Resources res);
 Object getSystemService(String name, Object service);
}
class TinkerApplicationInlineFence implements ITinkerInlineFenceBridge {
  private final Intent mTinkerResultIntent;
  private ApplicationLike mApplicationLike = null;
}
@Override
 public void attachBaseContext(TinkerApplication app, Context base) {
   attachBaseContextImpl_$noinline$(app, base);
 }
private void attachBaseContextImpl_$noinline$(TinkerApplication app, Context base) {
   try {
     dummyThrowExceptionMethod();
   } finally {
     ensureDelegate(app);//main
     if (mApplicationLike != null) {
       mApplicationLike.onBaseContextAttached(base);//main
     }
   }
 }
private synchronized void ensureDelegate(TinkerApplication app) {
   if (mApplicationLike == null) {
     mApplicationLike = (ApplicationLike) createDelegate(app);
   }
 }
private Object createDelegate(TinkerApplication app) {
   try {
     // Use reflection to create the delegate so it doesn't need to go into the primary dex.
     // And we can also patch it
     Class<?> delegateClass = Class.forName(mDelegateClassName, false, this.getClass().getClassLoader());
     Constructor<?> constructor = delegateClass.getConstructor(Application.class, int.class, boolean.class,
         long.class, long.class, Intent.class);
     return constructor.newInstance(app, mTinkerFlags, mTinkerLoadVerifyFlag,
         mApplicationStartElapsedTime, mApplicationStartMillisTime, mTinkerResultIntent);
   } catch (Throwable e) {
     throw new TinkerRuntimeException("createDelegate failed", e);
   }
 }

DefaultApplicationLike(…)

public DefaultApplicationLike(Application application, int tinkerFlags, boolean tinkerLoadVerifyFlag,
                              long applicationStartElapsedTime, long applicationStartMillisTime, Intent tinkerResultIntent) {
    super(application, tinkerFlags, tinkerLoadVerifyFlag, applicationStartElapsedTime, applicationStartMillisTime, tinkerResultIntent);
}

mApplicationLike.onBaseContextAttached

SampleApplicationLike类设计和原理

classDiagram
class ApplicationLike {
  +getApplication: Application
  +getTinkerResultIntent: Intent
}
ApplicationLifeCycle<|--ApplicationLike
ApplicationLike<|--DefaultApplicationLike
DefaultApplicationLike<|--SampleApplicationLike
graph LR
原先的自定义Application-->|代码迁移到|SampleApplicationLike-->|编译时生成注解中指定的|新的自定义Application-->|代理转发到|SampleApplicationLike
@interface DefaultLifeCycle {
  String application();
  String loaderClass() default "com.tencent.tinker.loader.TinkerLoader";
  int flags();
  boolean loadVerifyFlag() default false;
}
@DefaultLifeCycle(application = "tinker.sample.android.app.SampleApplication", //application name to generate
          flags = ShareConstants.TINKER_ENABLE_ALL,
          loadVerifyFlag = false)
class SampleApplicationLike {
 /**
 * install multiDex before install tinker
 * so we don't need to put the tinker lib classes in the main dex
 */
 @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
 @Override
 public void onBaseContextAttached(Context base) {
   super.onBaseContextAttached(base);
   //you must install multiDex whatever tinker is installed!
   MultiDex.install(base);

   SampleApplicationContext.application = getApplication();
   SampleApplicationContext.context = getApplication();
   TinkerManager.setTinkerApplicationLike(this);

   TinkerManager.initFastCrashProtect();
   //should set before tinker is installed
   TinkerManager.setUpgradeRetryEnable(true);

   //optional set logIml, or you can use default debug log
   TinkerInstaller.setLogIml(new MyLogImp());

   //installTinker after load multiDex
   //or you can put com.tencent.tinker.** to main dex
   TinkerManager.installTinker(this);//main
   Tinker tinker = Tinker.with(getApplication());
 }
}
public void registerActivityLifecycleCallbacks(Application.ActivityLifecycleCallbacks callback) {
   getApplication().registerActivityLifecycleCallbacks(callback);
 }

TinkerManager.installTinker

private static ApplicationLike        applicationLike;
 private static SampleUncaughtExceptionHandler uncaughtExceptionHandler;
 private static boolean isInstalled = false;

public static void setUpgradeRetryEnable(boolean enable) {
   UpgradePatchRetry.getInstance(applicationLike.getApplication()).setRetryEnable(enable);
 }

/**
 \* you can specify all class you want.
 \* sometimes, you can only install tinker in some process you want!
 *
 \* @param appLike
 */
 public static void installTinker(ApplicationLike appLike) {
   if (isInstalled) {
     TinkerLog.w(TAG, "install tinker, but has installed, ignore");
     return;
   }
   //or you can just use DefaultLoadReporter
   LoadReporter loadReporter = new SampleLoadReporter(appLike.getApplication());
   //or you can just use DefaultPatchReporter
   PatchReporter patchReporter = new SamplePatchReporter(appLike.getApplication());
   //or you can just use DefaultPatchListener
   PatchListener patchListener = new SamplePatchListener(appLike.getApplication());
   //you can set your own upgrade patch if you need
   AbstractPatch upgradePatchProcessor = new UpgradePatch();

   TinkerInstaller.install(appLike,
     loadReporter, patchReporter, patchListener,
     SampleResultService.class, upgradePatchProcessor);

   isInstalled = true;
 }

TinkerInstaller.install

public static Tinker install(ApplicationLike applicationLike, LoadReporter loadReporter, PatchReporter patchReporter,
               PatchListener listener, Class<? extends AbstractResultService> resultServiceClass,
               AbstractPatch upgradePatchProcessor) {

   Tinker tinker = new Tinker.Builder(applicationLike.getApplication())
     .tinkerFlags(applicationLike.getTinkerFlags())
     .loadReport(loadReporter)
     .listener(listener)
     .patchReporter(patchReporter)
     .tinkerLoadVerifyFlag(applicationLike.getTinkerLoadVerifyFlag()).build();

   Tinker.create(tinker);
   tinker.install(applicationLike.getTinkerResultIntent(), resultServiceClass, upgradePatchProcessor);
   return tinker;
 }

Tinker.install

public static class Builder {
public Builder(Context context) {
   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) {
     TinkerLog.e(TAG, "patchDirectory is null!");
     return;
   }
   this.patchInfoFile = SharePatchFileUtil.getPatchInfoFile(patchDirectory.getAbsolutePath());
   this.patchInfoLockFile = SharePatchFileUtil.getPatchInfoLockFile(patchDirectory.getAbsolutePath());
   TinkerLog.w(TAG, "tinker patch directory: %s", patchDirectory);
 }

/**create custom tinker by {@link Tinker.Builder}
 * please do it when very first your app start. */
 public static void create(Tinker tinker) {
   if (sInstance != null) {
     throw new TinkerRuntimeException("Tinker instance is already set.");
   }
   sInstance = tinker;
 }
public void install(Intent intentResult) {
   install(intentResult, DefaultTinkerResultService.class, new UpgradePatch());
 }

public void install(Intent intentResult, Class<? extends AbstractResultService> serviceClass,
           AbstractPatch upgradePatch) {
   sInstalled = true;
   TinkerPatchService.setPatchProcessor(upgradePatch, serviceClass);
 
   TinkerLog.i(TAG, "try to install tinker, isEnable: %b, version: %s", isTinkerEnabled(), ShareConstants.TINKER_VERSION);
 
   if (!isTinkerEnabled()) {
     TinkerLog.e(TAG, "tinker is disabled");
     return;
   }
   if (intentResult == null) {
     throw new TinkerRuntimeException("intentResult must not be null.");
   }
   tinkerLoadResult = new TinkerLoadResult();
   //从intentResult中获取loadTinker的结果tinkerResultIntent(位于TinkerApplication),这个结果经过反射构造TinkerApplicationInlineFence和SampleApplicationLike等最终传入到此处
  tinkerLoadResult.parseTinkerResult(getContext(), intentResult);//main
   //after load code set
   loadReporter.onLoadResult(patchDirectory, tinkerLoadResult.loadCode, tinkerLoadResult.costTime);//main
 
   if (!loaded) {
     TinkerLog.w(TAG, "tinker load fail!");
   }
 }
tinkerLoadResult.parseTinkerResult
public boolean parseTinkerResult(Context context, Intent intentResult) {
   Tinker tinker = Tinker.with(context);
   loadCode = ShareIntentUtil.getIntentReturnCode(intentResult);
   costTime = ShareIntentUtil.getIntentPatchCostTime(intentResult);
   systemOTA = ShareIntentUtil.getBooleanExtra(intentResult, ShareIntentUtil.INTENT_PATCH_SYSTEM_OTA, false);
   oatDir = ShareIntentUtil.getStringExtra(intentResult, ShareIntentUtil.INTENT_PATCH_OAT_DIR);
   useInterpretMode = ShareConstants.INTERPRET_DEX_OPTIMIZE_PATH.equals(oatDir);

final String oldVersion = ShareIntentUtil.getStringExtra(intentResult, ShareIntentUtil.INTENT_PATCH_OLD_VERSION);
final String newVersion = ShareIntentUtil.getStringExtra(intentResult, ShareIntentUtil.INTENT_PATCH_NEW_VERSION);

//found uncaught exception, just return
 Throwable exception = ShareIntentUtil.getIntentPatchException(intentResult);
 if (exception != null) {
   TinkerLog.i(TAG, "Tinker load have exception loadCode:%d", loadCode);
   int errorCode = ShareConstants.ERROR_LOAD_EXCEPTION_UNKNOWN;
   switch (loadCode) {
     case ShareConstants.ERROR_LOAD_PATCH_UNKNOWN_EXCEPTION:
       errorCode = ShareConstants.ERROR_LOAD_EXCEPTION_UNKNOWN;
       break;
     case ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_LOAD_EXCEPTION:
       errorCode = ShareConstants.ERROR_LOAD_EXCEPTION_DEX;
       break;
     case ShareConstants.ERROR_LOAD_PATCH_VERSION_RESOURCE_LOAD_EXCEPTION:
       errorCode = ShareConstants.ERROR_LOAD_EXCEPTION_RESOURCE;
       break;
     case ShareConstants.ERROR_LOAD_PATCH_UNCAUGHT_EXCEPTION:
       errorCode = ShareConstants.ERROR_LOAD_EXCEPTION_UNCAUGHT;
       break;
     default:
       break;
   }
   tinker.getLoadReporter().onLoadException(exception, errorCode);
   return false;
 }

switch (loadCode) {
case ShareConstants.ERROR_LOAD_OK:
   TinkerLog.i(TAG, "oh yeah, tinker load all success");
   tinker.setTinkerLoaded(true);
   // get load dex
   dexes = ShareIntentUtil.getIntentPatchDexPaths(intentResult);
   libs = ShareIntentUtil.getIntentPatchLibsPaths(intentResult);

   packageConfig = ShareIntentUtil.getIntentPackageConfig(intentResult);

   if (useInterpretMode) {
     tinker.getLoadReporter().onLoadInterpret(ShareConstants.TYPE_INTERPRET_OK, null);
   }
   if (isMainProcess && versionChanged) {
     //change the old version to new
     tinker.getLoadReporter().onLoadPatchVersionChanged(oldVersion, newVersion, patchDirectory, patchVersionDirectory.getName());
   }
   return true;

LoadReporter

DefaultLoadReporter

@Override
 public void onLoadException(Throwable e, int errorCode) {
   //for unCaught or dex exception, disable tinker all the time with sp
   switch (errorCode) {
     case ShareConstants.ERROR_LOAD_EXCEPTION_DEX:
       if (e.getMessage().contains(ShareConstants.CHECK_DEX_INSTALL_FAIL)) {
         TinkerLog.e(TAG, "patch loadReporter onLoadException: tinker dex check fail:" + e.getMessage());
       } else {
         TinkerLog.i(TAG, "patch loadReporter onLoadException: patch load dex exception: %s", e);
       }

ShareTinkerInternals.setTinkerDisableWithSharedPreferences(context);
 TinkerLog.i(TAG, "dex exception disable tinker forever with sp");
 break;

case ShareConstants.ERROR_LOAD_EXCEPTION_RESOURCE:
case ShareConstants.ERROR_LOAD_EXCEPTION_UNCAUGHT:
case ShareConstants.ERROR_LOAD_EXCEPTION_UNKNOWN:
......

 TinkerLog.e(TAG, "tinker load exception, welcome to submit issue to us: https://github.com/Tencent/tinker/issues");
 TinkerLog.printErrStackTrace(TAG, e, "tinker load exception");

 Tinker.with(context).setTinkerDisable();
 checkAndCleanPatch();
}

SampleLoadReporter

onLoadResult

@Override
 public void onLoadResult(File patchDirectory, int loadCode, long cost) {
   super.onLoadResult(patchDirectory, loadCode, cost);
   switch (loadCode) {
     case ShareConstants.ERROR_LOAD_OK:
       SampleTinkerReport.onLoaded(cost);
       break;
   }
   Looper.getMainLooper().myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
     @Override
     public boolean queueIdle() {
       if (UpgradePatchRetry.getInstance(context).onPatchRetryLoad()) {
         SampleTinkerReport.onReportRetryPatch();//main
       }
       return false;
     }
   });
 }

UpgradePatchRetry.onPatchRetryLoad

//UpgradePatchRetry
public boolean onPatchRetryLoad() {
   ......
   TinkerInstaller.onReceiveUpgradePatch(context, path);//main
}
//TinkerInstaller
/**
 * new patch file to install, try install them with :patch process
 */
 public static void onReceiveUpgradePatch(Context context, String patchLocation) {
   Tinker.with(context).getPatchListener().onPatchReceived(patchLocation);
 }

PatchListener

interface PatchListener {
   int onPatchReceived(String path);
}

DefaultPatchListener

onPatchReceived

/**
 \* when we receive a patch, what would we do?
 \* you can overwrite it
 \* */
 @Override
 public int onPatchReceived(String path) {
   final File patchFile = new File(path);
   final String patchMD5 = SharePatchFileUtil.getMD5(patchFile);
   final int returnCode = patchCheck(path, patchMD5);
   if (returnCode == ShareConstants.ERROR_PATCH_OK) {
     runForgService();
     TinkerPatchService.runPatchService(context, path);//main
   } else {
     Tinker.with(context).getLoadReporter().onLoadPatchListenerReceiveFail(new File(path), returnCode);
   }
   return returnCode;
 }

TinkerPatchService.runPatchService

class TinkerPatchService extends IntentService {
  public static void setPatchProcessor(AbstractPatch upgradePatch, Class<? extends AbstractResultService> serviceClass) {
   upgradePatchProcessor = upgradePatch;
   resultServiceClass = serviceClass;
   //try to load
   try {
     Class.forName(serviceClass.getName());
   } catch (ClassNotFoundException e) {
     TinkerLog.printErrStackTrace(TAG, e, "patch processor class not found.");
   }
 }
}
public static void runPatchService(final Context context, final String path) {
   TinkerLog.i(TAG, "run patch service...");
   Intent intent = new Intent(context, TinkerPatchService.class);
   intent.putExtra(PATCH_PATH_EXTRA, path);
   intent.putExtra(RESULT_CLASS_EXTRA, resultServiceClass.getName());
   try {
     context.startService(intent);
   } catch (Throwable thr) {
     TinkerLog.e(TAG, "run patch service fail, exception:" + thr);
   }
 }
@Override
 protected void onHandleIntent(@Nullable Intent intent) {
   increasingPriority();
   doApplyPatch(this, intent);
 }

private static void doApplyPatch(Context context, Intent intent) {
 Tinker tinker = Tinker.with(context);
 tinker.getPatchReporter().onPatchServiceStart(intent);//超过maxRetryCount会清除patch,最大重试20次

 PatchResult patchResult = new PatchResult();
 try {
   if (upgradePatchProcessor == null) {
     throw new TinkerRuntimeException("upgradePatchProcessor is null.");
   }
   result = upgradePatchProcessor.tryPatch(context, path, patchResult);//main
 } catch (Throwable throwable) {
   e = throwable;
   result = false;
   tinker.getPatchReporter().onPatchException(patchFile, e);
 }

cost = SystemClock.elapsedRealtime() - begin;
 tinker.getPatchReporter()
     .onPatchResult(patchFile, result, cost);
 
 patchResult.isSuccess = result;
 patchResult.rawPatchFilePath = path;
 patchResult.costTime = cost;
 patchResult.e = e;
 
 AbstractResultService.runResultService(context, patchResult, getPatchResultExtra(intent));//main
 
 sIsPatchApplying.set(false);

PatchReporter

DefaultPatchReporter.onPatchServiceStart

/************************************ :patch process below ***************************************/
 /**
 \* use for report or some work at the beginning of TinkerPatchService
 \* {@code TinkerPatchService.onHandleIntent} begin
 */
 @Override
 public void onPatchServiceStart(Intent intent) {
   TinkerLog.i(TAG, "patchReporter onPatchServiceStart: patch service start");
   shouldRetry = false;
   UpgradePatchRetry.getInstance(context).onPatchServiceStart(intent);
 }

UpgradePatchRetry.onPatchServiceStart

public void onPatchServiceStart(Intent intent) {
String path = TinkerPatchService.getPatchPathExtra(intent);
File patchFile = new File(path);
String patchMd5 = SharePatchFileUtil.getMD5(patchFile);
 if (retryInfoFile.exists()) {
   retryInfo = RetryInfo.readRetryProperty(retryInfoFile);
   if (retryInfo.md5 == null || retryInfo.times == null || !patchMd5.equals(retryInfo.md5)) {
     copyToTempFile(patchFile);//main
     retryInfo.md5 = patchMd5;
     retryInfo.times = "1";
   } else {
     int nowTimes = Integer.parseInt(retryInfo.times);
     if (nowTimes >= maxRetryCount) {
       SharePatchFileUtil.safeDeleteFile(tempPatchFile);
       TinkerLog.w(TAG, "onPatchServiceStart retry more than max count, delete retry info file!");
       return;
     } else {
       retryInfo.times = String.valueOf(nowTimes + 1);
     }
   }
 } else {
   copyToTempFile(patchFile);//main
   retryInfo = new RetryInfo(patchMd5, "1");
 }
 RetryInfo.writeRetryProperty(retryInfoFile, retryInfo);

UpgradePatch.tryPatch

@Override
 public boolean tryPatch(Context context, String tempPatchPath, PatchResult patchResult) {

//we use destPatchFile instead of patchFile, because patchFile may be deleted during the patch process
 if (!DexDiffPatchInternal.tryRecoverDexFiles(manager, signatureCheck, context, patchVersionDirectory, destPatchFile)) {//main
   TinkerLog.e(TAG, "UpgradePatch tryPatch:new patch recover, try patch dex failed");
   return false;
 }
 
 if (!ArkHotDiffPatchInternal.tryRecoverArkHotLibrary(manager, signatureCheck,
     context, patchVersionDirectory, destPatchFile)) {
   return false;
 }
 
 if (!BsDiffPatchInternal.tryRecoverLibraryFiles(manager, signatureCheck, context, patchVersionDirectory, destPatchFile)) {
   TinkerLog.e(TAG, "UpgradePatch tryPatch:new patch recover, try patch library failed");
   return false;
 }
 
 if (!ResDiffPatchInternal.tryRecoverResourceFiles(manager, signatureCheck, context, patchVersionDirectory, destPatchFile)) {
   TinkerLog.e(TAG, "UpgradePatch tryPatch:new patch recover, try patch resource failed");
   return false;
 }

DexDiffPatchInternal

protected static boolean tryRecoverDexFiles(Tinker manager, ShareSecurityCheck checker, Context context,
                       String patchVersionDirectory, File patchFile) {

boolean result = patchDexExtractViaDexDiff(context, patchVersionDirectory, dexMeta, patchFile);

}

private static boolean patchDexExtractViaDexDiff(Context context, String patchVersionDirectory, String meta, final File patchFile) {
   String dir = patchVersionDirectory + "/" + DEX_PATH + "/";
   if (!extractDexDiffInternals(context, dir, meta, patchFile, TYPE_DEX)) {//main
     TinkerLog.w(TAG, "patch recover, extractDiffInternals fail");
     return false;
   }

final String optimizeDexDirectory = patchVersionDirectory + "/" + DEX_OPTIMIZE_PATH + "/";
 return dexOptimizeDexFiles(context, legalFiles, optimizeDexDirectory, patchFile);//main

patchDexFile,复用DexPatchApplier.executeAndSaveTo

private static boolean extractDexDiffInternals(Context context, String dir, String meta, File patchFile, int type) {
   patchList.clear();
   ShareDexDiffPatchInfo.parseDexDiffPatchInfo(meta, patchList);

//check source crc instead of md5 for faster
 String rawEntryCrc = String.valueOf(rawApkFileEntry.getCrc());
 if (!rawEntryCrc.equals(oldDexCrc)) {
   TinkerLog.e(TAG, "apk entry %s crc is not equal, expect crc: %s, got crc: %s", patchRealPath, oldDexCrc, rawEntryCrc);
   manager.getPatchReporter().onPatchTypeExtractFail(patchFile, extractedFile, info.rawName, type);
   return false;
 }

  patchDexFile(apk, patch, rawApkFileEntry, patchFileEntry, info, extractedFile);

//patch合成之后比对(合成后的dex文件)和(生成patch之后立刻合成时得到的relatedInfo.newOrFullPatchedMd5)是否一致
//位于assets/dex_meta.txt中的kv[3],产生于logDexesToDexMeta--logToDexMeta
//String meta = fileName + "," + parentRelative + "," + destMd5InDvm + ","
 //  + destMd5InArt + "," + dexDiffMd5 + "," + oldCrc + "," + newOrFullPatchedCrc + "," + dexMode;

if (!SharePatchFileUtil.verifyDexFileMd5(extractedFile, extractedFileMd5)) {
   TinkerLog.w(TAG, "Failed to recover dex file when verify patched dex: " + extractedFile.getPath());
   manager.getPatchReporter().onPatchTypeExtractFail(patchFile, extractedFile, info.rawName, type);
   SharePatchFileUtil.safeDeleteFile(extractedFile);
   return false;
 }
private static void patchDexFile(
   ZipFile baseApk, ZipFile patchPkg, ZipEntry oldDexEntry, ZipEntry patchFileEntry,
   ShareDexDiffPatchInfo patchInfo, File patchedDexFile) throws IOException {
	new DexPatchApplier(zis, patchFileStream).executeAndSaveTo(zos);//main
}

dexOptimizeDexFiles

//DexDiffPatchInternal
private static boolean dexOptimizeDexFiles(Context context, List<File> dexFiles, String optimizeDexDirectory, final File patchFile) {
//try parallel dex optimizer
 TinkerDexOptimizer.optimizeAll(
   context, dexFiles, optimizeDexDirectoryFile,
   new TinkerDexOptimizer.ResultCallback() {...

TinkerDexOptimizer

public static boolean optimizeAll(Context context, Collection<File> dexFiles, File optimizedDir,
                  boolean useInterpretMode, String targetISA, ResultCallback cb) {
for (File dexFile : sortList) {
   OptimizeWorker worker = new OptimizeWorker(context, dexFile, optimizedDir, useInterpretMode, targetISA, cb);
   if (!worker.run()) {
     return false;
   }
 }
 return true;
private static class OptimizeWorker {

boolean run() {

String optimizedPath = SharePatchFileUtil.optimizedPathFor(this.dexFile, this.optimizedDir);
 if (!ShareTinkerInternals.isArkHotRuning()) {
   if (useInterpretMode) {//默认false
    interpretDex2Oat(dexFile.getAbsolutePath(), optimizedPath);//main
   } else if (Build.VERSION.SDK_INT >= 28
       || (Build.VERSION.SDK_INT >= 27 && Build.VERSION.PREVIEW_SDK_INT != 0)) {
     NewClassLoaderInjector.triggerDex2Oat(context, dexFile.getAbsolutePath());//main
   } else {
     DexFile.loadDex(dexFile.getAbsolutePath(), optimizedPath, 0);//main
   }
 }
interpretDex2Oat
private void interpretDex2Oat(String dexFilePath, String oatFilePath) throws IOException {
                final List<String> commandAndParams = new ArrayList<>();
                commandAndParams.add("dex2oat");
                // for 7.1.1, duplicate class fix
                if (Build.VERSION.SDK_INT >= 24) {
                    commandAndParams.add("--runtime-arg");
                    commandAndParams.add("-classpath");
                    commandAndParams.add("--runtime-arg");
                    commandAndParams.add("&");
                }
                commandAndParams.add("--dex-file=" + dexFilePath);
                commandAndParams.add("--oat-file=" + oatFilePath);
                commandAndParams.add("--instruction-set=" + targetISA);
                if (Build.VERSION.SDK_INT > 25) {
                    commandAndParams.add("--compiler-filter=quicken");
                } else {
                    commandAndParams.add("--compiler-filter=interpret-only");
                }

                final ProcessBuilder pb = new ProcessBuilder(commandAndParams);
                pb.redirectErrorStream(true);
                final Process dex2oatProcess = pb.start();
                StreamConsumer.consumeInputStream(dex2oatProcess.getInputStream());
                StreamConsumer.consumeInputStream(dex2oatProcess.getErrorStream());
                try {
                    final int ret = dex2oatProcess.waitFor();
                    if (ret != 0) {
                        throw new IOException("dex2oat works unsuccessfully, exit code: " + ret);
                    }
                }
}
triggerDex2Oat
//NewClassLoaderInjector
public static void triggerDex2Oat(Context context, File dexOptDir, boolean useDLCOnAPI29AndAbove,
                                  String... dexPaths) throws Throwable {
    ClassLoader triggerClassLoader;
    if (useDLCOnAPI29AndAbove && Build.VERSION.SDK_INT >= 29) {
        triggerClassLoader = createNewClassLoader(context.getClassLoader(), dexOptDir, useDLCOnAPI29AndAbove, dexPaths);
    } else {
        // Suggestion from Huawei: Only PathClassLoader (Perhaps other ClassLoaders known by system
        // like DexClassLoader also works ?) can be used here to trigger dex2oat so that JIT
        // mechanism can participate in runtime Dex optimization.
        final StringBuilder sb = new StringBuilder();
        boolean isFirst = true;
        for (String dexPath : dexPaths) {
            if (isFirst) {
                isFirst = false;
            } else {
                sb.append(File.pathSeparator);
            }
            sb.append(dexPath);
        }
        triggerClassLoader = new PathClassLoader(sb.toString(), ClassLoader.getSystemClassLoader());
    }
}
DexFile.loadDex

……

AbstractResultService.runResultService

class AbstractResultService extends IntentService {
  
  public static void runResultService(Context context, PatchResult result, String resultServiceClass) {
   if (resultServiceClass == null) {
     throw new TinkerRuntimeException("resultServiceClass is null.");
   }
   try {
     Intent intent = new Intent();
     intent.setClassName(context, resultServiceClass);
     intent.putExtra(RESULT_EXTRA, result);
     context.startService(intent);
   } catch (Throwable throwable) {
     TinkerLog.e(TAG, "run result service fail, exception:" + throwable);
   }
 }

protected void onHandleIntent(Intent intent) {
   PatchResult result = (PatchResult) ShareIntentUtil.getSerializableExtra(intent, RESULT_EXTRA);
   onPatchResult(result);//main
 }

}

DefaultTinkerResultService.onPatchResult

/**
 \* we may want to use the new patch just now!!
 */
 @Override
 public void onPatchResult(PatchResult result) {
   if (result == null) {
     TinkerLog.e(TAG, "DefaultTinkerResultService received null result!!!!");
     return;
   }
   TinkerLog.i(TAG, "DefaultTinkerResultService received a result:%s ", result.toString());

   //first, we want to kill the recover process
   TinkerServiceInternals.killTinkerPatchServiceProcess(getApplicationContext());

   // if success and newPatch, it is nice to delete the raw file, and restart at once
   // only main process can load an upgrade patch!
   if (result.isSuccess) {
     deleteRawPatchFile(new File(result.rawPatchFilePath));
     if (checkIfNeedKill(result)) {
       android.os.Process.killProcess(android.os.Process.myPid());
     } else {
       TinkerLog.i(TAG, "I have already install the newly patch version!");
     }
   }
 }

SampleResultService.onPatchResult

@Override
 public void onPatchResult(final PatchResult result) {

//first, we want to kill the recover process
 TinkerServiceInternals.killTinkerPatchServiceProcess(getApplicationContext());

Handler handler = new Handler(Looper.getMainLooper());
 handler.post(new Runnable() {
   @Override
   public void run() {
     if (result.isSuccess) {
       Toast.makeText(getApplicationContext(), "patch success, please restart process", Toast.LENGTH_LONG).show();
     } else {
       Toast.makeText(getApplicationContext(), "patch fail, please check reason", Toast.LENGTH_LONG).show();
     }
   }
 });

// is success and newPatch, it is nice to delete the raw file, and restart at once
 // for old patch, you can't delete the patch file
 if (result.isSuccess) {
   deleteRawPatchFile(new File(result.rawPatchFilePath));

   //not like TinkerResultService, I want to restart just when I am at background!
   //if you have not install tinker this moment, you can use TinkerApplicationHelper api
   if (checkIfNeedKill(result)) {
     if (Utils.isBackground()) {
       TinkerLog.i(TAG, "it is in background, just restart process");
       restartProcess();
     } else {
       //we can wait process at background, such as onAppBackground
       //or we can restart when the screen off
       TinkerLog.i(TAG, "tinker wait screen to restart process");
       new Utils.ScreenState(getApplicationContext(), new Utils.ScreenState.IOnScreenOff() {
         @Override
         public void onScreenOff() {
           restartProcess();
         }
       });
     }
   } else {
     TinkerLog.i(TAG, "I have already install the newly patch version!");
   }
 }
 
 /**
 \* you can restart your process through service or broadcast
 */
 private void restartProcess() {
   TinkerLog.i(TAG, "app is background now, i can kill quietly");
   //you can send service or broadcast intent to restart your process
   android.os.Process.killProcess(android.os.Process.myPid());
 }

其他

Tinker

cleanPatch

/**clean all patch files */
 public void cleanPatch() {
   final File patchInfoFile = SharePatchFileUtil.getPatchInfoFile(patchDirectory.getAbsolutePath());
   final File patchInfoLockFile = SharePatchFileUtil.getPatchInfoLockFile(patchDirectory.getAbsolutePath());
   final SharePatchInfo patchInfo = SharePatchInfo.readAndCheckPropertyWithLock(patchInfoFile, patchInfoLockFile);
   if (patchInfo != null) {
     patchInfo.isRemoveNewVersion = true;//标记目录:/data/data/${packageName}/tinker/patch.info中的标记信息,下次loadTinker时会根据这个标记清除对应patch目录信息
/*patchinfo.oldVersion表示当前正在使用的已经apply/load完成之后的patch
patchinfo.newVersion表示当前已经成功patch,但还没有apply完成的patch*/
    SharePatchInfo.rewritePatchInfoFileWithLock(patchInfoFile, patchInfo, patchInfoLockFile);
   }
 }

重试patch

@Override
public void onLoadResult(File patchDirectory, int loadCode, long cost) {
    super.onLoadResult(patchDirectory, loadCode, cost);
    switch (loadCode) {
        case ShareConstants.ERROR_LOAD_OK:
            SampleTinkerReport.onLoaded(cost);
            break;
    }
    Looper.myQueue().addIdleHandler(() -> {
        if (UpgradePatchRetry.getInstance(context).onPatchRetryLoad()) {
            SampleTinkerReport.onReportRetryPatch();
        }
        return false;
    });
}
UpgradePatchRetry::onPatchRetryLoad

test.dex检测流程

构建出的patch会包含test.dex文件,内部符合dex文件结构,仅仅包含一个class“com.tencent.tinker.loader.TinkerTestDexLoad”,其静态检测方法返回true,用于在install之后进行SystemClassLoaderAdder.checkDexInstall校验,以判别是否成功插入修复的dex文件

tinker_data

位置:(work/Desktop20200621/)

patch过程会将patch文件放入/data/data/${applicationId}/tinker/,目录结构为

tinker–patch.info

​ –info.lock

​ –patch332ed5cc–res–

​ –dex–tinker_classN.apk(内含多个dex,其中既有test.dex重命名之后的dex)

​ –odex–tinker_classN.dex

​ patch-332ed5cc.apk