使用Dexposed为你的安卓应用加上在线修复的能力。

来源:互联网 发布:显示器亮点测试软件 编辑:程序博客网 时间:2024/04/29 03:59

from:http://dengyin2000.iteye.com/blog/2234430

移动客户端应用相对于Webapp的最大一个问题是做不到Online bug fix。如果全量发出去一个版本出现一个critical bug,紧急或者有安全漏洞的问题。这时你就会哭爹喊娘了。 如果是Webapp你可能最多花个1,2个小时紧急发布上线,但是app呢,打包,跪求市场发布几百个渠道,周么还发不了,app配置升级,你还不能配置强制升级, 就算配置提示升级,用户心里肯定想,老子他妈的前两天刚升级最新版,怎么又要升,而且升级要流量,这时候会去点升级按钮的用户肯定少得可怜。这时候你就能体会到server side发布的美好。所以这里有两个问题需要解决,1是安卓是否有能力做到打补丁,2是如何设计才能精确的把补丁推送到线上有问题的app。 

1. 淘宝Dexposed框架 (https://github.com/alibaba/dexposed) 
如果你是撸机高手,你一定听过安卓神器Xposed框架,Xposed框架是一款可以在不修改APK的情况下影响程序运行(修改系统)的框架服务,基于它可以制作出许多功能强大的模块。什么你没听过,那赶紧谷歌下,你会发现你的安卓机可玩性大大提高。其实Xposed也是安卓的一个开源框架https://github.com/rovo89/Xposed https://github.com/rovo89/XposedBridge,Dexposed是啥?名字为啥这么像?Dexposed是安卓系统下的一款强大的无侵入性的AOP框架,当然他是基于Xposed的。之前在特卖会安卓代码中也有使用过Aspectj来写一些埋点代码,但是之后大家都渐渐的放弃了使用Aspectj来写埋点代码了, 这里有几个原因,一个Aspectj的有着特殊的语法,有入门门槛,你还要学习怎么使用Aspectj,另外一个原因是它有自己的编译器,app打包的时候还需要编译下Aspjectj代码,会注入他自己的代码,所以他是有侵入性的,而且编译耗时比较长,编译出来的代码你也是不能调试的。Xposed提供的是无侵入性的,而且AOP就是用的java代码。只要使用他的几个api即可。他有几个典型的使用场景: 
  a. Classic AOP programming (大家常用的aop编程) 
  b. Instrumentation (for testing, performance monitoring and etc.)  用于测试,性能监控等等 
  c. Online hot patch to fix critical, emergent or security bugs   为安卓应用线上打补丁,用来解决一些严重的,紧急的或者安全漏洞的bug。 
  d. SDK hooking for a better development experience 

下面来看看如果写一些AOP的代码: 
  1. Attach a piece of code before and after all occurrences of Activity.onCreate(Bundle).  
Java代码  收藏代码
  1. // Target class, method with parameter types, followed by the hook callback (XC_MethodHook).  
  2.     DexposedBridge.findAndHookMethod(Activity.class"onCreate", Bundle.classnew XC_MethodHook() {  
  3.    
  4.         // To be invoked before Activity.onCreate().  
  5.         @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable {  
  6.             // "thisObject" keeps the reference to the instance of target class.  
  7.             Activity instance = (Activity) param.thisObject;  
  8.    
  9.             // The array args include all the parameters.  
  10.             Bundle bundle = (Bundle) param.args[0];  
  11.             Intent intent = new Intent();  
  12.             // XposedHelpers provide useful utility methods.  
  13.             XposedHelpers.setObjectField(param.thisObject, "mIntent", intent);  
  14.    
  15.             // Calling setResult() will bypass the original method body use the result as method return value directly.  
  16.             if (bundle.containsKey("return"))  
  17.                 param.setResult(null);  
  18.         }  
  19.    
  20.         // To be invoked after Activity.onCreate()  
  21.         @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable {  
  22.             XposedHelpers.callMethod(param.thisObject, "sampleMethod"2);  
  23.         }  
  24.     });  


  2. Replace the original body of the target method. 
Java代码  收藏代码
  1. DexposedBridge.findAndHookMethod(Activity.class"onCreate", Bundle.classnew XC_MethodReplacement() {  
  2.    
  3.         @Override protected Object replaceHookedMethod(MethodHookParam param) throws Throwable {  
  4.             // Re-writing the method logic outside the original method context is a bit tricky but still viable.  
  5.             ...  
  6.         }  
  7.    
  8.     });  


不过这个框架目前对android 5.0系统还不支持,他提供一个函数来检测你的android系统是否支持Dexposed,DexposedBridge.canDexposed(context)。 你需要把补丁包打包成一个apk, 然后通过下面的代码来加载补丁包: 
Java代码  收藏代码
  1. File cacheDir = getExternalCacheDir();  
  2.    
  3.         if(cacheDir != null){  
  4.    
  5.             String fullpath = cacheDir.getAbsolutePath() + File.separator + "patch.apk";  
  6.    
  7.             PatchResult result = PatchMain.load(this, fullpath, null);  
  8.    
  9.             if (result.isSuccess()) {  
  10.    
  11.                 Log.e("Hotpatch""patch success!");  
  12.    
  13.             } else {  
  14.    
  15.                 Log.e("Hotpatch""patch error is " + result.getErrorInfo());  
  16.    
  17.             }  
  18.    
  19.         }  


综上所述:安卓是可以通过淘宝的Dexposed框架来实现打补丁包的,不过目前这个项目还处理初期阶段对android 5.0系统支持有限,可能某些定制rom可能也会有问题。不过我相信这个框架会越来越好的,作者也说了会加上对5.0系统的支持。 需要深入的读者可以移步到Dexposed的github地址:https://github.com/alibaba/dexposed。 

2. 如何精确及时的推送补丁 
要做到精确投放,就要考虑bug的产生有哪几种因素和那些维度。这样我们就能精准投放,而不是投放给了那些不需要补丁的用户,这样可能让没问题的用户打补丁之后反而产生问题。 以下是可能存在的一些因素和维度: 
  1. 版本, 这个是最主要的因素, 我们经常需要就是为某个版本打补丁。 
  2. android系统版本,某些安卓系统上有兼容性问题。 
  3. 机型,某些机型有兼容性的问题。 
  4. Rom,某些Rom才有的问题。 
  5. 手机制造商, 比如小米。 
  6. mid, 可能为某个或者某些用户打补丁。 
  7. 渠道号, 为某些渠道的用户打补丁。 
  8. 地区,为某些地区的用户打补丁。 
  9. 分仓,为在某个分仓的用户打补丁。 

当然上面的因素可以有一个或者多个合并。比如我要推送补丁到某个版本上的小米手机。我们在启动信息埋点中会上传用户的这些信息,后端就可以通过这些因素来定位要推送补丁的群体。我们会有一个补丁接口返回补丁的下载链接,补丁的匹配因素和这个补丁的crc或者md5。我们会把这些信息保存在客户端的数据库中,因为可能当用户升级到下一个版本时就不需要在加载这些补丁了。 所以我们在加载这些补丁的时候需要验证这个补丁是否适当当前app。当然如果想要及时推把补丁推送到客户端手中,我们可以使用push来推,而不是当用户打开app的时候来检测。下面是大概的流程。 



注意: 这里漏了对patch apk验证的过程,这个很重要,首先对apk的md5验证, 然后最重要的需要对apk文件的签名进行验证,apk签名验证最好是放到ndk中实现, 后面的文章讲到具体实现的时候会讲到。 

3. 代码示例 
本代码示例主要是展示为以一个被除数不能为0这样的一个有bug的代码打补丁。有问题的代码如下: 
Java代码  收藏代码
  1. public void doDivide(View view) {  
  2.     doDivideLogic();  
  3. }  
  4.    
  5. public void doDivideLogic() {  
  6.     BigDecimal num1 = new BigDecimal(etNum1.getText().toString());  
  7.     BigDecimal num2 = new BigDecimal(etNum2.getText().toString());  
  8.     BigDecimal result = num1.divide(num2);             //这里num2不能为零。  
  9.     tvResult.setText(String.valueOf(result));  
  10. }  

下面的代码为补丁包代码。我们主要是替换doDivideLogic的方法。补丁包这里为一个独立的android project项目,当然你需要把依赖的主项目的class文件打包为一个jar文件作为依赖。比如如下代码的MainActivity。补丁的代码如下: 
Java代码  收藏代码
  1. /** 
  2.  * Created by denny on 2015/7/20. 
  3.  */  
  4. public class DivideLogicPatch implements IPatch {  
  5.     @Override  
  6.     public void handlePatch(PatchParam patchParam) throws Throwable {  
  7.         Class<?> cls = null;  
  8.         try {  
  9.             cls= patchParam.context.getClassLoader()  
  10.                     .loadClass("sample.dexposed.vip.com.dexposedsample.MainActivity");  
  11.         } catch (ClassNotFoundException e) {  
  12.             e.printStackTrace();  
  13.             return;  
  14.         }  
  15.         DexposedBridge.findAndHookMethod(cls, "doDivideLogic"new XC_MethodReplacement(){  
  16.    
  17.             @Override  
  18.             protected Object replaceHookedMethod(MethodHookParam methodHookParam) throws Throwable {  
  19.                 Field etNum1Field = XposedHelpers.findField(MainActivity.class"etNum1");  
  20.                 EditText etNum1 = (EditText) etNum1Field.get(methodHookParam.thisObject);  
  21.                 Field etNum2Field = XposedHelpers.findField(MainActivity.class"etNum2");  
  22.                 EditText etNum2 = (EditText) etNum2Field.get(methodHookParam.thisObject);  
  23.                 Field tvResultField = XposedHelpers.findField(MainActivity.class"tvResult");  
  24.                 TextView tvResult = (TextView) tvResultField.get(methodHookParam.thisObject);  
  25.                 String val = etNum2.getText().toString();  
  26.                 if (val != null && "0".equals(val)) {      //被除数是否为0判断。  
  27.                     tvResult.setText("被除数不能为0");  
  28.                 }else {  
  29.                     BigDecimal num1 = new BigDecimal(etNum1.getText().toString());  
  30.                     BigDecimal num2 = new BigDecimal(val);  
  31.                     BigDecimal result = num1.divide(num2);  
  32.                     tvResult.setText(String.valueOf(result));  
  33.                 }  
  34.                 return null;  
  35.             }  
  36.         });  
  37.     }  
  38. }  

发现一个问题:在加载补丁包的时候需要调用DexposedBridge.canDexposed(context)方法才能加载成功。看了下源码,原来是在这方法里面加载so文件。 
这个代码示例,我们是把补丁包app-patch.apk放到项目的assets,然后拷贝到sdcard中进行装载,如果是真实的项目的话应该是从网上下载补丁包,然后加载。代码可以从下面的链接下载: 

DexposedSample.zip 
DexposedSamplePatch.zip 

Sample运行视频
  • DexposedSample.zip (486.2 KB)
  • 下载次数: 48
  • DexposedSamplePatch.zip (1.2 MB)
  • 下载次数: 43
  • 查看图片附件

0 0
原创粉丝点击