Android热更新技术探索

热更新相关概念
  • 组件化---就是将一个app分成多个模块,每个模块都是一个组件(Module),开发的过程中我们可以让这些组件相互依赖或者单独调试部分组件等,但是最终发布的时候是将这些组件合并统一成一个apk,这就是组件化开发。我之前的开发方式基本上都是这一种。具体可以参考Android组件化方案
  • 插件化---将整个app拆分成很多模块,这些模块包括一个宿主多个插件,每个模块都是一个apk(组件化的每个模块是个lib),最终打包的时候将宿主apk和插件apk分开或者联合打包。开发中,往往会堆积很多的需求进项目,超过 65535 后,插件化就是一个解决方案。

具体组件化和插件化分析大家可以看这个系列,讲解和例子以及源码都很清楚,APP项目如何与插件化无缝结合,放张图帮大家理解:


组件化和插件化
  • 热更新 --- 更新的类或者插件粒度较小的时候,我们会称之为热修复,一般用于修复bug!!比如更新一个bug方法或者紧急修改lib包,甚至一个类等。2016 Google 的 Android Studio 推出了Instant Run 功能 同时提出了3个名词;

    • “热部署” – 方法内的简单修改,无需重启app和Activity。
    • “暖部署” – app无需重启,但是activity需要重启,比如资源的修改。
    • “冷部署” – app需要重启,比如继承关系的改变或方法的签名变化等。
  • 增量更新---与热更新区别最大的一个,其实这个大家应该很好理解,安卓上的有些很大的应用,特别是游戏,大则好几个G的多如牛毛,但是每次更新的时候却不是要去下载最新版,而只是下载一个几十兆的增量包就可以完成更新了,而这所使用的技术就是增量更新了。实现的过程大概是这个样子的:我们手机上安装着某个大应用,下载增量包之后,手机上的apk和增量包合并形成新的包,然后会再次安装,这个安装过程可能是可见的,或者应用本身有足够的权限直接在后台安装完成。

今天碰到Android Studio的更新,这应该就是增量更新啦!补丁包只有51M,如果下载新版本有1G多。


Studio增量更新

而热更新究竟是什么呢?

有一些这样的情况, 当一个App发布之后,突然发现了一个严重bug需要进行紧急修复,这时候公司各方就会忙得焦头烂额:重新打包App、测试、向各个应用市场和渠道换包、提示用户升级、用户下载、覆盖安装。有时候仅仅是为了修改了一行代码,也要付出巨大的成本进行换包和重新发布。老是发布版本用户会疯掉的!!!(好吧 猿猿们也会疯掉。。)

这时候就提出一个问题:有没有办法以补丁的方式动态修复紧急Bug,不再需要重新发布App,不再需要用户重新下载,覆盖安装?

这种需要替换运行时新的类和资源文件的加载,就可以认为是热操作了。而在热更新出现之前,通过反射注解、反射调用和反射注入等方式已经可以实现类的动态加载了。而热更新框架的出现就是为了解决这样一个问题的。

从某种意义上来说,热更新就是要做一件事,替换。当替换的东西属于大块内容的时候,就是模块化了,当你去替换方法的时候,叫热更新,当你替换类的时候,加热插件,而且重某种意义上讲,所有的热更新方案,都是一种热插件,因为热更新方案就是在app之外去干这个事。就这么简单的理解。无论是替换一个类,还是一个方法,都是在干替换这件事请。。这里的替换,也算是几种hook操作,无论在什么代码等级上,都是一种侵入性的操作。

所以总结一句话简单理解热更新就是改变app运行行为的技术!(或者说就是对已发布app进行bug修复的技术) 此时的猿猿们顿时眼前一亮,用户也笑了。。

好的,现在我们已经知道热更新为何物了,那么我们就先看看热更新都有哪些成熟的方案在使用了。

热更新方案介绍

