Android 热修复方案Tinker(一) Application改造

基于Tinker V1.7.5

这篇文章主要分析一下Tinker隔离Application.至于为什么要隔离Application?可以参考上一篇 Android 热修复方案分析文章中说到的Qzone方案,要给除了Application子类所有的类注入一个独立dex中的类引用,来避免class被打上CLASS_ISPREVERIFIED标记.而这个独立的dex是在Application启动之后加载的,所以Application子类就不能插入独立dex的引用也就不能进行修复.不仅如此,由于Application子类被打上CLASS_ISPREVERIFIED标记,那么Application子类直接引用的对象就无法打补丁包,会抛出pre-verified异常.

在这种前提下还是想修复尽可能多的类怎么办?对于Qzone方案的解决方法就是把Application子类中的逻辑都抽离到一个独立的类例如ApplicationProxy中,Application只引用这个ApplicationProxy类从而减小影响范围.Tinker使用的是全量更新,补丁也是在Application中加载的,所以Tinker的Application也是不能修改的.并且由于Tinker的全量更新不能兼容加固方案,如果app使用了加固的话可以使用usePreGeneratedPatchDex模式回退到Qzone方案上,这样的话就需要面临同样的问题.

同时还因为Android N混合编译与对热补丁影响解析,这会造成要修复的class被缓存在App image中,App image中的class会插入PathClassLoader中,而PathClassLoader 加载补丁的时候不会替换缓存的class,最终会导致在全量更新的情况下部分类是从base.apk中加载,部分类是从patch.dex中加载,抛出IllegalAccessError.Tinker的解决方案是在运行时改写PathClassLoader来加载类,让App image中的缓存失效.而Application这个入口类还是只能用系统的PathClassLoader来加载,从这个角度来说也需要Application解耦出来.

Application 隔离

Tinker提供了两种方式来隔离Application,分别是DefaultLifeCycle注解和继承TinkerApplication,DefaultApplicationLike.推荐使用DefaultLifeCycle注解来隔离Application,这种方式会编译自动生成Application,减少一些误操作.而继承TinkerApplication,DefaultApplicationLike其实就是自己写出注解生成的代码,所以这篇文章就只介绍注解的方式.

@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);
    }
}

在编译的过程时注解执行完毕之后就会在设定的包路径下生成出真实的Application java文件,默认继承TinkerApplication.将注解中配置的数据传递给构造函数的参数.然后被编译成.class打包到dex文件中.

public class MyApplication extends TinkerApplication {

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

}

DefaultLifeCycle注解包含四个属性,分别是真实的application路径(application), 补丁loader的路径(loaderClass), 补丁支持不同文件格式flag(flags), 是否每次都校验补丁包MD5的flag(loadVerifyFlag).

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

    /**
     * 真实的Application
     */
    String application();

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

    /**
     * 支持的文件类型
     * ShareConstants.TINKERDISABLE:不支持任何类型的文件
     * ShareConstants.TINKERDEXONLY:只支持dex文件
     * ShareConstants.TINKERLIBRARYONLY:只支持library文件
     * ShareConstants.TINKERDEXANDLIBRARY:只支持dex与res的修改
     * ShareConstants.TINKERENABLEALL:支持任何类型的文件,也是我们通常的设置的模式
     */
    int flags();

    /**
     * 是否每次都校验补丁包的MD5
     */
    boolean loadVerifyFlag() default false;
}

在注解处理器AnnotationProcessor中,根据注解的参数和真实的Application模板,生成真正的Application.

在Android studio中 annotation module必须是java library否则在处理anno的时候会找不到AbstractProcessor.如果需要调试这部分代码的话可以通过配置gradle --deamon和加断点来debug.

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

//拿到代理类的名称和包名
String lifeCycleClassName = ((TypeElement) e).getQualifiedName().toString();
String lifeCyclePackageName = lifeCycleClassName.substring(0, lifeCycleClassName.lastIndexOf('.'));
lifeCycleClassName = lifeCycleClassName.substring(lifeCycleClassName.lastIndexOf('.') + 1);

