精华内容
下载资源
问答
  • 热更新

    千次阅读 2017-07-25 19:22:28
    虽然有插件开发,但热更新少不了。 当我们需要更新插件的时候使用的是插件开发,当我们需要更新宿主程序就需要使用热更新 热更新使用框架bsdiff和bzip2 我们需要在服务端使用新版apk和旧版apk生成差分包,由于...

    虽然有插件开发,但热更新少不了。


    当我们需要更新插件的时候使用的是插件开发,当我们需要更新宿主程序就需要使用热更新

    热更新使用框架bsdiff和bzip2

    我们需要在服务端使用新版apk和旧版apk生成差分包,由于服务端在windows下,所以生成动态文件(dll)

    通过寻找bsdiff的入口函数在bsdiff.cpp中在,因为截止到现在我主要了解了一点c,所以把bsdiff.cpp改成了bsdiff.c

    int main(int argc,char *argv[])
    {
    	int fd;
    
    	u_char *old,*_new;
    	off_t oldsize,newsize;
    	off_t *I,*V;
    	off_t scan,pos,len;
    	off_t lastscan,lastpos,lastoffset;
    	off_t oldscore,scsc;
    	off_t s,Sf,lenf,Sb,lenb;
    	off_t overlap,Ss,lens;
    	off_t i;
    	off_t dblen,eblen;
    	u_char *db,*eb;
    	u_char buf[8];
    	u_char header[32];
    	FILE * pf;
    
    	BZFILE * pfbz2;
    
    	int bz2err;
    
    
    	if(argc!=4) errx(1,"usage: %s oldfile newfile patchfile\n",argv[0]);
    
    	/* Allocate oldsize+1 bytes instead of oldsize bytes to ensure
    		that we never try to malloc(0) and get a NULL pointer */
    	//org:
    	//if(((fd=open(argv[1],O_RDONLY,0))<0) ||
    
    	//	((oldsize=lseek(fd,0,SEEK_END))==-1) ||
    
    	//	((old=malloc(oldsize+1))==NULL) ||
    
    	//	(lseek(fd,0,SEEK_SET)!=0) ||
    
    	//	(read(fd,old,oldsize)!=oldsize) ||
    
    	//	(close(fd)==-1)) err(1,"%s",argv[1]);
    	//new:
    	//Read in chunks, don't rely on read always returns full data!
    
    	if(((fd=open(argv[1],O_RDONLY|O_BINARY|O_NOINHERIT,0))<0) ||
    		((oldsize=lseek(fd,0,SEEK_END))==-1) ||
    		((old=(u_char*)malloc(oldsize+1))==NULL) ||
    		(lseek(fd,0,SEEK_SET)!=0))
    
    				err(1,"%s",argv[1]);
    
    	int r=oldsize;
    
    	while (r>0 && (i=read(fd,old+oldsize-r,r))>0) r-=i;
    
    	if (r>0 || close(fd)==-1) err(1,"%s",argv[1]);
    
    
    
    
    	if(((I=(off_t*)malloc((oldsize+1)*sizeof(off_t)))==NULL) ||
    		((V=(off_t*)malloc((oldsize+1)*sizeof(off_t)))==NULL)) err(1,NULL);
    
    	qsufsort(I,V,old,oldsize);
    
    	free(V);
    这里贴上前几行代码,只要调用这个函数就能得到差分包(.patch),
    而从if(argc!=4) errx(1,"usage: %s oldfile newfile patchfile\n",argv[0]);这行代码可以知道,argc这个参数必须等于4才可以正常执行,argv数组的第一个参数没有要求,从后面代码得到第二个参数为旧版apk的路径,第三个参数为新版apk的路径,第四个参数为生成的差分包的位置。

    当我们差分的时候只要调用这个main函数并传递相应参数就可以得到差分包了,所以我们修改入口函数的名字,目的是当我们调用的时候采取执行。我们只需要在bsdiff这个文件下实现我们声明的native方法并调用入口函数。然后生成动态库文件放入服务端项目下用来辅助生成差分包

    /* DO NOT EDIT THIS FILE - it is machine generated */
    #include <jni.h>
    /* Header for class com_dongnaoedu_bsdiff_BsDiff */
    
    #ifndef _Included_com_dongnaoedu_bsdiff_BsDiff
    #define _Included_com_dongnaoedu_bsdiff_BsDiff
    #ifdef __cplusplus
    extern "C" {
    #endif
    /*
     * Class:     com_dongnaoedu_bsdiff_BsDiff
     * Method:    diff
     * Signature: (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
     */
    JNIEXPORT void JNICALL Java_com_dongnaoedu_bsdiff_BsDiff_diff
      (JNIEnv *, jclass, jstring, jstring, jstring);
    
    #ifdef __cplusplus
    }
    #endif
    #endif

    public class BsDiff {
    
    	/**
    	 * 差分
    	 * 
    	 * @param oldfile
    	 * @param newfile
    	 * @param patchfile
    	 */
    	public native static void diff(String oldfile, String newfile, String patchfile);
    
    	static{
    		System.loadLibrary("bsdiff");
    	}
    }

    这里只贴出了部分代码。这样我们就可以得到差分包,在服务器就可以获取这个差分包,然后我们需要从客户端下载这个差分包并与旧版apk进行合并生成新版apk,这样就可以省去部分流量。

    合并的入口函数在bspatch文件中,下面为bspatch文件中native方法的实现,bspatch_main就是改了名字之后的入口函数

    //合并
    JNIEXPORT void JNICALL Java_com_dongnaoedu_appupdate_utils_BsPatch_patch
      (JNIEnv *env, jclass jcls, jstring oldfile_jstr, jstring newfile_jstr, jstring patchfile_jstr){
    	int argc = 4;
    	char* oldfile = (char*)(*env)->GetStringUTFChars(env,oldfile_jstr, NULL);
    	char* newfile = (char*)(*env)->GetStringUTFChars(env,newfile_jstr, NULL);
    	char* patchfile = (char*)(*env)->GetStringUTFChars(env,patchfile_jstr, NULL);
    
    	//参数(第一个参数无效)
    	char *argv[4];
    	argv[0] = "bspatch";
    	argv[1] = oldfile;
    	argv[2] = newfile;
    	argv[3] = patchfile;
    
    	bspatch_main(argc,argv);
    
    	(*env)->ReleaseStringUTFChars(env,oldfile_jstr, oldfile);
    	(*env)->ReleaseStringUTFChars(env,newfile_jstr, newfile);
    	(*env)->ReleaseStringUTFChars(env,patchfile_jstr, patchfile);
    
    }
    和差分类似,数组的第三个参数为生成的新版本apk路径

    查分和合并使用bsdiff主要依赖于bzip2这个框架


    然后我们就生成动态库文件(.so),因为安卓运行在linux平台所以生成的动态库文件和前面不同。自此我们生成了两个动态库文件,分别运行在服务端进行差分和运行在客户端进行合并。

    System.getProperty("os.name")能输出系统名字,能用于判断运行的操作系统

    System.load("路径")加载指定文件下的库文件

    差分算法:比较新旧文件,标识出翔通部分,压缩不同部分和额外部分(主要以来bzip2)生成patch文件

    合并算法:生成的patch文件和旧文件合成新文件

    当然这些算法内部实现非常复杂


    展开全文
  • 热更新技术demo
  • xLua 热更新

    2019-01-03 13:54:39
    xLua工具 腾讯手游热更新
  • bugly 热更新 热修复 热补丁。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
  • Xlua热更新

    2018-09-07 14:21:31
    做了一个简单的xlua热更新,可以让新手更好的去学习xlua,看不懂的话可以去翻我的博客里面有xlua之简单热更新,是根据这个工程的制作写的。
  • ILRuntime Unity热更新

    万次阅读 多人点赞 2019-05-28 19:35:44
    在新的项目中,使用到了ILRuntime的热更新方式,不同于XLua等,这种方式的热更新是由纯C#实现的,所以就不需要客户端懂Lua的代码。更详细的介绍可以看官方的文档。 官方的介绍及文档为:...

    在新的项目中,使用到了ILRuntime的热更新方式,不同于XLua等,这种方式的热更新是由纯C#实现的,所以就不需要客户端懂Lua的代码。更详细的介绍可以看官方的文档。

    官方的介绍及文档为:http://ourpalm.github.io/ILRuntime/public/v1/guide/index.html

    目前大致的理解为:将游戏分为两个部分,Unity和Hotfix。其中主要的游戏逻辑部分也就是可能需要热更的部分都写在Hotfix当中,然后将其导出为Hotfix.dll文件供Unity使用。游戏上线后若需要更新代码,只需要修改Hotfix中的代码,然后生成新的Hotfix.dll文件热更上去即可。

    接下来就用一个简单的Demo来实现这么一个过程。思路为,把原本UI逻辑的类放在Hotfix当中,由于没有继承MonoBehaviour,所以通过实现一个带有Start,Update等方法的接口,然后在Unity部分相应调用,来实现Hotfix中的生命周期。同时通过GameObject.Find的方法找到对应的组件,进行操作。

     

    创建Unity工程环境

    首先我们创建一个新的Unity工程,然后将ILRuntime需要的部分导入工程当中,即官方Demo中的Mono.Cecil.20,Mono.Cecil.Pdb,ILRuntime三个文件夹(删除ILRuntime/Adapters/Editor)

    勾选 player settings -> Other Settings - > Allow 'unsafe' Code选项

     

    创建Hotfix工程环境

    打开我们的VS,文件->新建->项目,创建一个C#类库,命名为Hotfix,如图

    然后在解决方案->引用处右键,添加引用,如图

    其中UnityEngine.dll,UnityEngine.UI.dll 和 UnityEngine.CoreModule.dll三个文件在Unity的安装目录下,Assembly-CSharp.dll在上面创建的Unity工程的Library/ScriptAssemblies目录下

    注:UnityEngine.CoreModule.dll是在Unity2017.2之后的版本才有,之前的版本可以不用添加,若在官方demo中UnityEngine.dll报找不到的错误的话,重新引用下正确目录下的dll文件即可。

     

    创建接口与适配器,并实现接口

    首先我们可以在Unity中创建一个简单的接口,用于处理Hotfix中的生命周期

    public interface IUI
    {
        void Start();
        void Update();
    }

    然后在Hotfix工程中,新建一个类Main.cs,实现IUI接口

    namespace Hotfix
    {
        //IUI为unity中的接口,所以要在unity中实现一个继承适配器
        public class MainUI:IUI
        {
            public void Start()
            {
    
            }
    
            public void Update()
            {
    
            }
        }
    }

    由于IUI是Unity的接口,而MainUI是Hotfix的类,这里有一个ILRuntime中跨域继承的概念,官方文档提到如果你想在热更DLL项目当中继承一个Unity主工程里的类,或者实现一个主工程里的接口,你需要在Unity主工程中实现一个继承适配器。

    所以我们在Unity中创建一个类 InterfaceIUIAdaptor.cs,实现继承适配器(根据文档给的代码进行修改)

    using ILRuntime.CLR.Method;
    using ILRuntime.Runtime.Enviorment;
    using ILRuntime.Runtime.Intepreter;
    using System;
    
    public class InterfaceIUIAdaptor : CrossBindingAdaptor
    {
        public override Type BaseCLRType {
            get {
                return typeof(IUI);//这是你想继承的那个类
            }
        }
    
        public override Type AdaptorType {
            get {
                return typeof(Adaptor);//这是实际的适配器类
            }
        }
    
        public override object CreateCLRInstance(ILRuntime.Runtime.Enviorment.AppDomain appdomain, ILTypeInstance instance)
        {
            return new Adaptor(appdomain, instance);//创建一个新的实例
        }
    
        //实际的适配器类需要继承你想继承的那个类,并且实现CrossBindingAdaptorType接口
        public class Adaptor : IUI, CrossBindingAdaptorType
        {
            ILTypeInstance instance;
            ILRuntime.Runtime.Enviorment.AppDomain appdomain;
    
            IMethod m_Start;
            bool m_StartGot;
    
            IMethod m_Update;
            bool m_UpdateGot;
    
            public Adaptor()
            {
    
            }
    
            public Adaptor(ILRuntime.Runtime.Enviorment.AppDomain appdomain, ILTypeInstance instance)
            {
                this.appdomain = appdomain;
                this.instance = instance;
            }
    
            public ILTypeInstance ILInstance { get { return instance; } }
    
            //你需要重写所有你希望在热更脚本里面重写的方法,并且将控制权转到脚本里去
            public void Start()
            {
                if (!m_StartGot)
                {
                    m_Start = instance.Type.GetMethod("Start", 0);
                    m_StartGot = true;
                }
                if (m_Start != null)
                {
                    appdomain.Invoke(m_Start, instance, null);//没有参数建议显式传递null为参数列表,否则会自动new object[0]导致GC Alloc
                }
            }
    
            public void Update()
            {
                if (!m_UpdateGot)
                {
                    m_Update = instance.Type.GetMethod("Update", 0);
                    m_UpdateGot = true;
                }
                if (m_Update != null)
                {
                    appdomain.Invoke(m_Update, instance, null);
                }
            }
        }
    }

     

    读取Hotfix.dll文件,并执行其内部操作

    首先我们将Hotfix工程中,解决方案右键生成,生成Hotfix.dll和Hotfix.pdb两个文件,将这两个文件拷贝到Unity的StreamingAssets目录下。

    然后我们创建一个新的类 Launch.cs,在这里面我们首先读取上面的两个Hotfix文件,然后进行一些ILRuntime的预设置,例如绑定继承适配器,注册委托等。最后我们要在里面找到Hotfix中实现IUI接口的类,因为这些类就是我们的UI逻辑类,然后在自己的Start,Update等生命周期方法中,调用Hotfix中IUI类对应的方法。代码如下:

    using ILRuntime.Runtime.Enviorment;
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using UnityEngine;
    
    public class Launch : MonoBehaviour
    {
        List<Action> DllUIUpdateList = new List<Action>();
    
        ILRuntime.Runtime.Enviorment.AppDomain appdomain;
        void Start()
        {
            StartCoroutine(LoadILRuntime());
        }
    
        IEnumerator LoadILRuntime()
        {
            //读取dll文件
            appdomain = new ILRuntime.Runtime.Enviorment.AppDomain();
    
            WWW www = new WWW(Application.streamingAssetsPath + "/Hotfix.dll");
    
            while (!www.isDone)
            {
                yield return null;
            }
            if (!string.IsNullOrEmpty(www.error))
            {
                Debug.LogError(www.error);
            }
            byte[] dll = www.bytes;
            www.Dispose();
    
            www = new WWW(Application.streamingAssetsPath + "/Hotfix.pdb");
    
            while (!www.isDone)
            {
                yield return null;
            }
            if (!string.IsNullOrEmpty(www.error))
            {
                Debug.LogError(www.error);
            }
            byte[] pdb = www.bytes;
            using (System.IO.MemoryStream fs = new MemoryStream(dll))
            {
                using (System.IO.MemoryStream p = new MemoryStream(pdb))
                {
                    appdomain.LoadAssembly(fs, p, new Mono.Cecil.Pdb.PdbReaderProvider());
                }
            }
    
            OnILRuntimeInit();
    
            OnILRuntimeInitialized();
        }
    
        void Update()
        {
            if (DllUIUpdateList.Count > 0)
            {
                foreach(var update in DllUIUpdateList)
                {
                    update();
                }
            }
        }
    
        void OnILRuntimeInit()
        {
            //跨域继承绑定适配器
            appdomain.RegisterCrossBindingAdaptor(new InterfaceIUIAdaptor());
            //Button点击事件的委托注册
            appdomain.DelegateManager.RegisterDelegateConvertor<UnityEngine.Events.UnityAction>((act) =>
            {
                return new UnityEngine.Events.UnityAction(() =>
                {
                    ((Action)act)();
                });
            });
        }
    
        void OnILRuntimeInitialized()
        {        
            //获取Hotfix.dll内部定义的类
            List<Type> allTypes = new List<Type>();
            var values = appdomain.LoadedTypes.Values.ToList();
            foreach (var v in values)
            {
                allTypes.Add(v.ReflectionType);
            }
            //去重
            allTypes = allTypes.Distinct().ToList();
    
            DllUIUpdateList.Clear();
            foreach (var v in allTypes)
            {
                //找到实现IUI接口的类 Adaptor 前面写的适配器IUI的类
                if (v.IsClass && v.GetInterface("Adaptor") != null)
                {
                    //生成实例
                    var gs = appdomain.Instantiate<IUI>(v.FullName);
    
                    //调用接口方法
                    gs.Start();
                    DllUIUpdateList.Add(gs.Update);
                }
            }
        }
    }
    

    注:代码中有一个委托注册的功能,是因为在Hotfix中调用UGUI的Button的onCkick,需要生成委托转换器,否则会报错

     

    搭建UI及实现UI逻辑

    Demo中,简单的在场景中创建一个简单的Button和Text,然后将Launch.cs挂到Canvas上即可。然后在我们之前Hotfix中创建的MainUI.cs中添加我们的UI逻辑:

    using UnityEngine;
    using UnityEngine.UI;
    
    namespace Hotfix
    {
        //IUI为unity中的接口,所以要在unity中实现一个继承适配器
        public class MainUI:IUI
        {
            Button m_btn;
            Text m_text;
            int count = 0;
            bool isClick = false;
    
            public void Start()
            {
                m_btn = GameObject.Find("Canvas/Button").GetComponent<Button>();
                m_text = GameObject.Find("Canvas/Text").GetComponent<Text>();
                m_text.text = "MainUI Start";
    
                //点击事件的委托需要在unity中实现委托转换器
                m_btn.onClick.AddListener(BtnClick);
            }
    
            public void Update()
            {
                if (isClick)
                {
                    if (count % 20 == 0)
                    {
                        m_text.text = "MainUI Update" + count / 20;
                    }
                    count++;
                }
            }
    
            void BtnClick()
            {
                isClick = true;
            }
        }
    }
    

    然后重新生成下dll文件,将原来Unity StreamingAssets下的文件替换掉即可(以后修改逻辑亦是如此,达到热更的效果)。

     

    运行效果如下:

     

    更详细的内容请看后面的文章~~~~

    展开全文
  • 安卓热更新bugly

    2017-06-21 17:25:30
    腾讯bugly热更新demo,1.3.1版本,腾讯bugly热更新demo腾讯bugly热更新demo腾讯bugly热更新demo腾讯bugly热更新demo腾讯bugly热更新demo腾讯bugly热更新demo
  • 逍遥西游热更新工具_逍遥热更新_逍遥西游.zip
  • XLua热更新测试

    2019-03-03 13:49:29
    Xlua热更新测试!!
  • cocos js 热更新

    2017-05-12 16:42:33
    cocos js 热更新
  • 逍遥西游热更新工具_逍遥热更新_逍遥西游_源码.zip
  • 热更新框架设计系列课程总体介绍: 本系列课程由《热更新框架设计之Xlua基础》、《热更新框架设计之热更流程与热补丁技术》、《热更新框架设计之游戏客户端框架》三套课程组成。 三套课程是一个不可分割有机的整体,...
  • ILRuntime热更新DLl

    2018-12-21 15:08:52
    ILRuntime 实现热更新 ILRuntime 实现热更新 ILRuntime 实现热更新 这个积分都是谁改的呀 这个积分都是谁改的呀这个积分都是谁改的呀这个积分都是谁改的呀这个积分都是谁改的呀这个积分都是谁改的呀这个积分都是谁...
  • Android热更新方案Robust——美团热更新(热修复)使用介绍

    Android热更新方案Robust

    http://tech.meituan.com/android_robust.html

    Android热更新方案Robust开源,新增自动化补丁工具

    http://tech.meituan.com/android_autopatch.html

    美团 Robust 的 github demo 地址

    https://github.com/Meituan-Dianping/Robust

    Robust 的原理

    Robust插件对产品的每个函数在编译打包阶段都插入了一段代码。当我们需要对已上线的app进行bug代码修复时,这时如果存在patch.jar,就会调用patch.jar中作为修复bug的代码而跳过原先的代码片段,由此达到修复的目的;而对产品的每个函数进行插入一段代码的工作是由插件 apply plugin : 'robust' 来完成的;对我们需要修复代码操作而实现修复功能的 patch.jar 是由插件 apply plugin : 'auto-patch-plugin' 生成的。

    大致应用流程

     
    /**
    * 1.集成了 Robust 后,生成 apk。保存期间的混淆
    *   文件 mapping.txt + Robust 生成记录文件 methodMap.robust ;
    * 2.使用注解 @Modify 和 @Add 标注需要修复和用*于修复的方法 ;
    * 3.开启补丁插件(apply plugin:'auto-patch-plugin'),
    *   执行生成 apk 命令,获得补丁包patch.jar ;
    * 4.通过推送或者接口的形式,通知 app 有补丁,需要修复;
    * 5.加载补丁文件不需要重新启动应用(即时生效)。
    */

    制作补丁前准备一

    配置方面有两个地方:1.是在project级别的配置;2.是在module级别的配置;3.robust.xml复制到工程目录下;把如下图所示
     

     

    project级别的配置

     

    module级别的配置

     

    robust.xml配置

    <img data-cke-saved-src="https://img-blog.csdn.net/20170518144934741?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvanVuaHVhaG91c2U=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" src="https://img-blog.csdn.net/20170518144934741?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvanVuaHVhaG91c2U=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="" />
    
    
     
     

    制作补丁前准备二

    首先了解一下这个测试demo的逻辑;打开APP进入到MainActivity,在MainActivity中主要有两个按钮:点击进入HotFixActivity页面和点击执行修复功能;在HotFixActivity页面只有一个TextView显示文字,当可以进行代码修复功能时,在MainActivity点击修复按钮之后再点击进入HotFixActivity页面,你会发现TextView显示的内容变成了我们要修复的内容。
    看一下代码:
    package com.draem.application20170516;
    
    import android.content.Intent;
    import android.os.Bundle;
    import android.os.Environment;
    import android.support.v7.app.AppCompatActivity;
    import android.util.Log;
    import android.view.View;
    import android.widget.Button;
    import android.widget.TextView;
    import android.widget.Toast;
    
    import com.meituan.robust.Patch;
    import com.meituan.robust.PatchExecutor;
    import com.meituan.robust.RobustCallBack;
    
    import java.io.File;
    
    import butterknife.BindView;
    import butterknife.ButterKnife;
    import butterknife.OnClick;
    
    public class MainActivity extends AppCompatActivity {
    
        @BindView(R.id.btn_go)
        Button btnGo;
        @BindView(R.id.btn_hotfix)
        Button btnHotfix;
        @BindView(R.id.tvShow2)
        TextView tvShow2;
        @BindView(R.id.tvShow3)
        TextView tvShow3;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    //        SystemClock.sleep(2*1000);
            setContentView(R.layout.activity_main);
            ButterKnife.bind(this);
            File file = getDir();//创建文件夹  /mnt/sdcard/HotFix
        }
    
    
        @OnClick({R.id.btn_go, R.id.btn_hotfix})
        public void onClick(View view) {
            switch (view.getId()) {
                case R.id.btn_go:
                    Intent it = new Intent(MainActivity.this, HotFixActivity.class);
                    startActivity(it);
                    break;
                case R.id.btn_hotfix://执行修复
                    new PatchExecutor(getApplicationContext(),
                            new PatchManipulateImp(),
                            new RobustCallBack() {
                                @Override
                                public void onPatchListFetched(boolean result, boolean isNet) {
                                    Log.e("error-hot", "打印 onPatchListFetched:" + "isNet=" + isNet );
                                }
    
                                @Override
                                public void onPatchFetched(boolean result, boolean isNet, Patch patch) {
                                    Log.e("error-hot", "打印 onPatchFetched:" + "result=" + result+"isNet="+isNet + "--->" + "patch=" + patch);
                                }
    
                                @Override
                                public void onPatchApplied(boolean result, Patch patch) {
                                    Log.e("error-hot", "打印 onPatchApplied:" + "result=" + result + "--->" + "patch=" + patch);
                                }
    
                                @Override
                                public void logNotify(String log, String where) {
                                    Log.e("error-hot", "打印 logNotify:" + "log=" + log + "--->" + "where=" + where);
                                }
    
                                @Override
                                public void exceptionNotify(Throwable throwable, String where) {
                                    Log.e("error-hot", "打印 exceptionNotify:" + "throwable=" + throwable.toString() + "--->" + "where=" + where);
                                }
                            }).start();
                    break;
            }
        }
    
        int count = 0;
    
        private File getDir() {
            StringBuilder path = new StringBuilder();
            if (isSDAvailable()) {
                path.append(Environment.getExternalStorageDirectory()
                        .getPath());
                path.append(File.separator);// '/'
                path.append("HotFix");// /mnt/sdcard/HotFix
              
                Log.e("error-hotfix", "如果SD卡可用就在SD卡创建");
                tvShow3.setText("SD卡可用就在sd创建");
            } else {
                //如果SD卡不可用就在内存创建
                File filesDir = getApplication().getCacheDir();    //  cache  getFileDir file
                path.append(filesDir.getAbsolutePath());
               
                tvShow3.setText("SD卡不可用就在内存创建");
                Log.e("error-hotfix", "SD卡不可用就在内存创建");
            }
            File file = new File(path.toString());
            if (!file.exists() || !file.isDirectory()) {
                file.mkdirs();// 创建文件夹
                count += 10;
            }
            Toast.makeText(this, "file=" + file, Toast.LENGTH_SHORT).show();
            Log.e("error-hotfix", count+" ==>file地址=" + file.toString() + "-->" + file.getAbsolutePath());
            tvShow2.setText(file.toString()+ "\n" + file.getAbsolutePath());
            return file;
    
        }
    
        private boolean isSDAvailable() {
            if (Environment.getExternalStorageState().equals(
                    Environment.MEDIA_MOUNTED)) {
                Toast.makeText(this, "sd 有效", Toast.LENGTH_SHORT).show();
                return true;
            } else {
                return false;
            }
        }
    }
    
     
    package com.draem.application20170516;
    
    import android.os.Bundle;
    import android.support.v7.app.AppCompatActivity;
    import android.widget.TextView;
    
    import butterknife.BindView;
    import butterknife.ButterKnife;
    
    public class HotFixActivity extends AppCompatActivity {
    
        @BindView(R.id.tvShow)
        TextView tvShow;
    
        @Override
    //    @Modify
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_hot_fix);
            ButterKnife.bind(this);
    
            tvShow.setText(getText());//加载错误的代码
    //        tvShow.setText(getInfo());//加载正确的代码
        }
    
    
        private String getText(){
            return "Hot-Fix, this just an error";
        }
    
    
    //    @Add
    //    public String getInfo(){
    //        return "Hot-Fix, 已经对含有error的代码进行了修改!";
    //    }
    }
    
     
    package com.draem.application20170516;
    
    import android.content.Context;
    import android.os.Environment;
    import android.util.Log;
    
    import com.meituan.robust.Patch;
    import com.meituan.robust.PatchManipulate;
    
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * Created by yuanjunhua on 2017/5/17.
     */
    
    public class PatchManipulateImp extends PatchManipulate {
        @Override
        protected List<Patch> fetchPatchList(Context context) {
    
            Patch patch = new Patch();
            patch.setName("test patch");
    
            StringBuilder path = new StringBuilder();
            path.append(Environment.getExternalStorageDirectory()
                    .getPath());
            path.append(File.separator);// '/'
            path.append("HotFix");// /mnt/sdcard/HotFix
            path.append(File.separator);
            path.append("patch");// /mnt/sdcard/HotFix/patch
    
            patch.setLocalPath(path.toString());
            Log.e("error-hotfix", "PatchManipulateImp 地址="+path);
    
            patch.setPatchesInfoImplClassFullName("com.draem.application20170516.PatchesInfoImpl");
            List<Patch> patchList = new ArrayList<>();
            patchList.add(patch);
            return patchList;
        }
    
    
        @Override
        protected boolean verifyPatch(Context context, Patch patch) {
            //do your verification, put the real patch to patch
            //放到app的私有目录
            StringBuilder path = new StringBuilder();
            path.append(context.getCacheDir());
            path.append(File.separator);// '/'
            path.append("HotFix");// /mnt/sdcard/HotFix
            path.append(File.separator);
            path.append("patch");// /mnt/sdcard/HotFix/patch
            patch.setTempPath(path.toString());
            //in the sample we just copy the file
            try {
                Log.e("error-hotfix", "patch.getLocalPath="+patch.getLocalPath()+"--->patch.getTempPath="+patch.getTempPath());
                copy(patch.getLocalPath(), patch.getTempPath());
            }catch (Exception e){
                e.printStackTrace();
                throw new RuntimeException("copy source patch to local patch error, no patch execute in path "+patch.getTempPath());
            }
    
            return true;
        }
    
        @Override
        protected boolean ensurePatchExist(Patch patch) {
            return true;
        }
    
    
    
        public void copy(String srcPath,String dstPath) throws IOException {
            File src = new File(srcPath);
            if(!src.exists()){
                try {
                    Log.e("error-hitfix", "资源不存在哦  srcPath="+srcPath);
                    Log.e("error-hitfix", "资源不存在哦  srcPath="+src.toString());
                    Log.e("error-hitfix", "资源不存在哦  srcPath="+src.length());
                } catch (Exception e) {
                    e.printStackTrace();
                }
                
                throw new RuntimeException("source patch does not exist ");
            }
            File dst=new File(dstPath);
            if(!dst.getParentFile().exists()){
                dst.getParentFile().mkdirs();
            }
            InputStream in = new FileInputStream(src);
            try {
                OutputStream out = new FileOutputStream(dst);
                try {
                    // Transfer bytes from in to out
                    byte[] buf = new byte[1024];
                    int len;
                    while ((len = in.read(buf)) > 0) {
                        out.write(buf, 0, len);
                    }
                } finally {
                    out.close();
                }
            } finally {
                in.close();
            }
        }
    }
    

    然后就保持上面的代码 + module级别gradle中保持设置
    //在生成 apk 的时候使用 apply plugin:’robust’
    apply plugin: 'robust'
    
    
    开启打补丁那个,关闭生成apk那个
    //apply plugin: 'auto-patch-plugin'
    这样准备二阶段就要告一段落了,然后我们执行终端命令生成apk:
     gradlew clean assembleRelease --stacktrace --no-daemon
     
    打包成功之后我们可以看到(并按照图示操作):
     
     
     
     
    操作完之后,将上面的源代码该解注释的解注释该加注解的加上注解:
     
     
     
     
    最后就是执行终端代码生成patch.jar:(打包apk会失败,没关系我们是为了生产jar)
    gradlew clean assembleRelease --stacktrace --no-daemon
     
    到此为止,制作补丁前的准备已经完成;

    开始修复工作

    先要执行终端,把patch.jar文件copy到手机SD目录:/sdcard/HotFix/patch.jar
    adb push C:\studio_workspace\Application20170516\app\build\outputs\robust\patch.jar /sdcard/HotFix/patch.jar
    成功之后,我们只需要点击一下修复按钮然后跳转到HotFixActivity页面就好了,这时候会发现textview内容变化了!
     
     
    还有就是在使用终端命令的时候可能会遇到“不是内部命令的错误”,解决如下:原Android Sdudio 2.2和AndroidSdudio 2.3不一样 ,Android Sdudio 2.3 的 adb.exe是放在android-sdk\platform-tools目录下面的,而2.2是放在tools目录下面的,所以需要把环境配置path的路径指到platform-tools下面。然后终端输入adb,能够显示相关的信息。当然如果你不想要使用终端命令来执行上面的操作完全可以使用“Build/Generate/Signed APK...”来代替上面的终端命令;
    注意:1,测试安装到手机上的apk必须是签过名了的版本;2,配置SD卡的读写权限;
     
     
    展开全文
  • 热更新原理

    千次阅读 2020-02-11 21:19:42
    到现在猜开始了解热更新原理 热更新方案有三种 底层替换方案 类加载方案 Instant Run 本篇文章主要是 类加载 和 Instant Run 两种方式进行的热更新 类加载方案 需要先了解Android 类加载,可以看这篇 ...
    对于热更新的问题就是了解两个点的问题:
    • 如何加载补丁包,也就是如何加载dex 文件的过程(dex是补丁包,更改的文件都在补丁包中)
    • 修复后的类如何替换掉旧的类

    通过这篇文章给大家介绍下我理解的热更新的逻辑,需要先了解一些关系到的知识

    热更新方案有三种

    • 底层替换方案
    • 类加载方案
    • Instant Run

    本篇文章主要是 类加载 和 Instant Run 两种方式进行的热更新

    类加载方案

    需要先了解Android 类加载,可以看这篇 https://blog.csdn.net/hjiangshujing/article/details/104249956
    此处用到的是Android 中的 DexClassLoader 类加载器
    以下做简单的介绍

    Android 类加载

    • BootClassLoader
    • DexClassLoader – optimizedDirect
    • PathClassLoader – 没有 optimizedDirect,默认optimizedDirect 的值为/data/dalvik-cache ,PathClassLoader 无法定义解压的dex文件存储路径,因此它通常用来加载已经安装的apk的dex文件(安装的apk的dex文件会存储在/data/dalvik-cache中)

    类加载方案原理

    首先要了解类加载过程

    class 加载过程从ClassLoader 的loadClass方法开始
    ClassLoader 的加载方法为loadClass

    可以通过 Android 中的类加载 文章中的最后的图来说

    在这里插入图片描述

    DexPathList.java 中的findClass 方法(核心环节)
    public Class<?> findClass(String name, List<Throwable> suppressed) {
            for (Element element : dexElements) {//1
                Class<?> clazz = element.findClass(name, definingContext, suppressed);//2
                if (clazz != null) {
                    return clazz;
                }
            }
    
            if (dexElementsSuppressedExceptions != null) {
                suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
            }
            return null;
        }
    
    
    
    • 遍历dexElements
    • 调用Element 的findClass 方法
    • 每个Element 中内部封装了DexFile ,用于加载dex,如果DexFile 不为null,就调用DexFile 的loadClassBinaryName 方法
    • loadClassBinaryName 方法中调用了defineClass,调用了defineClassNative方法来加载dex相关文件
    那么类加载方案的原理就出来,如下:
    • ClassLoader 的加载过程,其中一个环节就是调用DexPathList的findClass 方法
    • Element 内部封装了DexFile ,DexFile用于加载dex文件,因此每个dex文件对应一个Element ,多个Element 组成了有序的Element 数组dexElements。
    • 当要查找类时,会遍历dexElements (相当于遍历dex 文件数组),并调用DexFile的loadClassBinaryName 方法查找类
    • 如果在Element 中(dex 文件)找到了该类就返回
    • 如果没有找到就接着在下一个Elment中进行查找
    • 将Bug类Key.class进行修改,再将Key.class打包成包含dex的补丁包Patch.jar
    • 通过反射修改类加载中的dexElements,将补丁包放在Elment 数组dexElements 的第一个元素
    • 在类加载的时候先加载到的是Patch.dex中的修复后的Key.class(根据类加载的双亲委托机制,如果此类加载过就不会再加载),这样就做到替换之前存在Bug的Key.class
    代码实现
    	/**
         * 修复方法
         */
        private static void dexFix(ClassLoader classLoader, File optimizedDirectory, File... dexFiles) throws NoSuchFieldException, NoSuchMethodException, IllegalAccessError, IllegalAccessException {
    
            StringBuilder sb = new StringBuilder();
            for (File file : dexFiles) {
                sb.append(file.getAbsolutePath()).append(":");
            }
            //1.使用DexClassLoader 加载所有外部dex文件
            DexClassLoader dexClassLoader = new DexClassLoader(sb.deleteCharAt(sb.length() - 1).toString(),
                    optimizedDirectory.getAbsolutePath(), null, ClassLoader.getSystemClassLoader());
            //2.获取系统 dexElements
            Object pathElements = getClassLoaderElements(classLoader);
            //3.获取外部dex 的dexElements  
            Object dexElements = getClassLoaderElements(dexClassLoader);
    
            Field pathListField = ReflectUtil.findFiled(classLoader.getClass(), "pathList");
            Object pathList = pathListField.get(classLoader);
            Field dexElementsFiled = ReflectUtil.findFiled(pathList.getClass(), "dexElements");
            //4.将系统与外部dexElements合并
            Object arrayAppend = arrayAppend(dexElements, pathElements);
            //5.修改系统 dexElements
            dexElementsFiled.set(pathList, arrayAppend);
        }
    
        /**
         * 将所有Array类型的数据按顺序合并成一个Array数据
         */
        public static Object arrayAppend(Object... elements) {
            int length = 0;
            for (Object element : elements) {
                length += Array.getLength(element);
            }
            Object array = Array.newInstance(elements[0].getClass().getComponentType(), length);
    
            for (int i = 0, j = 0, k = 0, elementLength = Array.getLength(elements[k]); i < length; i++) {
                Array.set(array, i, Array.get(elements[k], i - j));
                if (i - j == elementLength - 1) {
                    j += elementLength;
                    k++;
                    if (k < elements.length) {
                        elementLength = Array.getLength(elements[k]);
                    }
                }
            }
            return array;
        }
    
        /**
         * 获取ClassLoader 中 dexElements 成员变量
         */
        private static Object getClassLoaderElements(ClassLoader classLoader) throws NoSuchMethodException, IllegalAccessException {
            Field pathListField = ReflectUtil.findFiled(classLoader.getClass(), "pathList");
            Object pathList = pathListField.get(classLoader);
            Field dexElementsFiled = ReflectUtil.findFiled(pathList.getClass(), "dexElements");
            return dexElementsFiled.get(pathList);
        }
    
    缺点:
    • 类加载方案需要重启App 后ClassLoader重新加载新类
    • 因为类无法被卸载,想要重新加载新的类就需要重启App
    • 因此采用类加载方案的热修复框架不能即时生效
    • 如:QQ空间的超级补丁 ,Nuwa ,微信的Tinker ,饿了么Amigo 都是使用通过操作Element 数组实现的

    Instant Run (立即更新)

    什么是Instant Run

    Instant Run 是Android Studio 2.0 以后新增的一个运行机制,能够显著减少开发人员第二次以及以后的构建和部署时间

    使用Instant Run 前后编译部署应用程序流程的区别
    • 之前:代码更改 -> 构建和部署app -> app销毁->app 重启 -> activity重启->更改的代码运行
    • 之后:代码更改->构建改变部分->部署更改部分->(Cold Swap<App 重启>, Hot Swap ,Warm Swap<Activity 重启>)->更改的代码运行
    • 传统的编译部署需要重新安装App和重启App,会比较耗时
      Instant Run 的构建和部署都是基于更改的部分
    Instant Run 原理

    Instant Run原理就是:Instant Run 在第一次构建 APK 时,使用 ASM 在每一个方法中注入了判断代码

    ASM :是一个 Java 字节码操控框架,它能够动态生成类或者增强现有类的功能。 ASM 可以直接产生 clsss文件,也可以在类被加载到虚拟机之前动态改变类的行为。

    通过Instant Run进行热更新的步骤
    • 通过一些工具在类被加载到虚拟机之前动态的在类中每个方法上注入判断代码
    • 注入的代码内容大致为:是否需要加载补丁
    • 如果需要加载补丁,使用DexClassLoader类加载器加载指定地址的修复包,然后用DexClassLoader 的 loadClass方法加载补丁类,再通过反射使用这个被加载的补丁类
    怎么使用Instant Run进行动态更新的 (以美团热更新Robust 为例简单列举下)
    实现原理可以围绕着两点
    1. 代码注入
    2. 替换老的逻辑(加载补丁包)

    Robust 中的几个重要类介绍:

    PatchesInfo 接口 (用于保存修复类和旧类的类信息)
    • 补丁包说明类,可以获取所有补丁对象,每个对象包含被修复类名及该类对应的补丁类
    • 每个修复包中必须有一个类需要实现这个,用于存放此修复包中所有需要修复的类信息
    • 通过这个接口获取到指定修复的类和旧类信息
    PatchedClassInfo
    • private String patchedClassName;//需要修复的类名称
    • private String patchClassName;//patch中的补丁类名称
    • 存放已修复类和旧类的类名,用于后续的动态加载
    ChangeQuickRedirect 接口
    • 每个补丁类必须实现ChangeQuickRedirect接口,内部有两个方法
    • isSupport 判断当前方法是否执行补丁逻辑
    • accessDispatch具体修复逻辑
    PatchProxy

    此类是对ChangeQuickRedirect修复类做了一层包装,最终还是调用的ChangeQuickRedirect实现类中的方法

    实现 ChangeQuickRedirect 的类

    每个补丁类必须实现的接口(ChangeQuickRedirect),内部有两个方法

    两个方法
    • isSupport 判断当前方法是否执行补丁逻辑
    • accessDispatch具体修复逻辑
    方法参数
    • 第一个参数是方法的签名,这个签名的格式很简单:方法所属类全称:方法名:方法是否为static类型,注意中间使用冒号进行连接的。
    • 第二个参数是方法的参数信息,而对于这个参数后面分析动态插入代码逻辑的时候会发现操作非常麻烦,才把这个参数弄到手的。
    Robust 的实现为:
    • 可以看到Robust为每个class增加了一个类型为ChangeQuickRedirect的静态成员,而在每个方法前都插入了使用changeQuickRedirect相关的逻辑,
    • 当changeQuickRedirect不为null时,可能会执行到accessDispatch从而替换掉之前老的逻辑
    代码注入

    原方法

    public long getIndex() {  
        return 100;  
    } 
    

    将方法中注入判断代码后

    public static ChangeQuickRedirect changeQuickRedirect;  
        public long getIndex() {  
            if(changeQuickRedirect != null) {  
                //PatchProxy中封装了获取当前className和methodName的逻辑,并在其内部最终调用了changeQuickRedirect的对应函数  
                if(PatchProxy.isSupport(new Object[0], this, changeQuickRedirect, false)) {  
                    return ((Long)PatchProxy.accessDispatch(new Object[0], this, changeQuickRedirect, false)).longValue();  
                }  
            }  
        return 100L;  
    }
    
    加载补丁包
    重点方法在PatchExecutor 中的patch 方法
    public class PatchExecutor extends Thread {
        protected Context context;
        protected PatchManipulate patchManipulate;
        protected RobustCallBack robustCallBack;
    
        public PatchExecutor(Context context, PatchManipulate patchManipulate, RobustCallBack robustCallBack) {
            this.context = context.getApplicationContext();
            this.patchManipulate = patchManipulate;
            this.robustCallBack = robustCallBack;
        }
    
        @Override
        public void run() {
            try {
                //拉取补丁列表
                List<Patch> patches = fetchPatchList();
                //应用补丁列表
                applyPatchList(patches);
            } catch (Throwable t) {
                Log.e("robust", "PatchExecutor run", t);
                robustCallBack.exceptionNotify(t, "class:PatchExecutor,method:run,line:36");
            }
        }
    
        /**
         * 拉取补丁列表
         */
        protected List<Patch> fetchPatchList() {
            return patchManipulate.fetchPatchList(context);
        }
    
        /**
         * 应用补丁列表
         */
        protected void applyPatchList(List<Patch> patches) {
            if (null == patches || patches.isEmpty()) {
                return;
            }
            Log.d("robust", " patchManipulate list size is " + patches.size());
            for (Patch p : patches) {
                if (p.isAppliedSuccess()) {
                    Log.d("robust", "p.isAppliedSuccess() skip " + p.getLocalPath());
                    continue;
                }
                if (patchManipulate.ensurePatchExist(p)) {
                    boolean currentPatchResult = false;
                    try {
                        currentPatchResult = patch(context, p);
                    } catch (Throwable t) {
                        robustCallBack.exceptionNotify(t, "class:PatchExecutor method:applyPatchList line:69");
                    }
                    if (currentPatchResult) {
                        //设置patch 状态为成功
                        p.setAppliedSuccess(true);
                        //统计PATCH成功率 PATCH成功
                        robustCallBack.onPatchApplied(true, p);
    
                    } else {
                        //统计PATCH成功率 PATCH失败
                        robustCallBack.onPatchApplied(false, p);
                    }
    
                    Log.d("robust", "patch LocalPath:" + p.getLocalPath() + ",apply result " + currentPatchResult);
    
                }
            }
        }
    
        protected boolean patch(Context context, Patch patch) {
            if (!patchManipulate.verifyPatch(context, patch)) {
                robustCallBack.logNotify("verifyPatch failure, patch info:" + "id = " + patch.getName() + ",md5 = " + patch.getMd5(), "class:PatchExecutor method:patch line:107");
                return false;
            }
    
            ClassLoader classLoader = null;
    
            try {
                File dexOutputDir = getPatchCacheDirPath(context, patch.getName() + patch.getMd5());
                classLoader = new DexClassLoader(patch.getTempPath(), dexOutputDir.getAbsolutePath(),
                        null, PatchExecutor.class.getClassLoader());
            } catch (Throwable throwable) {
                throwable.printStackTrace();
            }
            if (null == classLoader) {
                return false;
            }
    
            Class patchClass, sourceClass;
    
            Class patchesInfoClass;
            PatchesInfo patchesInfo = null;
            try {
                Log.d("robust", "patch patch_info_name:" + patch.getPatchesInfoImplClassFullName());
                patchesInfoClass = classLoader.loadClass(patch.getPatchesInfoImplClassFullName());
                patchesInfo = (PatchesInfo) patchesInfoClass.newInstance();
            } catch (Throwable t) {
                Log.e("robust", "patch failed 188 ", t);
            }
    
            if (patchesInfo == null) {
                robustCallBack.logNotify("patchesInfo is null, patch info:" + "id = " + patch.getName() + ",md5 = " + patch.getMd5(), "class:PatchExecutor method:patch line:114");
                return false;
            }
    
            //classes need to patch 1.获取补丁包中所有待修复类信息
            List<PatchedClassInfo> patchedClasses = patchesInfo.getPatchedClassesInfo();
            if (null == patchedClasses || patchedClasses.isEmpty()) {
    //            robustCallBack.logNotify("patchedClasses is null or empty, patch info:" + "id = " + patch.getName() + ",md5 = " + patch.getMd5(), "class:PatchExecutor method:patch line:122");
                //手写的补丁有时候会返回一个空list
                return true;
            }
    
            boolean isClassNotFoundException = false;
            for (PatchedClassInfo patchedClassInfo : patchedClasses) {
                String patchedClassName = patchedClassInfo.patchedClassName;
                String patchClassName = patchedClassInfo.patchClassName;
                if (TextUtils.isEmpty(patchedClassName) || TextUtils.isEmpty(patchClassName)) {
                    robustCallBack.logNotify("patchedClasses or patchClassName is empty, patch info:" + "id = " + patch.getName() + ",md5 = " + patch.getMd5(), "class:PatchExecutor method:patch line:131");
                    continue;
                }
                Log.d("robust", "current path:" + patchedClassName);
                try {
                    try {
                        //2.加载要被修复的类
                        sourceClass = classLoader.loadClass(patchedClassName.trim());
                    } catch (ClassNotFoundException e) {
                        isClassNotFoundException = true;
    //                    robustCallBack.exceptionNotify(e, "class:PatchExecutor method:patch line:258");
                        continue;
                    }
    
                    Field[] fields = sourceClass.getDeclaredFields();
                    Log.d("robust", "oldClass :" + sourceClass + "     fields " + fields.length);
                    Field changeQuickRedirectField = null;
                    for (Field field : fields) {
                        if (TextUtils.equals(field.getType().getCanonicalName(), ChangeQuickRedirect.class.getCanonicalName()) && TextUtils.equals(field.getDeclaringClass().getCanonicalName(), sourceClass.getCanonicalName())) {
                            //3.找到要被修复类的注入的静态变量
                            changeQuickRedirectField = field;
                            break;
                        }
                    }
                    if (changeQuickRedirectField == null) {
                        robustCallBack.logNotify("changeQuickRedirectField  is null, patch info:" + "id = " + patch.getName() + ",md5 = " + patch.getMd5(), "class:PatchExecutor method:patch line:147");
                        Log.d("robust", "current path:" + patchedClassName + " something wrong !! can  not find:ChangeQuickRedirect in" + patchClassName);
                        continue;
                    }
                    Log.d("robust", "current path:" + patchedClassName + " find:ChangeQuickRedirect " + patchClassName);
                    try {
                        //4.加载要修复类对应的patch中的补丁类对象
                        patchClass = classLoader.loadClass(patchClassName);
                        Object patchObject = patchClass.newInstance();
                        changeQuickRedirectField.setAccessible(true);
                        changeQuickRedirectField.set(null, patchObject);//5.将静态变量的值设置为补丁包中的补丁类
                        //patchObject为补丁类对象
                        Log.d("robust", "changeQuickRedirectField set success " + patchClassName);
                    } catch (Throwable t) {
                        Log.e("robust", "patch failed! ");
                        robustCallBack.exceptionNotify(t, "class:PatchExecutor method:patch line:163");
                    }
                } catch (Throwable t) {
                    Log.e("robust", "patch failed! ");
    //                robustCallBack.exceptionNotify(t, "class:PatchExecutor method:patch line:169");
                }
            }
            Log.d("robust", "patch finished ");
            if (isClassNotFoundException) {
                return false;
            }
            return true;
        }
    
        private static final String ROBUST_PATCH_CACHE_DIR = "patch_cache";
    
        /*
         * @param c
         * @return 返回缓存补丁路径,一般是内部存储,补丁目录
         */
        private static File getPatchCacheDirPath(Context c, String key) {
            File patchTempDir = c.getDir(ROBUST_PATCH_CACHE_DIR + key, Context.MODE_PRIVATE);
            if (!patchTempDir.exists()) {
                patchTempDir.mkdir();
            }
    
            return patchTempDir;
        }
    
    }
    
    
    重点代码
    • 注解0处: 用DexClassLoader加载补丁包中的 PatchesInfoImpl 类,通过反射拿到这个 PatchesInfoImpl 实例对象。
    • 注释1处:通过PatchesInfoImpl获取补丁包中所有待修复类信息
    • 注释2处:用DexClassLoader加载要被修复的类
    • 注释3处:找到要被修复类的注入的静态变量
    • 注释4处:用DexClassLoader加载要修复类对应的patch中的补丁类对象
    • 注释5处:将静态变量的值设置为补丁包中的补丁类,代码中patchObject为补丁类对象
    加载补丁包原理
    • 使用DexClassLoader加载指定地址的修复包
    • 然后用DexClassLoader 的 loadClass方法加载补丁类,new出新对象,
    • 在用反射把这新的补丁对象设置到旧类的changeQuickRedirect静态变量中即可。
    展开全文
  • Tinker微信热更新

    2017-02-08 12:24:07
    Tinker热更新
  • 热更新,每个程序员一听就明白,但是它语出何处,究竟表达了什么含义,到底代表了什么,对技术有什么要求,对经验相对较少的程序员来说可能就有一层神秘面纱了。” 热更新,是对hot update 或者
  • 主要介绍了详解webpack 热更新优化,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
  • ToLua热更新原理.zip

    2020-06-23 11:10:01
    ToLua热更新原理,基于LuaFramework_UGUI的实现,便于学习游戏的热更新方法 https://github.com/getker/LuaFramework_UGUI
  • unity Addressables 自己的热更新插件
  • cocoscreator热更新

    千次阅读 2019-06-27 19:38:54
    首先我们要熟悉一下官方的热更新文档,了解热更新机制。其次自己要去通过实践来熟悉一下。 官方实例几处需要注意的,会导致热更新出问题 参考这篇博客 热更新目录的储存 /** * 这里需要注意的是,不要将相同的...
  • 介绍  在介绍Bugly之前,需要先向大家简单介绍下一些热更新的相关内容。当前市面的热补丁方案有很多,其中比较出名的有阿里的AndFix、美团的Robust以及QZone的超级补丁方案。但它们都存在无法解决的问题,这也是...
  • Bugly实现热更新Demo

    2017-02-07 16:38:37
    使用bugly实现热更新
  • Android热更新

    千次阅读 2020-03-27 17:50:46
    一、什么是热更新 二、热更新原理 三、目前市场上热更新框架的对比 四、热更新实践(Sophix) 一、什么是热更新 用来紧急修复线上版本的bug,而且是在用户无感知的情况下,自动修复bug。我们之前的一个开发流程是...
  • 微信热更新方案实践

    2021-04-05 17:20:03
    作者zzz40500,源码Tinker_imitator,微信热更新方案实践,这是一个微信热修复-hotpatch,热更新的功能案例。
  • ionic热更新插件

    2016-12-06 08:56:41
    ionic热更新插件,多个项目上已使用无bug。下载后文档README有使用详细说明
  • unity实现模型热更新

    2018-02-07 18:05:59
    实现unity模型热更新,选择完资源打包后,再生成配置文件,然后把资源和配置文件一起放到服务器上,本地streamingAssets文件夹下放置空的vertion文本,即可实现资源热更新
  • U3D热更新框架

    2019-03-02 10:40:53
    实现自动打包资源,使用MD5码校验一键化CDN更新资源筛选,游戏启动热更新解决方案。脚本使用xlua。使用说明文档及框架源码下载

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 174,382
精华内容 69,752
关键字:

热更新