热更新方案发展至今,有很多团队开发过不同的解决方案,包括Dexposed、AndFix,(HotFix)Sophix,Qzone超级补丁的类Nuwa方式,微信的Tinker, 大众点评的nuwa、百度金融的rocooFix, 饿了么的amigo以及美团的robust、腾讯的Bugly热更新。

苹果公司现在已经禁止了热更新,不过估计也组织不了开发者们的热情吧!

我先讲几种方案具体如何使用,说下原理,最后再讲如何实现一个自己的热更新方案!

Dexposed / AndFix / (HotFix)SopHix ---阿里热更新方案

Dexposed (阿里热更新方案一)

"Dexposed" 是大厂阿里以前的一个开源热更新项目,基于 Xposed "Xposed"的AOP框架,方法级粒度,可以进行AOP编程、插桩、热补丁、SDK hook等功能。

Xposeed 大家如果不熟悉的话可以看下: Xposed源码剖析——概述,我以前用 Xposed 做过一些小东西(其实就是获取 root 权限后hook 修改一些手机数据,比如支付宝步数,qq 微信步数等,当然了,余额啥的是改不了滴),在这里就不献丑了,毕竟重点也不是这个。我们可以看出 Xposed 有一个缺陷就是需要 root ,而 Dexposed 就是一个不需要 root 权限的 hook 框架。以前阿里的主流 app ,例如手机淘宝,支付宝,天猫都使用了 Dexposed 支持在线热更新,现在已经不用了,用最新的 Sophix 了,后面讲。

Dexposed 中的 AOP 原理来自于 Xposed。在 Dalvik 虚拟机下,主要是通过改变一个方法对象方法在 Dalvik 虚拟机中的定义来实现,具体做法就是将该方法的类型改变为 native 并且将这个方法的实现链接到一个通用的 Native Dispatch 方法上。这个 Dispatch 方法通过 JNI 回调到 Java 端的一个统一处理方法,最后在统一处理方法中调用 before , after 函数来实现AOP。在 Art 虚拟机上目前也是是通过改变一个 ArtMethod 的入口函数来实现。

Dexposed

可惜 android 4.4之后的版本都用 Art 取代了 Dalvik ,所以要 hook Android4.4 以后的版本就必须去适配 Art 虚拟机的机制。目前官方表示,为了适配 Art 的 dexposed_l 只是 beta 版,所以最好不要在正式的线上产品中使用它。

现在阿里已经抛弃 Dexposed 了,原因很明显,4.4 以后不支持了,我们就不细细分析这个方案了,感兴趣的朋友可以通过"这里"了解。简单讲下它的实现方式:

  • 1.引入一个名为 patchloader 的 jar 包,这个函数库实现了一个热更新框架,宿主 apk (可能含有 bug 的上线版本)在发布时会将这个 jar 包一起打包进 apk 中;
  • 2.补丁 apk (已修复线上版本 bug 的版本)只是在编译时需要这个 jar 包,但打包成 apk 时不包含这个 jar 包,以免补丁 apk 集成到宿主 apk 中时发生冲突;
  • 3.补丁 apk 将会以 provided 的形式依赖 dexposedbridge.jar 和 patchloader.jar;
  • 4.通过在线下载的方式从服务器下载补丁 apk ,补丁 apk 集成到宿主 apk 中,使用补丁 apk 中的函数替换原来的函数,从而实现在线修复 bug 的功能。

AndFix (阿里热更新方案二)

AndFix 是一个 Android App 的在线热补丁框架。使用此框架,我们能够在不重复发版的情况下,在线修改 App 中的 Bug 。AndFix 就是 “Android Hot-Fix”的缩写。支持 Android 2.3到6.0版本,并且支持 arm 与 X86 系统架构的设备。完美支持 Dalvik 与 ART 的 Runtime。AndFix 的补丁文件是以 .apatch 结尾的文件。它从你的服务器分发到你的客户端来修复你 App 的 bug 。

AndFix 更新实现过程:


