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
的启动流程,后面你就可以使出你的十八般武艺来为我们的应用实现热修复了。