Android Hot Fix Tinker(1) Application Renovation

Keywords: Android Gradle Java less

Based on Tinker V1.7.5

This article focuses on Tinker Isolating Applications. For reasons why do you want to isolate Applications, refer to the previous article. Analysis of Android Hot Repair Scheme The Qzone scheme described in this article injects a class reference in a separate dex into all classes except the Application subclass to avoid the CLASS_ISPREVERIFIED tag being placed on the class. Since this independent dex was loaded after the Application was started, the Application subclass cannot insert a reference to a separate dex and cannot be repaired. Moreover, since the Application subclass is marked CLASS_ISPREVERIFIEDISPREVERIFIED tag, then the object directly referenced by the Application subclass cannot be patched, throwing a pre-verified exception.

What if you want to fix as many classes as possible?The solution for the Qzone scenario is to pull the logic from the Application subclass into a separate class such as Application Proxy, which is referenced only by the Application Proxy class to reduce the impact. Tinker uses full updates, and patches are loaded in the Application, so Tinker's Application cannot be modified. Also, Tinker's full updates are not possible.It is not compatible with hardening schemes. If app uses hardening, it can use usePreGeneratedPatchDex mode to revert back to the Qzone scheme, which requires the same problem.

Also because Android N Mixed Compilation and Analysis of the Impact on Hot Patches This causes the class to be repaired to be cached in App In image, classes in App image are inserted into PathClassLoader, and PathClassLoader does not replace cached classes when loading patches, which ultimately results in some classes being loaded from base.apk and others from patch.dex in case of full updates. The solution to throwing out IllegalAccessError.Tinker is to overwrite PathClassLoader at run time to load classes so that App can The cache in the image is invalid. However, the entry class Application can only be loaded by the PathClassLoader of the system, and from this point of view, it also needs to be decoupled by the Application.

Application Isolation

Tinker provides two ways to isolate applications, DefaultLifeCycle annotation and inheritance of TinkerApplication,DefaultApplicationLike. It is recommended to isolate applications using DefaultLifeCycle annotation, which compiles the auto-generated applications and reduces some misoperation. By inheriting TinkerApplication,DefaultApplicationLike actually writes the code generated by the annotation itself.In this article only the way notes are presented.