//拼装出真实的Application类名称
String applicationClassName = ca.application();
if (applicationClassName.startsWith(".")) {
    applicationClassName = lifeCyclePackageName + applicationClassName;
}

//拆分出真实的Application类名称和包名
String applicationPackageName = applicationClassName.substring(0, applicationClassName.lastIndexOf('.'));
applicationClassName = applicationClassName.substring(applicationClassName.lastIndexOf('.') + 1);

//拿到patch loader的名称
String loaderClassName = ca.loaderClass();
if (loaderClassName.startsWith(".")) {
    loaderClassName = lifeCyclePackageName + loaderClassName;
}

//读取Application模板文件,将模板中的%KEY%占位符全部替换成真实的数据
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());

//将完整的Application代码写入到跟代理Application的相同路径下的文件中
// 至此注解生成真实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());
}

Application模板是把一些变化的属性通过%KEY%的形式代替其中包含文件包名,类名,构造函数名,tinker_flag, 代理Application, patcher loader, 和补丁校验位.在注解编译时就可以根据key对包名,Application名和构造函数的参数进行填充.

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%);
    }

}

Application 相关依赖

用了DefaultLifeCycle注解之后就把真实的Application和代理Application隔离开了.至于两者之间是如何同步声明周期,如何建立依赖关系,以及他们的职责是什么?可以看一下这部分的类图.

真实Application

在最早能获取context的方法attachBaseContext处做相关的初始化工作.像时间统计,利用反射将TinkerLoader对象构建出来并调用tryLoad方法(校验环境后加载补丁),反射创建出代理Application对象,同步Application生命周期和重置safe mode计数.

//记录系统启动时间和App启动时刻
applicationStartElapsedTime = SystemClock.elapsedRealtime();
applicationStartMillisTime = System.currentTimeMillis();

//初始化patch loader, 并且调用tryLoad方法
loadTinker();
//确保代理Application对象已经被创建出来了
ensureDelegate();

//反射调用代理Application的`onBaseContextAttached`方法同步生命周期
try {
    Method method = ShareReflectUtil.findMethod(delegate, "onBaseContextAttached", Context.class);
    method.invoke(delegate, base);
} catch (Throwable t) {
    throw new TinkerRuntimeException("onBaseContextAttached method not found", t);
}

//重置safe mode计数, 当safe mode计数不小于三次时PatcherResultIntent会记录patch失败
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();
}

代理Application

因为要用ApplicationLike代理真实的Application,所以ApplicationLike就要持有Application, resources, classLoader, assetManager的引用.其中tinkerResultIntent属性中记录着TinkerLoader启动和加载patch的状态.

//Application,resource,assetManager,classLoader的引用
private final Application application;
private Resources[]    resources;
private ClassLoader[]  classLoader;
private AssetManager[] assetManager;

//用于记录启动加载patcher loader的状态
private final Intent tinkerResultIntent;

//系统的存活时间和app启动时刻
private final long applicationStartElapsedTime;
private final long applicationStartMillisTime;

//注解中的两个flags
private final int tinkerFlags;
private final boolean tinkerLoadVerifyFlag;

同时还要暴露出常用的Application共有方法,供外部使用.

void onCreate();
void onLowMemory();
void onTrimMemory(int level);
void onTerminate();
void onConfigurationChanged(Configuration newConfig);
void onBaseContextAttached(Context base);

至此Application改造的分析就完成了,上面提到了在Application的attachBaseContext中通过反射调起了TinkerLoader的tryLoad方法.tryLoad方法是加载最终补丁包的入口,下篇文章会沿着这条线继续分析下去.


转载请注明出处:http://blog.csdn.net/l2show/article/details/53187548

阅读更多

没有更多推荐了,返回首页