AndFix 更新实现过程

1.首先添加依赖

compile 'com.alipay.euler:andfix:0.3.1@aar'

2.然后在Application.onCreate()中添加以下代码:

patchManager = new PatchManager(context);
patchManager.init(appversion); //current version
patchManager.loadPatch();

3.可以用这句话获取 appversion,每次 appversion 变更都会导致所有补丁被删除,如果 appversion 没有改变,则会加载已经保存的所有补丁。

String appversion= getPackageManager().getPackageInfo(getPackageName(), 0).versionName;

4.然后在需要的地方调用 PatchManager 的 addPatch 方法加载新补丁,比如可以在下载补丁文件之后调用。

5.之后就是打补丁的过程了,首先生成一个 apk 文件,然后更改代码,在修复 bug 后生成另一个 apk。通过官方提供的工具 apkpatch 生成一个 .apatch 格式的补丁文件,需要提供原 apk,修复后的 apk,以及一个签名文件。

6.通过网络传输或者 adb push 的方式将 apatch 文件传到手机上,然后运行到 addPatch 的时候就会加载补丁。

AndFix 更新的原理:

1.首先通过虚拟机的 JarFile 加载补丁文件,然后读取 PATCH.MF 文件得到补丁类的名称
2.使用 DexFile 读取 patch 文件中的 dex 文件,得到后根据注解来获取补丁方法,然后根据注解中得到雷鸣和方法名,使用 classLoader 获取到 Class,然后根据反射得到 bug 方法。
3.jni 层使用 C++ 的指针替换 bug 方法对象的属性来修复 bug。

具体的实现主要都是我们在 Application 中初始化的PatchManager中(具体分析在后面的注释可以看到)。

public PatchManager(Context context) {
    mContext = context;
    mAndFixManager = new AndFixManager(mContext);//初始化AndFixManager
    mPatchDir = new File(mContext.getFilesDir(), DIR);//初始化存放patch补丁文件的文件夹
    mPatchs = new ConcurrentSkipListSet<Patch>();//初始化存在Patch类的集合,此类适合大并发
    mLoaders = new ConcurrentHashMap<String, ClassLoader>();//初始化存放类对应的类加载器集合
}

其中mAndFixManager = new AndFixManager(mContext);的实现:

public AndFixManager(Context context) {
    mContext = context;
    mSupport = Compat.isSupport();//判断Android机型是否适支持AndFix
    if (mSupport) {
        mSecurityChecker = new SecurityChecker(mContext);//初始化签名判断类
        mOptDir = new File(mContext.getFilesDir(), DIR);//初始化patch文件存放的文件夹
        if (!mOptDir.exists() && !mOptDir.mkdirs()) {// make directory fail
            mSupport = false;
            Log.e(TAG, "opt dir create error.");
        } else if (!mOptDir.isDirectory()) {// not directory
            mOptDir.delete();//如果不是文件目录就删除
            mSupport = false;
        }
    }
}

。。。。。。。。。。。。

然后是对版本的初始化mPatchManager.init(appversion),init(String appVersion)代码如下:

 public void init(String appVersion) {    
    if (!mPatchDir.exists() && !mPatchDir.mkdirs()) {// make directory fail
        Log.e(TAG, "patch dir create error.");        
        return;
    } else if (!mPatchDir.isDirectory()) {// not directory
        mPatchDir.delete();        
        return;
    }
    SharedPreferences sp = mContext.getSharedPreferences(SP_NAME,
            Context.MODE_PRIVATE);//存储关于patch文件的信息
    //根据你传入的版本号和之前的对比,做不同的处理
    String ver = sp.getString(SP_VERSION, null);    
    if (ver == null || !ver.equalsIgnoreCase(appVersion)) {
        cleanPatch();//删除本地patch文件
        sp.edit().putString(SP_VERSION, appVersion).commit();//并把传入的版本号保存
    } else {
        initPatchs();//初始化patch列表,把本地的patch文件加载到内存
    }
}/*************省略初始化、删除、加载具体方法实现*****************/

