Android开发 热修复Tinker源码浅析.docx

上传人:b****8 文档编号:11041482 上传时间:2023-02-24 格式:DOCX 页数:18 大小:21.75KB
下载 相关 举报
Android开发 热修复Tinker源码浅析.docx_第1页
第1页 / 共18页
Android开发 热修复Tinker源码浅析.docx_第2页
第2页 / 共18页
Android开发 热修复Tinker源码浅析.docx_第3页
第3页 / 共18页
Android开发 热修复Tinker源码浅析.docx_第4页
第4页 / 共18页
Android开发 热修复Tinker源码浅析.docx_第5页
第5页 / 共18页
点击查看更多>>
下载资源
资源描述

Android开发 热修复Tinker源码浅析.docx

《Android开发 热修复Tinker源码浅析.docx》由会员分享,可在线阅读,更多相关《Android开发 热修复Tinker源码浅析.docx(18页珍藏版)》请在冰豆网上搜索。

Android开发 热修复Tinker源码浅析.docx

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

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 外语学习 > 其它语言学习

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1