Tinker是微信官方为Android的热修复界带来的一个热修复方案,它支持代码的动态下发,对So库和资源修复也有很好的支持,让应用不用重新安装的情况下实现更新。有关Tinker的更多介绍,可以参看这里。我这里只是根据官方提供的Demo来简单介绍下Tinker的启动流程。
我们知道,如果在工程的Manifest中为我们的应用配置了Application的话,在用户启动我们的应用时,首先会调起我们配置的Application,进而完成一些基本的初始化操作。
Tinker为了能够对应用的Application提供修复,使用了一种委托机制,我们可以把在Application的要完成的工作转移到委托类中实现,而我们原Application类只需要继承TinkerApplication类,并提供一个无参构造函数,在构造函数中将我们的委托类传递给父类即可。
Tinker官方为了简化我们的接入工作,为我们提供了一个编译时注解类。我们只要在我们的委托类上通过注解即可完成Application的委托代理工作,然后将注解项中的application的值注册到Manifest中。
下面我们就来看看官方提供的Demo是如何一步步完成这些工作的。
添加插件及依赖
- 首先,我们打开
Sample项目根目录下的build.gradle文件,buildscript中添加Tinker的gradle插件:
classpath "com.tencent.tinker:tinker-patch-gradle-plugin:${TINKER_VERSION}"
其中TINKER_VERSION变量的值在根目录下的gradle.properties中有配置:
TINKER_VERSION=1.7.3
- 接着在
app/build.gradle中引入Tinker依赖,并配置一些Tinker运行需要的参数:
compile("com.tencent.tinker:tinker-android-lib:${TINKER_VERSION}") { changing = true }
provided("com.tencent.tinker:tinker-android-anno:${TINKER_VERSION}") { changing = true }
其它的配置项大家可以参看官方的文档,这里暂且略过。
- 下面我们来看看我们
Application的代理类SampleApplicationLike:
@DefaultLifeCycle(application = "tinker.sample.android.app.SampleApplication",
flags = ShareConstants.TINKER_ENABLE_ALL,
loadVerifyFlag = false)
public class SampleApplicationLike extends DefaultApplicationLike {
private static final String TAG = "Tinker.SampleApplicationLike";
public SampleApplicationLike(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);
}
/**
* install multiDex before install tinker
* so we don't need to put the tinker lib classes in the main dex
*
* @param base
*/
@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);
}
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
public void registerActivityLifecycleCallbacks(Application.ActivityLifecycleCallbacks callback) {
getApplication().registerActivityLifecycleCallbacks(callback);
}
}
我们首先来看看该类声明处的注解DefaultLifeCycle。前面我已经说过,这个注解是官方为我们提供的一个简化配置的注解类。这个注解是个编译时注解类,其实现在tinker-android-anno库中。这个库主要的实现其实就两个类和一个模版文件。我们先来看看这个注解类帮我们做了哪些工作。
打开AnnotationProcessor类,来看看注解的处理过程:
private void processDefaultLifeCycle(Set<? extends Element> elements) {
// DefaultLifeCycle
for (Element e : elements) {
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);
String applicationClassName = ca.application();
if (applicationClassName.startsWith(".")) {
applicationClassName = lifeCyclePackageName + applicationClassName;
}
String applicationPackageName = applicationClassName.substring(0, applicationClassName.lastIndexOf('.'));
applicationClassName = applicationClassName.substring(applicationClassName.lastIndexOf('.') + 1);
String loaderClassName = ca.loaderClass();
if (loaderClassName.startsWith(".")) {
loaderClassName = lifeCyclePackageName + loaderClassName;
}
System.out.println("*");
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());
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());
}
}
}
在这个方法中完成对DefaultLifeCycle注解的解析工作。下面我们就来参照着SampleApplicationLike类的注解项来理解这个解析工作。
首先我们遍历所有被DefaultLifeCycle标注的注解元素(这里我们只有SampleApplicationLike一个元素),拿到该元素的包名和类名。然后根据DefaultLifeCycle的配置项,获取应用类的名称,若名称以.开头,则自动拼接上包名。按照同样的方式拿到我们配置的loader类、flags以及loadVerifyFlag标志。接下来读取我们的模板文件,根据注解的配置项,替换到模板中的参数,生成我们自己的Application类。其目标文件很简单,就是生成一个Application类,并添加一个无参构造函数,将我们配置的loader等传给父类调用。
public class %APPLICATION% extends TinkerApplication {
public %APPLICATION%() {
super(%TINKER_FLAGS%, "%APPLICATION_LIFE_CYCLE%", "%TINKER_LOADER_CLASS%", %TINKER_LOAD_VERIFY_FLAG%);
}
}
当我们的注解类生效时,就可以在我们应用的app/build/generated/source/apt/debug目录下看到我们配置的Application类了,而这个类名的确是我们在注解DefaultLifeCycle#application中配置的类。我们没有配置loader类,注解DefaultLifeCycle为我们提供了一个默认的加载类com.tencent.tinker.loader.TinkerLoader,它与委托类SampleApplicationLike一起传递给了父类TinkerApplication。
按照传统的思路,当用户启动我们的应用时,首先会启动我们注册的Application类完成一些基础工作。到这里,我们已经看到了我们自己的Application类SampleApplication,而这个类继承自TinkerApplication类。这也就是说,我们所有的工作要么放到SampleApplication类中实现,要么改写TinkerApplication类。如果你认真读了上文,你会发现这两个类都不在我们自己的工程项目中(SampleApplication类是由注解类自动生成,这里简单认为它不属于我们自己的工程项目)。那么,我们要想在Application中配置一些东西,要如何完成呢?对,我们不是有自己的委托类吗,使用我们的委托类SampleApplicationLike,让Application类调用我们的委托类不就可以了吗?说干就干,我们来看看如何调起我们委托类的方法。
打开TinkerApplication类文件。上面说了,通过注解类,我们将我们的委托类SampleApplicationLike已经传到了TinkerApplication中,在其无参构造函数中赋值给了它的成员变量:
protected TinkerApplication(int tinkerFlags, String delegateClassName,
String loaderClassName, boolean tinkerLoadVerifyFlag) {
this.tinkerFlags = tinkerFlags;
this.delegateClassName = delegateClassName;
this.loaderClassName = loaderClassName;
this.tinkerLoadVerifyFlag = tinkerLoadVerifyFlag;
}
在TinkerApplication类中,我们看到了那些熟悉的Application周期方法,如onCreate方法等。下面我们就从我们最熟悉的onCreate方法入手,看看我们的委托类是如何被调用来完成我们的初始化工作的:
@Override
public final void onCreate() {
super.onCreate();
ensureDelegate();
delegateMethod("onCreate");
}
OK,我们来看ensureDelegate方法:
private synchronized void ensureDelegate() {
if (delegate == null) {
delegate = createDelegate();
}
}
初次调用,delegate当然为null(其实,这里已经被初始化过了,后文我会解释),我们来看createDelegate方法:
private Object createDelegate() {
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(delegateClassName, false, getClassLoader());
Constructor<?> constructor = delegateClass.getConstructor(Application.class, int.class, boolean.class, long.class, long.class,
Intent.class, Resources[].class, ClassLoader[].class, AssetManager[].class);
return constructor.newInstance(this, tinkerFlags, tinkerLoadVerifyFlag,
applicationStartElapsedTime, applicationStartMillisTime,
tinkerResultIntent, resources, classLoader, assetManager);
} catch (Throwable e) {
throw new TinkerRuntimeException("createDelegate failed", e);
}
}
在createDelegate方法中,通过反射来创建一个委托类,从上文我们已经知道,这里要创建的委托类就是我们的SampleApplicationLike。获取到SampleApplicationLike委托类实例以后,将对onCreate方法的调用委托给了SampleApplicationLike的onCreate方法,其它周期方法与此类似。这样我们就明白了,为什么在SampleApplicationLike中复写对应的方法就可以完成我们的初始化工作了。
回到SampleApplicationLike类,我们看到了一个新面孔onBaseContextAttached,这个方法似乎不是我们Application的方法?别急,我们再去TinkerApplication看看,找到我们的老朋友attachBaseContext,这个方法大家可能不是太熟悉,这个方法是先于onCreate方法被调用的,在该方法中调用了类中的onBaseContextAttached方法,我们要找的是它吗?打开看看,好吧,貌似不是吧?在它里面,首先初始化了我们的loader类和委托类,这也就是解释了为什么在onCreate中调用ensureDelegate时delegate不为null的原因。紧接着就是调用我们委托类的onBaseContextAttached方法了。哈哈,找到了,原来我们SampleApplicationLike中的onBaseContextAttached是在这里调用了。由此我们知道,如果有朋友需要在attachBaseContext是做一些初始化工作,就可以在我们委托类的onBaseContextAttached中完成了。同样的,其它配置工作就可以复写不同的周期方法来完成了。
以上即为Tinker的启动流程,后面你就可以使出你的十八般武艺来为我们的应用实现热修复了。
本文详细介绍了微信官方推出的Android热修复方案Tinker的启动流程。包括如何通过注解配置Application的代理类,以及如何利用代理类完成应用初始化工作。
834

被折叠的 条评论
为什么被折叠?



