转自:http://www.jianshu.com/p/2a7d16ab29e8
    

    
    

      本教程采用阿里dexposed开源库实现。

https://github.com/alibaba/dexposed

  </blockquote>
  
  ## 主APP实现:
  
  ### 主程序Application onCreate方法中初始化dexposed
  
  ```

&lt;span class="hljs-tag">DexposedBridge&lt;/span>&lt;span class="hljs-class">.canDexposed&lt;/span>(&lt;span class="hljs-tag">context&lt;/span>);

      
      ### Patch apk下载及修复:
      
      
        - 为保证修复patch的及时性,使用push推送patch,客户端收到消息后立即完成patch的下载及修复;
        
        - 客户端版本管理模块在程序入口Activity中检测是否有需要修复的patch;
        
        - 下载完patch apk到程序私有目录,即/data/data/packageName/files目录,同时可在xml中保存patch apk本地存储路径、方便下载启动app时加载补丁patch。
        
      
      
      ```
`    &lt;span class="hljs-keyword">public&lt;/span> &lt;span class="hljs-class">&lt;span class="hljs-keyword">class&lt;/span> &lt;span class="hljs-title">HotPatchManager&lt;/span> &lt;/span>{
    &lt;span class="hljs-keyword">public&lt;/span> &lt;span class="hljs-keyword">static&lt;/span> &lt;span class="hljs-keyword">boolean&lt;/span> canDexposed = &lt;span class="hljs-keyword">false&lt;/span>;
    &lt;span class="hljs-keyword">private&lt;/span> &lt;span class="hljs-keyword">static&lt;/span> &lt;span class="hljs-keyword">final&lt;/span> String SP_KEY_HOT_PATCH = &lt;span class="hljs-string">"hot_patch_path"&lt;/span>;

    &lt;span class="hljs-comment">/**
     * init hotPatch library.
     *
     * &lt;span class="hljs-doctag">@param&lt;/span> context
     */&lt;/span>
    &lt;span class="hljs-function">&lt;span class="hljs-keyword">public&lt;/span> &lt;span class="hljs-keyword">static&lt;/span> &lt;span class="hljs-keyword">void&lt;/span> &lt;span class="hljs-title">init&lt;/span>&lt;span class="hljs-params">(Context context)&lt;/span> &lt;/span>{
        &lt;span class="hljs-comment">// aop init.&lt;/span>
        canDexposed = DexposedBridge.canDexposed(context);
        &lt;span class="hljs-keyword">if&lt;/span> (canDexposed) {
            List&lt;String&gt; list = getHotPatchPaths(context);
            &lt;span class="hljs-keyword">if&lt;/span> (list != &lt;span class="hljs-keyword">null&lt;/span> && list.size() &gt; &lt;span class="hljs-number">0&lt;/span>) {
                &lt;span class="hljs-keyword">for&lt;/span> (String path : list) {
                    runPatchApk(context, path);
                }
            }
        } &lt;span class="hljs-keyword">else&lt;/span> {
            &lt;span class="hljs-keyword">if&lt;/span> (LogUtils.DEBUG) {
                LogUtils.d(&lt;span class="hljs-string">"==========your device not support dexposed aop.=========="&lt;/span>);
            }
        }
    }

    &lt;span class="hljs-comment">/**
     * /data/data/package/files
     *
     * &lt;span class="hljs-doctag">@param&lt;/span> context
     * &lt;span class="hljs-doctag">@param&lt;/span> apkPath
     */&lt;/span>
    &lt;span class="hljs-function">&lt;span class="hljs-keyword">public&lt;/span> &lt;span class="hljs-keyword">static&lt;/span> &lt;span class="hljs-keyword">void&lt;/span> &lt;span class="hljs-title">runPatchApk&lt;/span>&lt;span class="hljs-params">(Context context, String apkPath)&lt;/span> &lt;/span>{
        &lt;span class="hljs-keyword">if&lt;/span> (Build.VERSION.SDK_INT &gt;= &lt;span class="hljs-number">21&lt;/span> || !canDexposed) {
            LogUtils.d(&lt;span class="hljs-string">"This device doesn't support dexposed."&lt;/span>);
            &lt;span class="hljs-keyword">return&lt;/span>;
        }
        &lt;span class="hljs-keyword">if&lt;/span> (!pathIsValid(context, apkPath)) {
            &lt;span class="hljs-keyword">return&lt;/span>;
        }
        &lt;span class="hljs-keyword">try&lt;/span> {
            PatchResult result = PatchMain.load(context, apkPath, &lt;span class="hljs-keyword">null&lt;/span>);
            &lt;span class="hljs-keyword">if&lt;/span> (result.isSuccess()) {
                LogUtils.d(&lt;span class="hljs-string">"hotPath load apk success."&lt;/span>);
            } &lt;span class="hljs-keyword">else&lt;/span> {
                LogUtils.e(&lt;span class="hljs-string">"hotPath load apk error."&lt;/span>, result.getErrorInfo());
                result.getThrowbale().printStackTrace();
            }
        } &lt;span class="hljs-keyword">catch&lt;/span> (Exception e) {
            e.printStackTrace();
        }
    }

    &lt;span class="hljs-comment">/**
     * download hotPatch and auto mege.
     *
     * &lt;span class="hljs-doctag">@param&lt;/span> context
     */&lt;/span>
    &lt;span class="hljs-function">&lt;span class="hljs-keyword">public&lt;/span> &lt;span class="hljs-keyword">static&lt;/span> &lt;span class="hljs-keyword">void&lt;/span> &lt;span class="hljs-title">downloadHotPatch&lt;/span>&lt;span class="hljs-params">(&lt;span class="hljs-keyword">final&lt;/span> Context context, String downloadUrl)&lt;/span> &lt;/span>{
        &lt;span class="hljs-keyword">if&lt;/span> (TextUtils.isEmpty(downloadUrl)) {
            LogUtils.d(&lt;span class="hljs-string">"downloadUrl is null."&lt;/span>);
            &lt;span class="hljs-keyword">return&lt;/span>;
        }
        DownloadInfo downloadInfo = &lt;span class="hljs-keyword">new&lt;/span> DownloadInfo();
        downloadInfo.setDownloadUrl(downloadUrl);
        String fileName = downloadUrl.substring(downloadUrl.lastIndexOf(&lt;span class="hljs-string">"/"&lt;/span>) + &lt;span class="hljs-number">1&lt;/span>);
        String fileSavePath = &lt;span class="hljs-keyword">new&lt;/span> File(context.getFilesDir(), fileName).getAbsolutePath();
        downloadInfo.setFileSavePath(fileSavePath);
        downloadInfo.setDaoCallback(
                &lt;span class="hljs-keyword">new&lt;/span> Task.Callback() {
                    &lt;span class="hljs-annotation">@Override&lt;/span>
                    &lt;span class="hljs-function">&lt;span class="hljs-keyword">public&lt;/span> &lt;span class="hljs-keyword">void&lt;/span> &lt;span class="hljs-title">onSuccess&lt;/span>&lt;span class="hljs-params">(DownloadInfo downloadInfo)&lt;/span> &lt;/span>{
                        LogUtils.d(&lt;span class="hljs-string">"runPatchApk begin."&lt;/span>, downloadInfo.getFileSavePath());
                        runPatchApk(context, downloadInfo.getFileSavePath());
                        appendHotPatchPath(context, downloadInfo.getFileSavePath());
                        LogUtils.d(&lt;span class="hljs-string">"runPatchApk end."&lt;/span>, downloadInfo.getFileSavePath());
                    }

                    &lt;span class="hljs-annotation">@Override&lt;/span>
                    &lt;span class="hljs-function">&lt;span class="hljs-keyword">public&lt;/span> &lt;span class="hljs-keyword">void&lt;/span> &lt;span class="hljs-title">onStart&lt;/span>&lt;span class="hljs-params">(DownloadInfo downloadInfo)&lt;/span> &lt;/span>{
                    }

                    &lt;span class="hljs-annotation">@Override&lt;/span>
                    &lt;span class="hljs-function">&lt;span class="hljs-keyword">public&lt;/span> &lt;span class="hljs-keyword">void&lt;/span> &lt;span class="hljs-title">onFailure&lt;/span>&lt;span class="hljs-params">(DownloadInfo downloadInfo)&lt;/span> &lt;/span>{
                    }

                    &lt;span class="hljs-annotation">@Override&lt;/span>
                    &lt;span class="hljs-function">&lt;span class="hljs-keyword">public&lt;/span> &lt;span class="hljs-keyword">boolean&lt;/span> &lt;span class="hljs-title">onLoading&lt;/span>&lt;span class="hljs-params">(&lt;span class="hljs-keyword">long&lt;/span> total, &lt;span class="hljs-keyword">long&lt;/span> current)&lt;/span> &lt;/span>{
                        &lt;span class="hljs-keyword">return&lt;/span> &lt;span class="hljs-keyword">true&lt;/span>;
                    }

                    &lt;span class="hljs-annotation">@Override&lt;/span>
                    &lt;span class="hljs-function">&lt;span class="hljs-keyword">public&lt;/span> &lt;span class="hljs-keyword">void&lt;/span> &lt;span class="hljs-title">onCancelled&lt;/span>&lt;span class="hljs-params">(DownloadInfo downloadInfo)&lt;/span> &lt;/span>{
                    }
                }
        );
        DownloadManager dm = DownloadService.getDownloadManager(context, DownloadService.ACTION);
        dm.addDownloadTask(downloadInfo);
    }

    &lt;span class="hljs-function">&lt;span class="hljs-keyword">public&lt;/span> &lt;span class="hljs-keyword">static&lt;/span> &lt;span class="hljs-keyword">void&lt;/span> &lt;span class="hljs-title">clearHotPatchFiles&lt;/span>&lt;span class="hljs-params">(Context context)&lt;/span> &lt;/span>{
        List&lt;String&gt; list = getHotPatchPaths(context);
        &lt;span class="hljs-keyword">if&lt;/span> (list != &lt;span class="hljs-keyword">null&lt;/span> && list.size() &gt; &lt;span class="hljs-number">0&lt;/span>) {
            &lt;span class="hljs-keyword">for&lt;/span> (String path : list) {
                FileUtils.delFile(path);
            }
        }
    }

    &lt;span class="hljs-function">&lt;span class="hljs-keyword">public&lt;/span> &lt;span class="hljs-keyword">static&lt;/span> &lt;span class="hljs-keyword">boolean&lt;/span> &lt;span class="hljs-title">pathIsValid&lt;/span>&lt;span class="hljs-params">(Context context, String apkPath)&lt;/span> &lt;/span>{
        &lt;span class="hljs-keyword">if&lt;/span> (TextUtils.isEmpty(apkPath)) {
            LogUtils.d(&lt;span class="hljs-string">"apkPath is null."&lt;/span>);
            &lt;span class="hljs-keyword">return&lt;/span> &lt;span class="hljs-keyword">false&lt;/span>;
        }
        String parentDir = String.format(&lt;span class="hljs-string">"/data/data/%s/files"&lt;/span>, context.getPackageName());
        File apkFile = &lt;span class="hljs-keyword">new&lt;/span> File(apkPath);
        &lt;span class="hljs-keyword">if&lt;/span> (!parentDir.equals(apkFile.getParent())) {
            LogUtils.d(&lt;span class="hljs-string">"apkPath is error."&lt;/span>, apkPath);
            &lt;span class="hljs-keyword">return&lt;/span> &lt;span class="hljs-keyword">false&lt;/span>;
        }
        &lt;span class="hljs-keyword">if&lt;/span> (!apkFile.exists()){
            LogUtils.d(&lt;span class="hljs-string">"apkPath is not exist."&lt;/span>, apkPath);
            &lt;span class="hljs-keyword">return&lt;/span> &lt;span class="hljs-keyword">false&lt;/span>;
        }
        &lt;span class="hljs-keyword">return&lt;/span> &lt;span class="hljs-keyword">true&lt;/span>;
    }

    &lt;span class="hljs-function">&lt;span class="hljs-keyword">public&lt;/span> &lt;span class="hljs-keyword">static&lt;/span> List&lt;String&gt; &lt;span class="hljs-title">getHotPatchPaths&lt;/span>&lt;span class="hljs-params">(Context context)&lt;/span> &lt;/span>{
        List&lt;String&gt; list = &lt;span class="hljs-keyword">null&lt;/span>;
        SP sp = SP.getInstance(context);
        String paths = sp.getString(SP_KEY_HOT_PATCH, &lt;span class="hljs-keyword">null&lt;/span>);
        &lt;span class="hljs-keyword">if&lt;/span> (!TextUtils.isEmpty(paths)) {
            &lt;span class="hljs-keyword">if&lt;/span> (paths.indexOf(&lt;span class="hljs-string">","&lt;/span>) != -&lt;span class="hljs-number">1&lt;/span>) {
                String[] pathArr = paths.split(&lt;span class="hljs-string">","&lt;/span>);
                &lt;span class="hljs-keyword">if&lt;/span> (pathArr != &lt;span class="hljs-keyword">null&lt;/span> && pathArr.length &gt; &lt;span class="hljs-number">0&lt;/span>) {
                    list = Arrays.asList(paths);
                }
            } &lt;span class="hljs-keyword">else&lt;/span> {
                list = &lt;span class="hljs-keyword">new&lt;/span> ArrayList&lt;String&gt;();
                list.add(paths);
            }
        }
        &lt;span class="hljs-keyword">return&lt;/span> list;
    }

    &lt;span class="hljs-function">&lt;span class="hljs-keyword">public&lt;/span> &lt;span class="hljs-keyword">static&lt;/span> &lt;span class="hljs-keyword">void&lt;/span> &lt;span class="hljs-title">appendHotPatchPath&lt;/span>&lt;span class="hljs-params">(Context context, String apkPath)&lt;/span> &lt;/span>{
        &lt;span class="hljs-keyword">if&lt;/span> (!pathIsValid(context, apkPath)) {
            &lt;span class="hljs-keyword">return&lt;/span>;
        }

        SP sp = SP.getInstance(context);
        String paths = sp.getString(SP_KEY_HOT_PATCH, &lt;span class="hljs-keyword">null&lt;/span>);
        &lt;span class="hljs-keyword">if&lt;/span> (!TextUtils.isEmpty(paths)) {
            String allPath = &lt;span class="hljs-keyword">new&lt;/span> StringBuilder(apkPath).append(&lt;span class="hljs-string">","&lt;/span>).append(apkPath).toString();
            sp.commit(SP_KEY_HOT_PATCH, allPath);
        } &lt;span class="hljs-keyword">else&lt;/span> {
            sp.commit(SP_KEY_HOT_PATCH, apkPath);
        }
    }

    &lt;span class="hljs-function">&lt;span class="hljs-keyword">public&lt;/span> &lt;span class="hljs-keyword">static&lt;/span> &lt;span class="hljs-keyword">void&lt;/span> &lt;span class="hljs-title">clearHotPatchPaths&lt;/span>&lt;span class="hljs-params">(Context context)&lt;/span> &lt;/span>{
        SP sp = SP.getInstance(context);
        sp.commit(SP_KEY_HOT_PATCH, &lt;span class="hljs-string">""&lt;/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">
        

          ![](http://upload-images.jianshu.io/upload_images/711578-70dc9b6a8049b976.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
        

        
        <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>

&nbsp;

<div>
  文/ancode(简书作者)
 原文链接:http://www.jianshu.com/p/2a7d16ab29e8
 著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。
</div>

💬 评论