转自: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,可以实现整个方法的替换或方法前、后执行修复代码。 以下实例为方法替换实例,其它只需实现相应的回调接口即可。
...