精华内容
下载资源
问答
  • 新apk和旧的apk通过dexbiff算法对比生成差异包,差异包通过比对文件MD5值,把修改过的文件打进差异包,差异包下发到服务器,下载到手机指定的路径, 通过这个下载的路径生成dexclassloader,获取dexclassloader中的...

    上篇文章写了tinker的使用方法
    tinker使用
    本篇文章简单的分析一下dex文件的修复流程
    在几年前我对热修复的理解为
    新apk和旧的apk通过dexbiff算法对比生成差异包,差异包通过比对文件的MD5值,把修改过的文件打进差异包,差异包下发到服务器,下载到手机指定的路径,
    通过这个下载的路径生成dexclassloader,获取dexclassloader中的的pathlist,在获取Element数组,然后获取系统的pathclassloader和他的element数组,把这个差异包的Element数组塞到系统默认的element数组里面。通过反射把新的element数组设置给pathclassloader然后下次启动的时候就会加载差异包的dex类

    倒是最近发现阅读tinker的源码的时候,发现还是有一些不一样的地方
    首先从入口代码开始看

    TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(), path);
    

    这个tinker加载patch包的代码
    继续跟踪

     public static void onReceiveUpgradePatch(Context context, String patchLocation) {
            Tinker.with(context).getPatchListener().onPatchReceived(patchLocation);
        }
        getPatchListenr是一个接口,直接看它的实现类DefaultPatchListener
        
        @Override
        public int onPatchReceived(String path) {
            final File patchFile = new File(path);
            final String patchMD5 = SharePatchFileUtil.getMD5(patchFile);
            final int returnCode = patchCheck(path, patchMD5);
            if (returnCode == ShareConstants.ERROR_PATCH_OK) {
                runForgService();
                TinkerPatchService.runPatchService(context, path);
            } else {
                Tinker.with(context).getLoadReporter().onLoadPatchListenerReceiveFail(new File(path), returnCode);
            }
            return returnCode;
        }
        
       有一个MD5的校验
       接下来看这一行
        TinkerPatchService.runPatchService(context, path);
    
      public static void runPatchService(final Context context, final String path) {
            Intent intent = new Intent(context, TinkerPatchService.class);
            intent.putExtra(PATCH_PATH_EXTRA, path);
            intent.putExtra(RESULT_CLASS_EXTRA, resultServiceClass.getName());
            try {
                context.startService(intent);
            } catch (Throwable thr) {
                ShareTinkerLog.e(TAG, "run patch service fail, exception:" + thr);
            }
        }
        
    

    直接开启了一个server,而且这个server是有自己独立的进程的在manifest里面可以看到 android:process=":patch"

     <service
                android:name="com.tencent.tinker.lib.service.TinkerPatchService"
                android:exported="false"
                android:permission="android.permission.BIND_JOB_SERVICE"
                android:process=":patch" />
    

    在这个server的onHandleIntent中加载了补丁包

     @Override
        protected void onHandleIntent(Intent intent) {
            increasingPriority();
            doApplyPatch(this, intent);
        }
     private static void doApplyPatch(Context context, Intent intent) {
           ....
     		result = upgradePatchProcessor.tryPatch(context, path, patchResult);
            AbstractResultService.runResultService(context, patchResult, getPatchResultExtra(intent));
    
            sIsPatchApplying.set(false);
        }
    继续看tryPatch,位于UpgradePatch类中
    
    	   @Override
        public boolean tryPatch(Context context, String tempPatchPath, PatchResult patchResult) {
        	...
        	  //we use destPatchFile instead of patchFile, because patchFile may be deleted during the patch process
            if (!DexDiffPatchInternal.tryRecoverDexFiles(manager, signatureCheck, context, patchVersionDirectory, destPatchFile, patchResult)) {
                ShareTinkerLog.e(TAG, "UpgradePatch tryPatch:new patch recover, try patch dex failed");
                return false;
            }
            ...
        }
    	 因为只是简单看一下dex文件的修复,所以只截取了相关的代码
    	 关键代码为
    	 DexDiffPatchInternal.tryRecoverDexFiles
    	
    	protected static boolean tryRecoverDexFiles(Tinker manager, ShareSecurityCheck checker, Context context,
                                                    String patchVersionDirectory, File patchFile, PatchResult patchResult) {
           ...
            boolean result = patchDexExtractViaDexDiff(context, patchVersionDirectory, dexMeta, patchFile, patchResult);
            long cost = SystemClock.elapsedRealtime() - begin;
            ShareTinkerLog.i(TAG, "recover dex result:%b, cost:%d", result, cost);
            return result;
        }
    	->patchDexExtractViaDexDiff
    	
    	 private static boolean patchDexExtractViaDexDiff(Context context, String patchVersionDirectory, String meta, final File patchFile, PatchResult patchResult) {
           ...
            return dexOptimizeDexFiles(context, legalFiles, optimizeDexDirectory, patchFile, patchResult);
        }
        
        ->dexOptimizeDexFiles
     private static boolean dexOptimizeDexFiles(Context context, List<File> dexFiles, String optimizeDexDirectory, final File patchFile, final PatchResult patchResult) {
            final Tinker manager = Tinker.with(context);
    
            optFiles.clear();
    
            if (dexFiles != null) {
                File optimizeDexDirectoryFile = new File(optimizeDexDirectory);
    
                if (!optimizeDexDirectoryFile.exists() && !optimizeDexDirectoryFile.mkdirs()) {
                    ShareTinkerLog.w(TAG, "patch recover, make optimizeDexDirectoryFile fail");
                    return false;
                }
                // add opt files
                for (File file : dexFiles) {
                    String outputPathName = SharePatchFileUtil.optimizedPathFor(file, optimizeDexDirectoryFile);
                    optFiles.add(new File(outputPathName));
                }
    
                ShareTinkerLog.i(TAG, "patch recover, try to optimize dex file count:%d, optimizeDexDirectory:%s", dexFiles.size(), optimizeDexDirectory);
               ...
                TinkerDexOptimizer.optimizeAll(
                      context, dexFiles, optimizeDexDirectoryFile,
                      useDLC,
                      new TinkerDexOptimizer.ResultCallback() {
                          long startTime;
    
                          @Override
                          public void onStart(File dexFile, File optimizedDir) {
                              startTime = System.currentTimeMillis();
                              ShareTinkerLog.i(TAG, "start to parallel optimize dex %s, size: %d", dexFile.getPath(), dexFile.length());
                          }
    
                          @Override
                          public void onSuccess(File dexFile, File optimizedDir, File optimizedFile) {
                              ShareTinkerLog.i(TAG, "success to parallel optimize dex %s, opt file:%s, opt file size: %d, use time %d",
                                  dexFile.getPath(), optimizedFile.getPath(), optimizedFile.length(), (System.currentTimeMillis() - startTime));
                              if (!optimizedFile.exists()) {
                                  synchronized (anyOatNotGenerated) {
                                      anyOatNotGenerated[0] = true;
                                  }
                              }
                          }
    
                          @Override
                          public void onFailed(File dexFile, File optimizedDir, Throwable thr) {
                              ShareTinkerLog.i(TAG, "fail to parallel optimize dex %s use time %d",
                                  dexFile.getPath(), (System.currentTimeMillis() - startTime));
                              failOptDexFile.add(dexFile);
                              throwable[0] = thr;
                          }
                      }
                );
    		...
            return true;
        }
    
    ->optimizeAll
    public static boolean optimizeAll(Context context, Collection<File> dexFiles, File optimizedDir,
                                          boolean useDLC, ResultCallback cb) {
            return optimizeAll(context, dexFiles, optimizedDir, false, useDLC, null, cb);
        }
    
        public static boolean optimizeAll(Context context, Collection<File> dexFiles, File optimizedDir,
                                          boolean useInterpretMode, boolean useDLC,
                                          String targetISA, ResultCallback cb) {
            ArrayList<File> sortList = new ArrayList<>(dexFiles);
            // sort input dexFiles with its file length in reverse order.
           ...
            for (File dexFile : sortList) {
                OptimizeWorker worker = new OptimizeWorker(context, dexFile, optimizedDir, useInterpretMode,
                      useDLC, targetISA, cb);
                if (!worker.run()) {
                    return false;
                }
            }
            return true;
        }
        ->OptimizeWorker.run
     boolean run() {
                try {
                 ...
                    if (!ShareTinkerInternals.isArkHotRuning()) {
                        if (useInterpretMode) {
                            interpretDex2Oat(dexFile.getAbsolutePath(), optimizedPath);
                        } else if (Build.VERSION.SDK_INT >= 26
                                || (Build.VERSION.SDK_INT >= 25 && Build.VERSION.PREVIEW_SDK_INT != 0)) {
                            NewClassLoaderInjector.triggerDex2Oat(context, optimizedDir,
                                                                  useDLC, dexFile.getAbsolutePath());
                            // Android Q is significantly slowed down by Fallback Dex Loading procedure, so we
                            // trigger background dexopt to generate executable odex here.
                            triggerPMDexOptOnDemand(context, dexFile.getAbsolutePath(), optimizedPath);
                        } else {
                            DexFile.loadDex(dexFile.getAbsolutePath(), optimizedPath, 0);
                        }
                    }
                    ...
                return true;
            }
            
            因为现在andorid 版本都比较新,所以直接看(Build.VERSION.SDK_INT >= 26)里面的代码
            ->NewClassLoaderInjector.triggerDex2Oat
            public static void triggerDex2Oat(Context context, File dexOptDir, boolean useDLC,
                                          String... dexPaths) throws Throwable {
            final ClassLoader triggerClassLoader = createNewClassLoader(context.getClassLoader(), dexOptDir, useDLC, dexPaths);
        }
    ->createNewClassLoader
    这里就是dex文件修复的关键代码了
    private static ClassLoader createNewClassLoader(ClassLoader oldClassLoader,
                                                        File dexOptDir,
                                                        boolean useDLC,
                                                        String... patchDexPaths) throws Throwable {
            final Field pathListField = findField(
                    Class.forName("dalvik.system.BaseDexClassLoader", false, oldClassLoader),
                    "pathList");
            final Object oldPathList = pathListField.get(oldClassLoader);
            final StringBuilder dexPathBuilder = new StringBuilder();
            final boolean hasPatchDexPaths = patchDexPaths != null && patchDexPaths.length > 0;
            if (hasPatchDexPaths) {
                for (int i = 0; i < patchDexPaths.length; ++i) {
                    if (i > 0) {
                        dexPathBuilder.append(File.pathSeparator);
                    }
                    dexPathBuilder.append(patchDexPaths[i]);
                }
            }
            final String combinedDexPath = dexPathBuilder.toString();
            final Field nativeLibraryDirectoriesField = findField(oldPathList.getClass(), "nativeLibraryDirectories");
            List<File> oldNativeLibraryDirectories = null;
            if (nativeLibraryDirectoriesField.getType().isArray()) {
                oldNativeLibraryDirectories = Arrays.asList((File[]) nativeLibraryDirectoriesField.get(oldPathList));
            } else {
                oldNativeLibraryDirectories = (List<File>) nativeLibraryDirectoriesField.get(oldPathList);
            }
            final StringBuilder libraryPathBuilder = new StringBuilder();
            boolean isFirstItem = true;
            for (File libDir : oldNativeLibraryDirectories) {
                if (libDir == null) {
                    continue;
                }
                if (isFirstItem) {
                    isFirstItem = false;
                } else {
                    libraryPathBuilder.append(File.pathSeparator);
                }
                libraryPathBuilder.append(libDir.getAbsolutePath());
            }
    
            final String combinedLibraryPath = libraryPathBuilder.toString();
    
            ClassLoader result = null;
            if (useDLC && Build.VERSION.SDK_INT >= 27) {
                result = new DelegateLastClassLoader(combinedDexPath, combinedLibraryPath, ClassLoader.getSystemClassLoader());
                final Field parentField = ClassLoader.class.getDeclaredField("parent");
                parentField.setAccessible(true);
                parentField.set(result, oldClassLoader);
            } else {
                result = new TinkerClassLoader(combinedDexPath, dexOptDir, combinedLibraryPath, oldClassLoader);
            }
    
            // 'EnsureSameClassLoader' mechanism which is first introduced in Android O
            // may cause exception if we replace definingContext of old classloader.
            if (Build.VERSION.SDK_INT < 26) {
                findField(oldPathList.getClass(), "definingContext").set(oldPathList, result);
            }
    
            return result;
        }
       
    
    

    到这里就开始分开分析了
    首先看前面的代码

     final StringBuilder dexPathBuilder = new StringBuilder();
            final boolean hasPatchDexPaths = patchDexPaths != null && patchDexPaths.length > 0;
            if (hasPatchDexPaths) {
                for (int i = 0; i < patchDexPaths.length; ++i) {
                    if (i > 0) {
                        dexPathBuilder.append(File.pathSeparator);
                    }
                    dexPathBuilder.append(patchDexPaths[i]);
                }
            }
    
            final String combinedDexPath = dexPathBuilder.toString();
    

    patchDexPaths是patch包的路径
    最后转换成了combinedDexPath用File.pathSeparator分隔

    final Field nativeLibraryDirectoriesField = findField(oldPathList.getClass(), "nativeLibraryDirectories");
            List<File> oldNativeLibraryDirectories = null;
            if (nativeLibraryDirectoriesField.getType().isArray()) {
                oldNativeLibraryDirectories = Arrays.asList((File[]) nativeLibraryDirectoriesField.get(oldPathList));
            } else {
                oldNativeLibraryDirectories = (List<File>) nativeLibraryDirectoriesField.get(oldPathList);
            }
            final StringBuilder libraryPathBuilder = new StringBuilder();
            boolean isFirstItem = true;
            for (File libDir : oldNativeLibraryDirectories) {
                if (libDir == null) {
                    continue;
                }
                if (isFirstItem) {
                    isFirstItem = false;
                } else {
                    libraryPathBuilder.append(File.pathSeparator);
                }
                libraryPathBuilder.append(libDir.getAbsolutePath());
            }
    
            final String combinedLibraryPath = libraryPathBuilder.toString();
    

    这里和上面一样是将老的oldPathList的路径通过StringBuilder append到一起组合成了combinedLibraryPath
    最后通过反射把新的classloader替换了旧的

    result = new DelegateLastClassLoader(combinedDexPath, combinedLibraryPath, ClassLoader.getSystemClassLoader());
                final Field parentField = ClassLoader.class.getDeclaredField("parent");
                parentField.setAccessible(true);
                parentField.set(result, oldClassLoader);
    

    关于这个DelegateLastClassLoader可以在这里看到详细的介绍
    DelegateLastClassLoader

    最后回到doApplyPatch方法中
    加载完成之后开启了一个AbstractResultService.runResultService

    在这个server的onHandleIntent调用了onPatchResult
     @Override
    protected void onHandleIntent(Intent intent) {
        if (intent == null) {
            ShareTinkerLog.e(TAG, "AbstractResultService received a null intent, ignoring.");
            return;
        }
        PatchResult result = (PatchResult) ShareIntentUtil.getSerializableExtra(intent, RESULT_EXTRA);
    
        onPatchResult(result);
    }
    public abstract void onPatchResult(PatchResult result);
     这个类是一个抽象类,实现类为DefaultTinkerResultService
     
      @Override
    public void onPatchResult(PatchResult result) {
     
        TinkerServiceInternals.killTinkerPatchServiceProcess(getApplicationContext());
        if (result.isSuccess) {
            deleteRawPatchFile(new File(result.rawPatchFilePath));//删除原来的路径的文件
            if (checkIfNeedKill(result)) {
                android.os.Process.killProcess(android.os.Process.myPid());
            } else {
                ShareTinkerLog.i(TAG, "I have already install the newly patch version!");
            }
        }
    }
    

    最后把这个补丁包文件删除,然后杀掉开启了的新进程
    到这里dex文件修复的流程就跟踪完了,
    那核心方法就是通过反射把原来的类加载器换成了新的,和我文章开头的理解还是有一点不一样的,不过在我的印象中,好像tinker的某一个版本是用的我文章开头使用的方法后续因为android版本的原因才替换的?
    参考资料
    混合编译对tinker的影响
    tinker

    展开全文
  • 实例240 编写MD5查看器 实例241 基于Base64编码 实例242 基于Base64解码 实例243 发送简单的Email 实例244 发送带附件的Email 实例245 读取XML文件属性 第3篇 窗体与控件应用 第11章 窗体设计 11.1 设置...
  • vc++ 应用源码包_5

    热门讨论 2012-09-15 14:45:16
    MD5算法 MediaPlayer 视频播放的实现。 MFC 对话框 MP3 内部包含:mp3播放器Lrc歌词同步源程序代码分析、mp3播放器+支持歌词同步显示哦、简单音乐播放器。 mfc 解码 视频音频解码部分。 MFC_MultiSender_OVER ...
  • 文件md5值> <保存的网盘路径, 需包含文件名> 例子: 如果秒传成功, 则保存到网盘路径 /test/file.txt cloudpan189-go rapidupload -size=56276137 -md5=fbe082d80e90f90f0fb1f94adbbcfa7f /test/file.txt...
  •  实例155 使用MD5算法对密码进行加密 201 实例156 恺撒密码算法的实现 202 第2篇 Windows窗体篇 第7章 Windows窗体基础 206 7.1 设置窗体位置 207  实例157 控制窗体加载时的位置 207  实例158 设置...
  • 实例240 编写MD5查看器 实例241 基于Base64编码 实例242 基于Base64解码 实例243 发送简单的Email 实例244 发送带附件的Email 实例245 读取XML文件属性 第3篇 窗体与控件应用 第11章 窗体设计 11.1 设置...
  • 实例240 编写MD5查看器 实例241 基于Base64编码 实例242 基于Base64解码 实例243 发送简单的Email 实例244 发送带附件的Email 实例245 读取XML文件属性 第3篇 窗体与控件应用 第11章 窗体设计 11.1 设置...
  • 实例240 编写MD5查看器 实例241 基于Base64编码 实例242 基于Base64解码 实例243 发送简单的Email 实例244 发送带附件的Email 实例245 读取XML文件属性 第3篇 窗体与控件应用 第11章 窗体设计 11.1 设置...
  • 实例240 编写MD5查看器 实例241 基于Base64编码 实例242 基于Base64解码 实例243 发送简单的Email 实例244 发送带附件的Email 实例245 读取XML文件属性 第3篇 窗体与控件应用 第11章 窗体设计 11.1 设置...
  • 实例155 使用MD5算法对密码进行加密 201 实例156 凯撒密码算法的实现 202 第2篇 Windows窗体篇 第7章 Windows窗体基础 206 7.1 设置窗体位置 207 实例157 控制窗体加载时的位置 207 实例158 设置窗体在屏幕中的位置 ...
  • 实例125 单个用户密码重置功能(MD5加密) 实例126 以传参方式批量更新供求信息发布时间 实例127 动态组合SQL语句方式实现批量更新 实例128 删除单条供求信息,并弹出删除提示 实例129 后台管理中批量删除发布...
  • 实例125 单个用户密码重置功能(MD5加密) 实例126 以传参方式批量更新供求信息发布时间 实例127 动态组合SQL语句方式实现批量更新 实例128 删除单条供求信息,并弹出删除提示 实例129 后台管理中批量删除发布...
  • encryptMD5File, encryptMD5File2String : MD5 加密文件 encryptSHA1, encryptSHA1ToString : SHA1 加密 encryptSHA224, encryptSHA224ToString : SHA224 加密 encryptSHA256, encryptSHA256ToString : SHA256 加密 ...
  • 风越ASP代码生成器 2.4

    2009-05-26 21:22:40
    常用加解密、编解码(DES、MD5、SHA、BASE64) 批量提取文本,支持正则表达式,可将HTML等文件指定内容存入数据库 批量查找替换,支持正则表达式 批量文件、文件夹改名,支持正则表达式 正则表达式测试器...
  • 常用加解密、编解码(DES、MD5、SHA、BASE64) 批量提取文本,支持正则表达式,可将HTML等文件指定内容存入数据库 批量查找替换,支持正则表达式 批量文件、文件夹改名,支持正则表达式 正则表达式测试器(可设置...
  • 常用加解密、编解码(DES、MD5、SHA、BASE64) 批量提取文本,支持正则表达式,可将HTML等文件指定内容存入数据库 批量查找替换,支持正则表达式 批量文件、文件夹改名,支持正则表达式 正则表达式测试器...
  • 常用加解密、编解码(DES、MD5、SHA、BASE64) 批量提取文本,支持正则表达式,可将HTML等文件指定内容存入数据库 批量查找替换,支持正则表达式 批量文件、文件夹改名,支持正则表达式 正则表达式测试器...
  • 先在数据中创建名为c2c的数据库,使用Navicat for MySQL运行 c2c.sql文件创建表和导入数据 将图片解压到任意一个盘,然后配置Tomcat,将图片路径引用到本地配置的图片路径下。 一、设计概要  本次设计的是一个...
  • 实例155 使用MD5算法对密码进行加密 实例156 恺撒密码算法的实现 第2篇 Windows窗体篇 第7章 Windows窗体基础 7.1 设置窗体位置 实例157 控制窗体加载时的位置 实例158 设置窗体在屏幕中的位置 实例159 从...
  • 实例155 使用MD5算法对密码进行加密 实例156 恺撒密码算法的实现 第2篇 Windows窗体篇 第7章 Windows窗体基础 7.1 设置窗体位置 实例157 控制窗体加载时的位置 实例158 设置窗体在屏幕中的位置 实例159 从...
  • 实例155 使用MD5算法对密码进行加密 实例156 恺撒密码算法的实现 第2篇 Windows窗体篇 第7章 Windows窗体基础 7.1 设置窗体位置 实例157 控制窗体加载时的位置 实例158 设置窗体在屏幕中的位置 实例159 从...
  • 风越ASP代码生成器2.8

    2010-03-12 12:10:47
    常用加解密、编解码(DES、MD5、SHA、BASE64) 批量提取文本,支持正则表达式,可将HTML等文件指定内容存入数据库 批量查找替换,支持正则表达式 批量文件、文件夹改名,支持正则表达式 正则表达式测试器...
  • 实例240 编写MD5查看器 302 实例241 基于Base64编码 304 实例242 基于Base64解码 305 实例243 发送简单的Email 306 实例244 发送带附件的Email 308 实例245 读取XML文件属性 310 第3篇 窗体与控件应用 第11章 窗体...
  • 实例240 编写MD5查看器 302 实例241 基于Base64编码 304 实例242 基于Base64解码 305 实例243 发送简单的Email 306 实例244 发送带附件的Email 308 实例245 读取XML文件属性 310 第3篇 窗体与控件应用 第11章 窗体...
  • 实例240 编写MD5查看器 302 实例241 基于Base64编码 304 实例242 基于Base64解码 305 实例243 发送简单的Email 306 实例244 发送带附件的Email 308 实例245 读取XML文件属性 310 第3篇 窗体与控件应用 第11章 窗体...
  • 实例240 编写MD5查看器 302 实例241 基于Base64编码 304 实例242 基于Base64解码 305 实例243 发送简单的Email 306 实例244 发送带附件的Email 308 实例245 读取XML文件属性 310 第3篇 窗体与控件应用 第11章 窗体...
  • 实例300 修改文件名称 实例301 文件的简单读写 实例302 修改文本文件 实例303 查看文件是否存在 13.2 对文件夹的操作 实例304 创建、移动和删除文件夹 实例305 修改文件夹名称 实例306 查看文件夹是否存在 13.3 文件...
  • 实例300 修改文件名称 实例301 文件的简单读写 实例302 修改文本文件 实例303 查看文件是否存在 13.2 对文件夹的操作 实例304 创建、移动和删除文件夹 实例305 修改文件夹名称 实例306 查看文件夹是否存在 13.3 文件...
  • 实例300 修改文件名称 实例301 文件的简单读写 实例302 修改文本文件 实例303 查看文件是否存在 13.2 对文件夹的操作 实例304 创建、移动和删除文件夹 实例305 修改文件夹名称 实例306 查看文件夹是否存在 13.3 文件...

空空如也

空空如也

1 2 3
收藏数 57
精华内容 22
关键字:

手机修改文件指定md5值