转自:http://www.jianshu.com/p/2a7d16ab29e8 本教程采用阿里dexposed开源库实现。https://github.com/alibaba/dexposed
</blockquote> ## 主APP实现: ### 主程序Application onCreate方法中初始化dexposed ```
<span class="hljs-tag">DexposedBridge</span><span class="hljs-class">.canDexposed</span>(<span class="hljs-tag">context</span>);### Patch apk下载及修复: - 为保证修复patch的及时性,使用push推送patch,客户端收到消息后立即完成patch的下载及修复; - 客户端版本管理模块在程序入口Activity中检测是否有需要修复的patch; - 下载完patch apk到程序私有目录,即/data/data/packageName/files目录,同时可在xml中保存patch apk本地存储路径、方便下载启动app时加载补丁patch。 ``` ` <span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">HotPatchManager</span> </span>{ <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">boolean</span> canDexposed = <span class="hljs-keyword">false</span>; <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> String SP_KEY_HOT_PATCH = <span class="hljs-string">"hot_patch_path"</span>; <span class="hljs-comment">/** * init hotPatch library. * * <span class="hljs-doctag">@param</span> context */</span> <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">init</span><span class="hljs-params">(Context context)</span> </span>{ <span class="hljs-comment">// aop init.</span> canDexposed = DexposedBridge.canDexposed(context); <span class="hljs-keyword">if</span> (canDexposed) { List<String> list = getHotPatchPaths(context); <span class="hljs-keyword">if</span> (list != <span class="hljs-keyword">null</span> && list.size() > <span class="hljs-number">0</span>) { <span class="hljs-keyword">for</span> (String path : list) { runPatchApk(context, path); } } } <span class="hljs-keyword">else</span> { <span class="hljs-keyword">if</span> (LogUtils.DEBUG) { LogUtils.d(<span class="hljs-string">"==========your device not support dexposed aop.=========="</span>); } } } <span class="hljs-comment">/** * /data/data/package/files * * <span class="hljs-doctag">@param</span> context * <span class="hljs-doctag">@param</span> apkPath */</span> <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">runPatchApk</span><span class="hljs-params">(Context context, String apkPath)</span> </span>{ <span class="hljs-keyword">if</span> (Build.VERSION.SDK_INT >= <span class="hljs-number">21</span> || !canDexposed) { LogUtils.d(<span class="hljs-string">"This device doesn't support dexposed."</span>); <span class="hljs-keyword">return</span>; } <span class="hljs-keyword">if</span> (!pathIsValid(context, apkPath)) { <span class="hljs-keyword">return</span>; } <span class="hljs-keyword">try</span> { PatchResult result = PatchMain.load(context, apkPath, <span class="hljs-keyword">null</span>); <span class="hljs-keyword">if</span> (result.isSuccess()) { LogUtils.d(<span class="hljs-string">"hotPath load apk success."</span>); } <span class="hljs-keyword">else</span> { LogUtils.e(<span class="hljs-string">"hotPath load apk error."</span>, result.getErrorInfo()); result.getThrowbale().printStackTrace(); } } <span class="hljs-keyword">catch</span> (Exception e) { e.printStackTrace(); } } <span class="hljs-comment">/** * download hotPatch and auto mege. * * <span class="hljs-doctag">@param</span> context */</span> <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">downloadHotPatch</span><span class="hljs-params">(<span class="hljs-keyword">final</span> Context context, String downloadUrl)</span> </span>{ <span class="hljs-keyword">if</span> (TextUtils.isEmpty(downloadUrl)) { LogUtils.d(<span class="hljs-string">"downloadUrl is null."</span>); <span class="hljs-keyword">return</span>; } DownloadInfo downloadInfo = <span class="hljs-keyword">new</span> DownloadInfo(); downloadInfo.setDownloadUrl(downloadUrl); String fileName = downloadUrl.substring(downloadUrl.lastIndexOf(<span class="hljs-string">"/"</span>) + <span class="hljs-number">1</span>); String fileSavePath = <span class="hljs-keyword">new</span> File(context.getFilesDir(), fileName).getAbsolutePath(); downloadInfo.setFileSavePath(fileSavePath); downloadInfo.setDaoCallback( <span class="hljs-keyword">new</span> Task.Callback() { <span class="hljs-annotation">@Override</span> <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onSuccess</span><span class="hljs-params">(DownloadInfo downloadInfo)</span> </span>{ LogUtils.d(<span class="hljs-string">"runPatchApk begin."</span>, downloadInfo.getFileSavePath()); runPatchApk(context, downloadInfo.getFileSavePath()); appendHotPatchPath(context, downloadInfo.getFileSavePath()); LogUtils.d(<span class="hljs-string">"runPatchApk end."</span>, downloadInfo.getFileSavePath()); } <span class="hljs-annotation">@Override</span> <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onStart</span><span class="hljs-params">(DownloadInfo downloadInfo)</span> </span>{ } <span class="hljs-annotation">@Override</span> <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onFailure</span><span class="hljs-params">(DownloadInfo downloadInfo)</span> </span>{ } <span class="hljs-annotation">@Override</span> <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">onLoading</span><span class="hljs-params">(<span class="hljs-keyword">long</span> total, <span class="hljs-keyword">long</span> current)</span> </span>{ <span class="hljs-keyword">return</span> <span class="hljs-keyword">true</span>; } <span class="hljs-annotation">@Override</span> <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onCancelled</span><span class="hljs-params">(DownloadInfo downloadInfo)</span> </span>{ } } ); DownloadManager dm = DownloadService.getDownloadManager(context, DownloadService.ACTION); dm.addDownloadTask(downloadInfo); } <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">clearHotPatchFiles</span><span class="hljs-params">(Context context)</span> </span>{ List<String> list = getHotPatchPaths(context); <span class="hljs-keyword">if</span> (list != <span class="hljs-keyword">null</span> && list.size() > <span class="hljs-number">0</span>) { <span class="hljs-keyword">for</span> (String path : list) { FileUtils.delFile(path); } } } <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">pathIsValid</span><span class="hljs-params">(Context context, String apkPath)</span> </span>{ <span class="hljs-keyword">if</span> (TextUtils.isEmpty(apkPath)) { LogUtils.d(<span class="hljs-string">"apkPath is null."</span>); <span class="hljs-keyword">return</span> <span class="hljs-keyword">false</span>; } String parentDir = String.format(<span class="hljs-string">"/data/data/%s/files"</span>, context.getPackageName()); File apkFile = <span class="hljs-keyword">new</span> File(apkPath); <span class="hljs-keyword">if</span> (!parentDir.equals(apkFile.getParent())) { LogUtils.d(<span class="hljs-string">"apkPath is error."</span>, apkPath); <span class="hljs-keyword">return</span> <span class="hljs-keyword">false</span>; } <span class="hljs-keyword">if</span> (!apkFile.exists()){ LogUtils.d(<span class="hljs-string">"apkPath is not exist."</span>, apkPath); <span class="hljs-keyword">return</span> <span class="hljs-keyword">false</span>; } <span class="hljs-keyword">return</span> <span class="hljs-keyword">true</span>; } <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> List<String> <span class="hljs-title">getHotPatchPaths</span><span class="hljs-params">(Context context)</span> </span>{ List<String> list = <span class="hljs-keyword">null</span>; SP sp = SP.getInstance(context); String paths = sp.getString(SP_KEY_HOT_PATCH, <span class="hljs-keyword">null</span>); <span class="hljs-keyword">if</span> (!TextUtils.isEmpty(paths)) { <span class="hljs-keyword">if</span> (paths.indexOf(<span class="hljs-string">","</span>) != -<span class="hljs-number">1</span>) { String[] pathArr = paths.split(<span class="hljs-string">","</span>); <span class="hljs-keyword">if</span> (pathArr != <span class="hljs-keyword">null</span> && pathArr.length > <span class="hljs-number">0</span>) { list = Arrays.asList(paths); } } <span class="hljs-keyword">else</span> { list = <span class="hljs-keyword">new</span> ArrayList<String>(); list.add(paths); } } <span class="hljs-keyword">return</span> list; } <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">appendHotPatchPath</span><span class="hljs-params">(Context context, String apkPath)</span> </span>{ <span class="hljs-keyword">if</span> (!pathIsValid(context, apkPath)) { <span class="hljs-keyword">return</span>; } SP sp = SP.getInstance(context); String paths = sp.getString(SP_KEY_HOT_PATCH, <span class="hljs-keyword">null</span>); <span class="hljs-keyword">if</span> (!TextUtils.isEmpty(paths)) { String allPath = <span class="hljs-keyword">new</span> StringBuilder(apkPath).append(<span class="hljs-string">","</span>).append(apkPath).toString(); sp.commit(SP_KEY_HOT_PATCH, allPath); } <span class="hljs-keyword">else</span> { sp.commit(SP_KEY_HOT_PATCH, apkPath); } } <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">clearHotPatchPaths</span><span class="hljs-params">(Context context)</span> </span>{ SP sp = SP.getInstance(context); sp.commit(SP_KEY_HOT_PATCH, <span class="hljs-string">""</span>); } }`## Patch Apk部分: <blockquote> dexpose支持方法粒度的patch,可以实现整个方法的替换或方法前、后执行修复代码。以下实例为方法替换实例,其它只需实现相应的回调接口即可。
</blockquote> ### 方法替换实例: - 新建Android工程,引入patchloader.jar、dexposedbridge.jar; - 创建Patch修复类实现IPatch接口; ```` <span class=“hljs-keyword”>public</span> <span class=“hljs-class”><span class=“hljs-keyword”>class</span> <span class=“hljs-title”>HotPatch</span> <span class=“hljs-keyword”>implements</span> <span class=“hljs-title”>IPatch</span> </span>{
<span class=“hljs-annotation”>@Override</span> <span class=“hljs-function”><span class=“hljs-keyword”>public</span> <span class=“hljs-keyword”>void</span> <span class=“hljs-title”>handlePatch</span><span class=“hljs-params”>(<span class=“hljs-keyword”>final</span> PatchParam arg0)</span> <span class=“hljs-keyword”>throws</span> Throwable </span>{
Class<?> cls = <span class=“hljs-keyword”>null</span>; <span class=“hljs-keyword”>try</span> { cls= arg0.context.getClassLoader() .loadClass(<span class=“hljs-string”>“com.zaozuo.app.MainActivity”</span>); } <span class=“hljs-keyword”>catch</span> (ClassNotFoundException e) { e.printStackTrace(); <span class=“hljs-keyword”>return</span>; }
DexposedBridge.findAndHookMethod(cls, <span class=“hljs-string”>“bindData”</span>, <span class=“hljs-keyword”>new</span> XC_MethodReplacement() { <span class=“hljs-annotation”>@Override</span> <span class=“hljs-function”><span class=“hljs-keyword”>protected</span> Object <span class=“hljs-title”>replaceHookedMethod</span><span class=“hljs-params”>(MethodHookParam param)</span> <span class=“hljs-keyword”>throws</span> Throwable </span>{ Activity mainActivity = (Activity) param.thisObject; Toast.makeText(mainActivity, <span class=“hljs-string”>“test show hotPatch."</span>,Toast.LENGTH_LONG).show(); <span class=“hljs-keyword”>return</span> <span class=“hljs-keyword”>null</span>;
} }); }}`
- 打包patch apk,上传到服务器并通知客户端下载。 ### Patch Apk安全性: - 打包apk必须使用主app签名文件签名; - 主app对加载的patch apk做签名和无篡改校验: <div class="image-package imagebubble">  <div class="image-caption"> 手机扫码快速访问 </div> </div> </div> </div> </div> <div class="visitor_edit"> <!-- further readings --> <div id="further-readings" data-user-slug="" data-user-nickname=""> <div id="further-reading-line" class="hide further-reading-line"> </div> <div id="further-reading-new" class="reading-edit"> [** 推荐拓展阅读](/sign_in) <div id="further-reading-form"> </div> </div> </div> <div class="pull-right"> <!-- copyright --> <div class="author-copyright pull-right" title="" data-toggle="tooltip" data-html="true" data-original-title="转载请联系作者获得授权,并标注“简书作者”。"> <a>** 著作权归作者所有</a> </div> </div> </div> <!-- Reward / Support Author --> <div class="support-author"> </div> <div> 文/ancode(简书作者) 原文链接:http://www.jianshu.com/p/2a7d16ab29e8 著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。 </div>
Android-HotPatch在线热补丁方案
转自:http://www.jianshu.com/p/2a7d16ab29e8 本教程采用阿里dexposed开源库实现。 [https://github.com/alibaba/dexposed](https://github.com/alibaba/dexposed) ## 主APP实现: #...
💬 评论