init 初始化主要是对 patch 补丁文件信息进行保存或者删除以及加载。

那么 patch 补丁文件是如何加载的呢?其实 patch 补丁文件本质上是一个 jar 包,使用 JarFile 来读取即可:

public Patch(File file) throws IOException {
    mFile = file;
    init();
}

@SuppressWarnings("deprecation")
private void init() throws IOException {
    JarFile jarFile = null;
    InputStream inputStream = null;    
    try {
        jarFile = new JarFile(mFile);//使用JarFile读取Patch文件
        JarEntry entry = jarFile.getJarEntry(ENTRY_NAME);//获取META-INF/PATCH.MF文件
        inputStream = jarFile.getInputStream(entry);
        Manifest manifest = new Manifest(inputStream);
        Attributes main = manifest.getMainAttributes();
        mName = main.getValue(PATCH_NAME);//获取PATCH.MF属性Patch-Name
        mTime = new Date(main.getValue(CREATED_TIME));//获取PATCH.MF属性Created-Time

        mClassesMap = new HashMap<String, List<String>>();
        Attributes.Name attrName;
        String name;
        List<String> strings;        
    for (Iterator<?> it = main.keySet().iterator(); it.hasNext();) {
            attrName = (Attributes.Name) it.next();
            name = attrName.toString();            
            //判断name的后缀是否是-Classes,并把name对应的值加入到集合中,对应的值就是class类名的列表
            if (name.endsWith(CLASSES)) {
                strings = Arrays.asList(main.getValue(attrName).split(","));                
    if (name.equalsIgnoreCase(PATCH_CLASSES)) {
                    mClassesMap.put(mName, strings);
                } else {
                    mClassesMap.put(
                            name.trim().substring(0, name.length() - 8),// remove
                                                                        // "-Classes"
                            strings);
                }
            }
        }
    } finally {        if (jarFile != null) {
            jarFile.close();
        }        if (inputStream != null) {
            inputStream.close();
        }
    }
}

然后就是最重要的patchManager.loadPatch():

public void loadPatch() {
    mLoaders.put("*", mContext.getClassLoader());// wildcard
    Set<String> patchNames;
    List<String> classes;    
    for (Patch patch : mPatchs) {
        patchNames = patch.getPatchNames();        
    for (String patchName : patchNames) {
            classes = patch.getClasses(patchName);//获取patch对应的class类的集合List
            mAndFixManager.fix(patch.getFile(), mContext.getClassLoader(),
                    classes);//修复bug方法
        }
    }
}

循环获取补丁对应的 class 类来修复 bug 方法,mAndFixManager.fix(patch.getFile(), mContext.getClassLoader(),classes):
篇幅所限,代码可点击左下角“阅读原文”查看。

从上面的 bug 修复源码可以看出,就是在找补丁包中有 @MethodReplace 注解的方法,然后反射获取原 apk 中方法的位置,最后进行替换。
而最后调用的 replaceMethod(Method dest,Method src) 则是 native 方法,源码中有两个 replaceMethod:

extern void dalvik_replaceMethod(JNIEnv* env, jobject src, jobject dest);//Dalvik
extern void art_replaceMethod(JNIEnv* env, jobject src, jobject dest);//Art

从源码的注释也能看出来,因为安卓 4.4 版本之后使用的不再是 Dalvik 虚拟机,而是 Art 虚拟机,所以需要对不同的手机系统做不同的处理。
首先看 Dalvik 替换方法的实现:

extern void __attribute__ ((visibility ("hidden"))) dalvik_replaceMethod(
    JNIEnv* env, jobject src, jobject dest) {
    jobject clazz = env->CallObjectMethod(dest, jClassMethod);
    ClassObject* clz = (ClassObject*) dvmDecodeIndirectRef_fnPtr(
        dvmThreadSelf_fnPtr(), clazz);
    clz->status = CLASS_INITIALIZED;

    Method* meth = (Method*) env->FromReflectedMethod(src);
    Method* target = (Method*) env->FromReflectedMethod(dest);
    LOGD("dalvikMethod: %s", meth->name);

    meth->jniArgInfo = 0x80000000;
    meth->accessFlags |= ACC_NATIVE;//把Method的属性设置成Native方法

    int argsSize = dvmComputeMethodArgsSize_fnPtr(meth);    
    if (!dvmIsStaticMethod(meth))
    argsSize++;
    meth->registersSize = meth->insSize = argsSize;
    meth->insns = (void*) target;

    meth->nativeFunc = dalvik_dispatcher;//把方法的实现替换成native方法
}

Art 替换方法的实现:

//不同的art系统版本不同处理也不同extern void __attribute__ ((visibility ("hidden"))) art_replaceMethod(
        JNIEnv* env, jobject src, jobject dest) {    
    if (apilevel > 22) {
        replace_6_0(env, src, dest);
    } else if (apilevel > 21) {
        replace_5_1(env, src, dest);
    } else {
        replace_5_0(env, src, dest);
    }
}//以5.0为例:void replace_5_0(JNIEnv* env, jobject src, jobject dest) {
    art::mirror::ArtMethod* smeth =
            (art::mirror::ArtMethod*) env->FromReflectedMethod(src);
   
    art::mirror::ArtMethod* dmeth =
            (art::mirror::ArtMethod*) env->FromReflectedMethod(dest);
   
    dmeth->declaring_class_->class_loader_ =
            smeth->declaring_class_->class_loader_; //for plugin classloader
    dmeth->declaring_class_->clinit_thread_id_ =
            smeth->declaring_class_->clinit_thread_id_;
    dmeth->declaring_class_->status_ = (void *)((int)smeth->declaring_class_->status_-1);    
    //把一些参数的指针给补丁方法
    smeth->declaring_class_ = dmeth->declaring_class_;
    smeth->access_flags_ = dmeth->access_flags_;
    smeth->frame_size_in_bytes_ = dmeth->frame_size_in_bytes_;
    smeth->dex_cache_initialized_static_storage_ =
            dmeth->dex_cache_initialized_static_storage_;
    smeth->dex_cache_resolved_types_ = dmeth->dex_cache_resolved_types_;
    smeth->dex_cache_resolved_methods_ = dmeth->dex_cache_resolved_methods_;
    smeth->vmap_table_ = dmeth->vmap_table_;
    smeth->core_spill_mask_ = dmeth->core_spill_mask_;
    smeth->fp_spill_mask_ = dmeth->fp_spill_mask_;
    smeth->mapping_table_ = dmeth->mapping_table_;
    smeth->code_item_offset_ = dmeth->code_item_offset_;
    smeth->entry_point_from_compiled_code_ =
            dmeth->entry_point_from_compiled_code_;
   
    smeth->entry_point_from_interpreter_ = dmeth->entry_point_from_interpreter_;
    smeth->native_method_ = dmeth->native_method_;//把补丁方法替换掉
    smeth->method_index_ = dmeth->method_index_;
    smeth->method_dex_index_ = dmeth->method_dex_index_;
   
    LOGD("replace_5_0: %d , %d", smeth->entry_point_from_compiled_code_,
            dmeth->entry_point_from_compiled_code_);
}

其实这个替换过程可以看做三步完成
1.打开链接库得到操作句柄,获取 native 层的内部函数,得到 ClassObject 对象
2.修改访问权限的属性为 public
3.得到新旧方法的指针,新方法指向目标方法,实现方法的替换。

如果我们想知道补丁包中到底替换了哪些方法,可以直接方便易 patch 文件,然后看到的所有含有 @ReplaceMethod 注解的方法基本上就都是需要替换的方法了。

