Android开发 热修复Tinker源码浅析.docx
《Android开发 热修复Tinker源码浅析.docx》由会员分享,可在线阅读,更多相关《Android开发 热修复Tinker源码浅析.docx(18页珍藏版)》请在冰豆网上搜索。
Android开发热修复Tinker源码浅析
Android开发——热修复Tinker源码浅析
0.前言
热修复这项技术,基本上已经成为Android项目比较重要的模块了。
主要因为项目在上线之后,都难免会有各种问题,而依靠发版去修复问题,成本太高了。
现在热修复的技术基本上有阿里的AndFix、QZone的方案、美团提出的思想方案以及腾讯的Tinker等。
其中AndFix可能接入是最简单的一个(和Tinker命令行接入方式差不多),不过AndFix兼容性有一定的问题,QZone方案对性能会有一定的影响,且在Art模式下出现内存错乱的问题,美团提出的思想方案主要是基于InstantRun的原理,目前尚未开源,兼容性较好。
这么看来,如果选择开源方案,Tinker目前是最佳的选择,下面来看看Tinker的大致的原理分析。
1.原理概述
Tinker将old.apk和new.apk做了diff,拿到patch.dex后将其与本机中apk的classes.dex做了合并,生成新的classes.dex,运行时通过反射将合并后的dex文件放置在加载的dexElements数组的前面。
运行时替代的原理,其实和Qzone的方案差不多,都是去反射修改dexElements。
两者的差异是:
Qzone是直接将patch.dex插到数组的前面;而Tinker是合并后的全量dex插在数组的前面。
因为Qzone方案中提到的CLASS_ISPREVERIFIED的解决方案存在问题。
Android的ClassLoader体系中加载类一般使用的是PathClassLoader和DexClassLoader,大家只需要明白,Android使用PathClassLoader作为其类加载器,DexClassLoader可以从.jar和.apk类型的文件内部加载classes.dex文件就好了。
对于加载类,无非是给个classname,然后去findClass,PathClassLoader和DexClassLoader都继承自BaseDexClassLoader。
在BaseDexClassLoader中有如下源码:
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
#BaseDexClassLoader
@Override
protectedClass
>findClass(Stringname)throwsClassNotFoundException{
Classclazz=pathList.findClass(name);
if(clazz==null){
thrownewClassNotFoundException(name);
}
returnclazz;
}
#DexPathList
publicClassfindClass(Stringname){
for(Elementelement:
dexElements){
DexFiledex=element.dexFile;
if(dex!
=null){
Classclazz=dex.loadClassBinaryName(name,definingContext);
if(clazz!
=null){
returnclazz;
}
}
}
returnnull;
}
#DexFile
publicClassloadClassBinaryName(Stringname,ClassLoaderloader){
returndefineClass(name,loader,mCookie);
}
privatenativestaticClassdefineClass(Stringname,ClassLoaderloader,intcookie);
可以看出BaseDexClassLoader中有个pathList对象,pathList中包含一个DexFile的集合dexElements,而对于类加载就是遍历这个集合,通过DexFile去寻找。
通俗点说,一个ClassLoader可以包含多个dex文件,每个dex文件是一个Element,多个dex文件排列成一个有序的数组dexElements,当找类的时候,会按顺序遍历dex文件,然后从当前遍历的dex文件中找类,如果找类则返回,如果找不到从下一个dex文件继续查找。
那么这样的话,我们可以在这个数组的第一个元素放置我们的patch.jar,里面包含修复过的类,这样的话,当遍历findClass的时候,我们修复的类就会被查找到,从而替代有bug的类。
本片文章Tinker源码分析的两条线:
(1)应用启动时,从默认目录加载合并后的classes.dex
(2)patch下发后,合成classes.dex至目标目录
2.源码浅析
2.1加载patch
加载的代码实际上在生成的Application中调用的,其父类为TinkerApplication,在其attachBaseContext中辗转会调用到loadTinker()方法,在该方法内部,反射调用了TinkerLoader的tryLoad方法。
继而调用了tryLoadPatchFilesInternal方法。
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
@Override
publicIntenttryLoad(TinkerApplicationapp,inttinkerFlag,booleantinkerLoadVerifyFlag){
IntentresultIntent=newIntent();
longbegin=SystemClock.elapsedRealtime();
tryLoadPatchFilesInternal(app,tinkerFlag,tinkerLoadVerifyFlag,resultIntent);
longcost=SystemClock.elapsedRealtime()-begin;
ShareIntentUtil.setIntentPatchCostTime(resultIntent,cost);
returnresultIntent;
}
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
privatevoidtryLoadPatchFilesInternal(TinkerApplicationapp,inttinkerFlag,booleantinkerLoadVerifyFlag,IntentresultIntent){
//省略大量安全性校验代码
if(isEnabledForDex){
//tinker/patch.info/patch-641e634c/dex
booleandexCheck=TinkerDexLoader.checkComplete(patchVersionDirectory,securityCheck,resultIntent);
if(!
dexCheck){
//filenotfound,donotloadpatch
Log.w(TAG,"tryLoadPatchFiles:
dexcheckfail");
return;
}
}
//nowwecanloadpatchjar
if(isEnabledForDex){
booleanloadTinkerJars=TinkerDexLoader.loadTinkerJars(app,tinkerLoadVerifyFlag,patchVersionDirectory,resultIntent,isSystemOTA);
if(!
loadTinkerJars){
Log.w(TAG,"tryLoadPatchFiles:
onPatchLoadDexesFail");
return;
}
}
}
其中TinkerDexLoader.checkComplete主要是用于检查下发的meta文件中记录的dex信息(meta文件,可以查看生成patch的产物,在assets/dex-meta.txt),检查meta文件中记录的dex文件信息对应的dex文件是否存在,并把值存在TinkerDexLoader的静态变量dexList中。
TinkerDexLoader.loadTinkerJars传入四个参数,分别为application,tinkerLoadVerifyFlag(注解上声明的值,传入为false),patchVersionDirectory当前version的patch文件夹,intent,当前patch是否仅适用于art。
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
publicstaticbooleanloadTinkerJars(Applicationapplication,booleantinkerLoadVerifyFlag,
Stringdirectory,IntentintentResult,booleanisSystemOTA){
PathClassLoaderclassLoader=(PathClassLoader)TinkerDexLoader.class.getClassLoader();
StringdexPath=directory+"/"+DEX_PATH+"/";
FileoptimizeDir=newFile(directory+"/"+DEX_OPTIMIZE_PATH);
ArrayListlegalFiles=newArrayList<>();
finalbooleanisArtPlatForm=ShareTinkerInternals.isVmArt();
for(ShareDexDiffPatchInfoinfo:
dexList){
//fordalvik,ignoreartsupportdex
if(isJustArtSupportDex(info)){
continue;
}
Stringpath=dexPath+info.realName;
Filefile=newFile(path);
legalFiles.add(file);
}
//justforart
if(isSystemOTA){
parallelOTAResult=true;
parallelOTAThrowable=null;
Log.w(TAG,"systemOTA,tryparalleloatdexes!
!
!
!
!
");
TinkerParallelDexOptimizer.optimizeAll(
legalFiles,optimizeDir,
newTinkerParallelDexOptimizer.ResultCallback(){
}
);
SystemClassLoaderAdder.installDexes(application,classLoader,optimizeDir,legalFiles);
returntrue;
找出仅支持art的dex,且当前patch是否仅适用于art时,并行去loadDex。
关键是最后的installDexes:
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
@SuppressLint("NewApi")
publicstaticvoidinstallDexes(Applicationapplication,PathClassLoaderloader,FiledexOptDir,Listfiles)
throwsThrowable{
if(!
files.isEmpty()){
ClassLoaderclassLoader=loader;
if(Build.VERSION.SDK_INT>=24){
classLoader=AndroidNClassLoader.inject(loader,application);
}
//becauseindalvik,ifinnerclassisnotthesameclassloaderwithitwrapperclass.
//itwon'tfailatdex2opt
if(Build.VERSION.SDK_INT>=23){
V23.install(classLoader,files,dexOptDir);
}elseif(Build.VERSION.SDK_INT>=19){
V19.install(classLoader,files,dexOptDir);
}elseif(Build.VERSION.SDK_INT>=14){
V14.install(classLoader,files,dexOptDir);
}else{
V4.install(classLoader,files,dexOptDir);
}
//installdone
sPatchDexCount=files.size();
Log.i(TAG,"afterloadedclassloader:
"+classLoader+",dexsize:
"+sPatchDexCount);
if(!
checkDexInstall(classLoader)){
//resetpatchdex
SystemClassLoaderAdder.uninstallPatchDex(classLoader);
thrownewTinkerRuntimeException(ShareConstants.CHECK_DEX_INSTALL_FAIL);
}
}
}
这里实际上就是根据不同的系统版本,去反射处理dexElements。
我们看一下V19的实现:
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
privatestaticfinalclassV19{
privatestaticvoidinstall(ClassLoaderloader,ListadditionalClassPathEntries,
FileoptimizedDirectory)
throwsIllegalArgumentException,IllegalAccessException,
NoSuchFieldException,InvocationTargetException,NoSuchMethodException,IOException{
FieldpathListField=ShareReflectUtil.findField(loader,"pathList");
ObjectdexPathList=pathListField.get(loader);
ArrayListsuppressedExceptions=newArrayList();
ShareReflectUtil.expandFieldArray(dexPathList,"dexElements",makeDexElements(dexPathList,
newArrayList(additionalClassPathEntries),optimizedDirectory,
suppressedExceptions));
if(suppressedExceptions.size()>0){
for(IOExceptione:
suppressedExceptions){
Log.w(TAG,"ExceptioninmakeDexElement",e);
throwe;
}
}
}
}
(1)找到PathClassLoader(BaseDexClassLoader)对象中的pathList对象
(2)根据pathList对象找到其中的makeDexElements方法,传入patch相关的对应的实参,返回Element[]对象
(3)拿到pathList对象中原本的dexElements方法
(4)步骤2与步骤3中的Element[]数组进行合并,将patch相关的dex放在数组的前面
(5)最后将合并后的数组,设置给pathList
2.2合成patch
入口为onReceiveUpgradePatch()方法:
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(),
Environment.getExternalStorageDirectory().getAbsolutePath()+"/patch_signed.apk");
上述代码会调用DefaultPatchListener中的onPatchReceived方法:
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
#DefaultPatchListener
@Override
publicintonPatchReceived(Stringpath){
intreturnCode=patchCheck(path);
if(returnCode==ShareConstants.ERROR_PATCH_OK){
TinkerPatchService.runPatchService(context,path);
}else{
Tinker.with(context).getLoadReporter().onLoadPatchListenerReceiveFail(newFile(path),returnCode);
}
returnreturnCode;
}
首先对Tinker的相关配置(isEnable)以及patch的合法性进行检测,如果合法,则调用TinkerPatchService.runPatchService(context,path)。
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
publicstaticvoidrunPatchService(Contextcontext,Stringpath){
try{
Intentintent=newIntent(context,TinkerPatchService.class);
intent.putExtra(PATCH_PATH_EXTRA,path);
intent.putExtra(RESULT_CLASS_EXTRA,resultServiceClass.getName());
context.startService(intent);
}catch(Throwablethrowable){
TinkerLog.e(TAG,"startpatchservicefail,exception:
"+throwable);
}
}
TinkerPatchService是IntentService的子类,这里通过intent设置了两个参数,一个是patch的路径,一个是resultServiceClass,该值是调用Tinker.install的时候设置的,默认为DefaultTinkerResultService.class。
由于是IntentService,直接看onHandleIntent即可。
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
@Override
protectedvoidonHandleIntent(Intentintent){
finalContextcontext=getApplicationContext();
Tinkertinker=Tinker.with(context);
Stringpath=getPatchPathExtra(intent);
FilepatchFile=newFile(path);
booleanresult;
increasingPriority();
PatchResultpatchResult=newPatchResult();
result=upgradePatchProcessor.tryPatch(context,path,patchResult);
patchResult.isSuccess=result;
patchResult.rawPatchFilePath=path;
patchResult.costTime=cost;
patchResult.e=e;
AbstractResultService.runResultService(context,patchResult,getPatchResultExtra(intent));
}
比较清晰,主要关注upgradePatchProcessor.tryPatch方法,调用的是UpgradePatch.tryPatch。
这里有个有意思的地方increasingPriority(),其内部实现为:
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
privatevoidincreasingPriority(){
TinkerLog.i(TAG,"trytoin