@DefaultLifeCycle(
        application = "com.test.MyApplication",
        flags = ShareConstants.TINKER_ENABLE_ALL,
        loadVerifyFlag = false
)
public class ApplicationLike extends DefaultApplicationLike {
    public MyApplicationLike(Application application, int tinkerFlags, boolean tinkerLoadVerifyFlag,
                                 long applicationStartElapsedTime, long applicationStartMillisTime, Intent tinkerResultIntent,
                                 Resources[] resources, ClassLoader[] classLoader, AssetManager[] assetManager) {
        super(application, tinkerFlags, tinkerLoadVerifyFlag, 
                applicationStartElapsedTime, applicationStartMillisTime, tinkerResultIntent, 
                resources, classLoader, assetManager);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

When the comment is executed during the compilation process, the actual Application is generated in the set package path Java File, which inherits TinkerApplication by default. Passes the data configured in the comment to the constructor's parameters. It is then compiled into. class and packaged into a dex file.

public class MyApplication extends TinkerApplication {

    public MyApplication() {
        super(7, "cn.jesse.patchersample.ApplicationLike", "com.tencent.tinker.loader.TinkerLoader", false);
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

The DefaultLifeCycle annotation contains four properties: the true application path, the path to the patch loader (loaderClass), the patch supporting different file formats flag(flags), and whether the flag(loadVerifyFlag) of the patch package MD5 is checked each time.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
@Inherited
public @interface DefaultLifeCycle {

    /**
     * Real Application
     */
    String application();

    /**
     * patch loader
     */
    String loaderClass() default "com.tencent.tinker.loader.TinkerLoader";

    /**
     * Supported file types
     * ShareConstants.TINKERDISABLE:No type of file is supported
     * ShareConstants.TINKERDEXONLY:Only dex files are supported
     * ShareConstants.TINKERLIBRARYONLY:Only library files are supported
     * ShareConstants.TINKERDEXANDLIBRARY:Only dex and res modifications are supported
     * ShareConstants.TINKERENABLEALL:Supports any type of file and is our usual mode of setup
     */
    int flags();

    /**
     * Whether the patch pack MD5 is checked every time
     */
    boolean loadVerifyFlag() default false;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

In Annotation Processor, the real Application is generated based on the parameters of the annotation and the actual Application template.

stay Android The annotation module in studio must be Java  If you need to debug this part of the code, you can configure gradle --deamon and breakpoint to debug.

DefaultLifeCycle ca = e.getAnnotation(DefaultLifeCycle.class);

//Get the name and package name of the proxy class
String lifeCycleClassName = ((TypeElement) e).getQualifiedName().toString();
String lifeCyclePackageName = lifeCycleClassName.substring(0, lifeCycleClassName.lastIndexOf('.'));
lifeCycleClassName = lifeCycleClassName.substring(lifeCycleClassName.lastIndexOf('.') + 1);

//Assemble the actual Application class name
String applicationClassName = ca.application();
if (applicationClassName.startsWith(".")) {
    applicationClassName = lifeCyclePackageName + applicationClassName;
}

//Split out the actual Application class name and package name
String applicationPackageName = applicationClassName.substring(0, applicationClassName.lastIndexOf('.'));
applicationClassName = applicationClassName.substring(applicationClassName.lastIndexOf('.') + 1);

//Get the name of the patch loader
String loaderClassName = ca.loaderClass();
if (loaderClassName.startsWith(".")) {
    loaderClassName = lifeCyclePackageName + loaderClassName;
}

//Read the Application template file and replace all the%KEY% placeholders in the template with real data
final InputStream is = AnnotationProcessor.class.getResourceAsStream(APPLICATION_TEMPLATE_PATH);
final Scanner scanner = new Scanner(is);
final String template = scanner.useDelimiter("\\A").next();
final String fileContent = template
    .replaceAll("%PACKAGE%", applicationPackageName)
    .replaceAll("%APPLICATION%", applicationClassName)
    .replaceAll("%APPLICATION_LIFE_CYCLE%", lifeCyclePackageName + "." + lifeCycleClassName)
    .replaceAll("%TINKER_FLAGS%", "" + ca.flags())
    .replaceAll("%TINKER_LOADER_CLASS%", "" + loaderClassName)
    .replaceAll("%TINKER_LOAD_VERIFY_FLAG%", "" + ca.loadVerifyFlag());

//Write the complete Application code to a file in the same path as the proxy Application
// The annotation is now complete to generate a real Application
try {
    JavaFileObject fileObject = processingEnv.getFiler().createSourceFile(applicationPackageName + "." + applicationClassName);
    processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "Creating " + fileObject.toUri());
    Writer writer = fileObject.openWriter();
    try {
        PrintWriter pw = new PrintWriter(writer);
        pw.print(fileContent);
        pw.flush();

    } finally {
        writer.close();
    }
} catch (IOException x) {
    processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, x.toString());
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52

The Application template replaces some of the changed attributes with%KEY% containing the package name, class name, constructor name, tinker_flag, proxy Application, patcher loader, and patch check bits. The package name, application name, and constructor parameters can be populated based on the key at annotation compilation time.

package %PACKAGE%;

import com.tencent.tinker.loader.app.TinkerApplication;

/**
 *
 * Generated application for tinker life cycle
 *
 */
public class %APPLICATION% extends TinkerApplication {

    public %APPLICATION%() {
        super(%TINKER_FLAGS%, "%APPLICATION_LIFE_CYCLE%", "%TINKER_LOADER_CLASS%", %TINKER_LOAD_VERIFY_FLAG%);
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

Application-related dependencies

The DefaultLifeCycle annotation separates the real Application from the proxy Application. As to how they synchronize the declaration cycle, how they establish dependencies, and what their responsibilities are, take a look at the class diagram in this section.

Real Application

Do the initialization work at the attachBaseContext, the method that first gets the context. Like time statistics, use reflection to build the TinkerLoader object and call the tryLoad method (load the patch after checking the environment), reflection to create the proxy Application object, synchronize the Application lifecycle, and reset safe Model count.

//Record system startup time and App startup time
applicationStartElapsedTime = SystemClock.elapsedRealtime();
applicationStartMillisTime = System.currentTimeMillis();

//Initialize the patch loader and call the tryLoad method
loadTinker();
//Ensure that the proxy Application object has been created
ensureDelegate();

//Reflect Call Agent Application's `onBaseContextAttached`Method Synchronization Life Cycle
try {
    Method method = ShareReflectUtil.findMethod(delegate, "onBaseContextAttached", Context.class);
    method.invoke(delegate, base);
} catch (Throwable t) {
    throw new TinkerRuntimeException("onBaseContextAttached method not found", t);
}

//Reset the safe model count, Patcher ResultIntent records patch failures when the safe model count is no less than three times
if (useSafeMode) {
    String processName = ShareTinkerInternals.getProcessName(this);
    String preferName = ShareConstants.TINKER_OWN_PREFERENCE_CONFIG + processName;
    SharedPreferences sp = getSharedPreferences(preferName, Context.MODE_PRIVATE);
    sp.edit().putInt(ShareConstants.TINKER_SAFE_MODE_COUNT, 0).commit();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

Proxy Application

Because you want to proxy the real Application with ApplicationLike, ApplicationLike needs to hold the Application. Resources, classLoader, reference to assetManager. The tinkerResultIntent property records the status of TinkerLoader starting and loading patch es.

//References to Application,resource,assetManager,classLoader
private final Application application;
private Resources[]    resources;
private ClassLoader[]  classLoader;
private AssetManager[] assetManager;

//Used to record the status of the startup load patcher loader
private final Intent tinkerResultIntent;

//System Lifetime and app Startup Time
private final long applicationStartElapsedTime;
private final long applicationStartMillisTime;

//Two flags in the comment
private final int tinkerFlags;
private final boolean tinkerLoadVerifyFlag;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

It also exposes common Application Common methods for external use.

void onCreate();
void onLowMemory();
void onTrimMemory(int level);
void onTerminate();
void onConfigurationChanged(Configuration newConfig);
void onBaseContextAttached(Context base);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

The analysis of the Application modification is now complete. The tryLoad method that invokes TinkerLoader through reflection in the attachBaseContext of the Application is mentioned above. The tryLoad method is the entry to load the final patch package. The next article will continue along this line.

For reprinting, please indicate the source: http://blog.csdn.net/l2show/article/details/53187548

Posted by crusty76 on Fri, 21 Jun 2019 11:21:38 -0700