最近我在学习 C++,顿时感觉到还是这种可以控制底层的语言是多么强大,不过安卓可以通过 JNI 调用 C++,也就没什么可吐槽的了!

好的,现在 AndFix 我们分析了一遍它的实现过程和原理,其优点是不需要重启即可应用补丁,遗憾的是它还是有不少缺陷的,这直接导致阿里再次抛弃了它,缺陷如下:

1.并不能支持所有的方法修复
AndFix修复范围

2.不支持 YunOS
3.无法添加新类和新的字段
4.需要使用加固前的 apk 制作补丁,但是补丁文件很容易被反编译,也就是修改过的类源码容易泄露。
5.使用加固平台可能会使热补丁功能失效(看到有人在 360 加固提了这个问题,自己还未验证)。

Sophix---阿里终极热修复方案

不过阿里作为大厂咋可能没有个自己的热更新框架呢,所以阿里爸爸最近还是做了一个新的热更新框架 SopHix

方案对比

巴巴再次证明我是最强的,谁都没我厉害!!!因为我啥都支持,而且没缺点。。简直就是无懈可击!
那么我们就来项目集成下看看具体的使用效果吧!具体就拿支持的方法级替换来演示吧!
先去创建个应用:

创建Sophix应用

获取 AppId:24582808-1
AppSecret:da283640306b464ff68ce3b13e036a6e 以及 RSA 密钥**。三个参数配置在 application 节点下面:

    <meta-data
        android:name="com.taobao.android.hotfix.IDSECRET"
        android:value="24582808-1" />
    <meta-data
        android:name="com.taobao.android.hotfix.APPSECRET"
        android:value="da283640306b464ff68ce3b13e036a6e" />
    <meta-data
        android:name="com.taobao.android.hotfix.RSASECRET"
        android:value="MIIEvAIBA**********" /> 

添加 maven 仓库地址:

repositories {
    maven {
       url "http://maven.aliyun.com/nexus/content/repositories/releases"
    }
}    

添加 gradle 坐标版本依赖:

compile 'com.aliyun.ams:alicloud-android-hotfix:3.1.0'

项目结构也很简单:


项目结构

MainActivity:

public class MainActivity extends AppCompatActivity {    
    @Override
    protected void onCreate(Bundle savedInstanceState) {        
    super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ((TextView)findViewById(R.id.tv)).setText(String.valueOf(BuildConfig.VERSION_NAME));
        findViewById(R.id.btn_click).setOnClickListener(new View.OnClickListener() {            @Override
            public void onClick(View v) {
                Intent intent;
                intent = new Intent(MainActivity.this,SecondActivity.class);
                startActivity(intent);
            }
        });
      }
}

其实就是有一个文本框显示当前版本,还有一个按钮用来跳转到 SecondActivity
而SecondActivity的内容:

public class SecondActivity extends AppCompatActivity {    
    @Override
    protected void onCreate(Bundle savedInstanceState) {        
    super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);
        String  s  = null;
        findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {            
            @Override
            public void onClick(View v) {
                Toast.makeText(SecondActivity.this, "弹出框内容弹出错啦!", Toast.LENGTH_SHORT).show();
            }
        });
    }
}

也很简单,只有一个按钮,按钮点击之后弹出一个 Toast 显示“弹出框内容弹出错啦!”
就这样,我们的一个上线 app 完成了(粗糙是粗糙了点),下面来看下效果吧(请谅解我第一次录屏的渣渣技术,以后会做的越来越好)


bug效果

然后我们的用户开始用了,发现一个bug!“弹出框弹出的内容是错误的!”,用户可不管别的,马上给我改好啊!

此时的开发er估计心头千万头草泥马在奔腾了,求神拜佛上线不要出问题,刚上线就出问题了,“where is my 测试er!!!”不说了,赶紧修吧,最暴力的方法就是 SecondActivity 的 Toast 中弹出“弹出框内容弹正常啦!”一句代码搞定!bingo!

如果没有热更新,可能就要搞个临时版本或者甚至发布一个新版本,但是现在我们有了 Sophix ,就不需要这么麻烦了。
首先我们去下载补丁打包工具(不得不说,工具确实比较粗糙(丑)。。。)


阿里补丁工具

旧包:<必填> 选择基线包路径(有问题的 APK)。
新包:<必填> 选择新包路径(修复过该问题 APK)。
日志:打开日志输出窗口。
高级:展开高级选项
设置:配置其他信息。
GO!:开始生成补丁。

所以首先我们把旧包和新包添加上之后,配置好之后看看会发生什么吧!
强制冷启动是补丁打完后重启才生效。


配置

正在生成补丁
补丁生成成功

时间看情况吧,因为项目本身内容比较少,所以生成补丁的速度比较快,等一下就好了。项目比较大的话估计需要等的时间长一点

我们来看看到底生成了什么?打开补丁生成目录


生成的补丁文件

这个就是我们生成的补丁文件了,下一步补丁如何使用? 我们打开阿里的管理控制台,将补丁上传到控制台,就可以发布了.


补丁上传

补丁发布

这里有个坑,我用自己的中兴手机发现在使用补丁调试工具的时候一直获取包名错误,然后就借了别人的华为手机测试然后就可以了。最后我是在模拟器上完成录制的。

我们首先下载调试工具来看看效果吧,首先连接应用(坑就在这里,有的手机可能会提示包名错误,但是其实是没有错的,虽然官网给出了解决方案,可依旧没有解决,不得已只能用模拟器了)

调试工具

然后有两种方式可以加载补丁包,一种是扫二维码,还有一种是加载本地补丁jar包,模拟器上实在不好操作啊!!!最后我屈服了,借了同学的手机扫二维码加载补丁包了。。。然后就会有 log 提示


调试工具加载补丁包

从图中的 log 提示我们可以看出首先下载了补丁包,然后打补丁完成,要求我们重启 APP,那我们就重启呗,看到的当然就应该是补丁打好的 1.1 版本和 Toast 弹出正常啦!!


更新版本
更新Toast

当然了,目前我们还是在调试工具上加载的补丁包,我们接下来将补丁包发布后就可以不用调试工具,直接打开 app 就可以实现打补丁了,这样就完成了 bug 的修复!
其实这么看起来确实是非常简单就实现了热修复,主要我们的生成补丁工作都是交给阿里提供的工具实现了,其实我们也能看得出来,Sophix 和前面介绍的 AndFix 很像,不同的地方是补丁文件已经给出工具可以一键生成了,而且支持的东西更多了。其他比如 so 库和 library 以及资源文件的更新大家可以查看官方文档了解。

其实 Sophix 主要是在阿里百川 HotFix 的版本上的一个更新,而 HotFix 又是什么呢?

所以阿里爸爸一直在进步着呢,知道技术存在问题就要去解决问题,这不,从Dexposed-->AndFix-->HotFix-->Sophix,技术是越来越成熟了。
下面介绍另外一个大厂的几种热更新方案

热更新方案的对比
好了,上面我们也说了几种热更新的方案了,其他的热更新方案大家可以去搜索了解。

上面阿里给出了AndFix和HotFix以及Sophix的对比,现在我们就对时下的几种热更新方案进行对比,看看到底哪种好:


方案对比1

从对比中我们也能发现Sophix和Tinker作为两大巨头的最新热更新方案,都是比较厉害的,大家如果有需要的话可以去尝试下。

因为时间关系,实现自己的热更新方案还没有写完,暂时不放上来了,等我写完了会放上下一篇的链接的。谢谢大家的捧场支持!

篇幅所限,所载部分只占全文的三分之一,省略作者对于Qzone超级补丁 & 微信Tinker 腾讯热更新方案的详细对比,可以点击左下角“阅读原文”查看全部,大量内容不容错过。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容