unity热更新json_InjectFix 热更新方案集成

源码地址:

Tencent/InjectFix​github.com
9d5439b318ec1ff065ca262f0b86fa20.png

原理:

InjectFix基本原理​segmentfault.com
ca0f096fb8f50d74c34366953a6874a9.png

优点:

1.无需拆分工程,不需对工程进行重构, 但大规模新增功能受限
2.全部代码都以原生方式运行,有更新的话,仅仅待更新那少数几个函数切换到解析执行
3.DLC更新较小

集成IFix

  • 编译

打开源码包的SourceVSProjbuild_for_unity.bat,UNITY_HOME变量的值修改为指向本机unity安装目录 @set UNITY_HOME=G:WinEditor

运行build_for_unity.bat

将IFixToolKit拷贝到Unity项目的Assets同级目录

eb579ea5def6eecd4bbd50ed67572eea.png
IFixToolKit拷贝

将IFix.Core.dll拷贝到Unity项目的Assets/Plugins

升级IFix步骤也是如此

使用说明

  • 加载补丁
本地测试
var patchPath = "./Assets/IFix/Resources/Assembly-CSharp.ill.bytes";
if (File.Exists(patchPath))
{
    PatchManager.Load(new FileStream(patchPath, FileMode.Open));
}

线上更新,从AB包中加载,如果二进制流内容不为空,则

PatchManager.Load(binaryObject.m_stream);
  • 配置

热补丁的实现依赖于提前做些静态代码插入,需要配置对可能热更类预处理,配置后才能被修复。

[Configure]
public class InterpertConfig {
[IFix]
static IEnumerable<Type> HotfixInject
{
    get
    {
        var t1 = from type in Assembly.Load("要加载的dll").GetTypes() select type;
或者 var t1 = Assembly.Load("Assembly-CSharp(加载的程序集)").GetTypes()
 
        var canFixtypes = (from type in t1
                         where (IsNeedInject(type)
                     )
                     select type);
        return canFixtypes;
    }
}
}
private static List<string> removeInjectNamespacesFix = new List<string>
{
   "XLua",
};
 private static bool IsTypeNeedInject(Type type){
if (!string.IsNullOrEmpty(type.Namespace)){
 if (removeInjectNamespacesFix .Contains(type.Namespace))
    {
      return false;
    }
 foreach (string removeInjectNamespaceFix in removeInjectNamespacesFix )
 {
 if (type.Namespace.StartsWith(removeInjectNamespaceFix ))
     {
        return false;
     }
  }
}
return true;
}

注意:

· 配置类打上Configure标签
· 配置的属性打上IFix标签,而且必须是 static 类型
· 这配置必须放在Editor目录下

动态配置可以批量处理,后续该名字空间下增删类,都不需要更改配置。

配置好后,打包手机版本会自动预处理,如果希望自动化打包,也可以手动调用IFix.Editor.IFixEditor.InjectAllAssemblys函数。

  • 流程说明

有两个步骤:Inject,Fix。

Inject只需在发包时做一次,这个步骤主要是对代码做一定的预处理,只有做了预处理的代码后续才能正常加载补丁。

执行"InjectFix/Fix"菜单。

补丁制作成功后会放到工程指定目录下,文件名为“{Dll Name}.patch.bytes”(比如:“Assembly-CSharp.patch.bytes”),编辑器测试可直接加载,加载就能看到效果,线上热更打将patch.bytes文件打进热更包即可。

Fix的过程是根据修改后的代码编译后的dll,生成补丁。

修改代码和Fix之间无需执行Inject,否则iFix会认为这是个线上版本,拒绝生成补丁。鉴于这个限制,编辑器下体验流程上做一定的调整:先修改代码为正确逻辑,生成patch。然后回退代码,执行Inject模拟线上有问题的版本。

  • IFix常用说明

[IFix.Patch]

修复某个函数,该标签只能用在方法上,直接在方法上面标注一下[IFix.Patch]即可

[IFix.Patch]
 private int Add(int a,int b)
  {
      return a * b;
   }

[IFix.Interpret]

新增个函数或者类,在属性,方法,类型上,直接在要新增的代码上面标注一下这个标签即可。

[IFix.Patch]和[IFix.Interpret]属于比较常用的修复Bug

[IFix.CustomBridge]

​ 在注入阶段使用; 把一个虚拟机的类适配到原生interface或者把一个虚拟机的函数适配到原生delegate。

· 修复代码赋值一个闭包到一个delegate变量;
· 修复代码的Unity协程用了yield return;
· 新增一个函数,赋值到一个delegate变量;
· 新增一个类,赋值到一个原生interface变量;
· 新增函数,用了yield return;

​ 该标签只能用在类上,写上一个静态类,里面有一个静态字段,值就是interface和delegate的类型集合,该配置类不能放到Editor目录,且不能内嵌到另外一个类里头。

例如:

[IFix.CustomBridge]public static class AdditionalBridge
{
 static List<Type> bridge = new List<Type>()
    {
 typeof(ISubSystem),
 typeof(IEnumerator), //如果之前使用过协程,可不做处理
 typeof(Test.MyDelegate)
    };
}

[Filter]

​ 在注入阶段使用,过滤某些方法。在注入阶段,凡是在[IFix]标签下的属性里面的值,都会被注入适配代码,但如果不想对某个函数进行注入,可以用该标签进行过滤。该标签只能用在方法上,Configure类中的一个静态方法。

[Filter]
 static bool Filter(System.Reflection.MethodInfo methodInfo)
  {
 return methodInfo.DeclaringType.FullName == "Test" 
 && (methodInfo.Name == "Test2" || methodInfo.Name == "Test1");
  }

用法

​ 该标签只能用在方法上,Configure类中的一个静态方法。

标签使用阶段用途用法
[IFix.Patch]补丁修复函数只能放在函数上
[IFix.Interpret]补丁新增属性,函数,类型放在属性,函数,类型上
[IFix.CustomBridge]注入interface和delegate桥接只能放在单独写一个静态类上,存储虚拟机的类适配到原生interface或者虚拟机的函数适配到原生delegate,该类不能放Editor目录
[Configure]注入配置类只能放在单独写一个存放在Editor目录下的类上
[IFix]注入可能需要修复函数的类的集合只能放在[Configure]类的一个静态属性上
[Filter]注入不想发生注入的函数只能放在[Configure]类的一个静态函数上

测试用例:

using System;
using System.Collections;
using UnityEngine;
using UnityEngine.UI;

namespace Assets.Scripts.GameSystem
{
    [IFix.CustomBridge]
    public static class AdditionalBridge
    {
      static System.Collections.Generic.List<Type> bridge = new System.Collections.Generic.List<Type>()
    {

        typeof(ISubSystem),
        typeof(IEnumerator), //如果之前使用过协程,可不做处理
        typeof(IFixShowUIView.MyDelegate)
    };
    }

    class IFixShowUIView : MonoBehaviour
    {
        #region widget
        public Text2 ifixInfo;
        public Button patchBtn;
        public Toggle interpretTgl;
        public Button attributeBtn;
        public Button mthBtn;
        public Button classBtn;
        public GameObject interpretGroup;
        public Toggle customBridgeTgl;
        public Button interfaceBtn;
        public Button iEnumeratorBtn;
        public Button delegateBtn;
        public GameObject customBridgeGroup;

        private string iFixName;//这个name字段是原生的

        public string IFixName
        {
            [IFix.Interpret]
            set
            {
                iFixName = value;
            }
            [IFix.Interpret]
            get
            {
                return iFixName;
            }
        }

        private void Awake()
        {
            InitWidget();
        }
        private void OnDestroy()
        {
            Clear();
        }
        #endregion
        public void Clear()
        {
            patchBtn.onClick.RemoveListener(PatchBtnClick);
            interpretTgl.onValueChanged.RemoveListener(InterpretClick);
            attributeBtn.onClick.RemoveListener(AttributeBtnClick);
            mthBtn.onClick.RemoveListener(MthBtnClick);
            classBtn.onClick.RemoveListener(ClassBtnClick);
            customBridgeTgl.onValueChanged.RemoveListener(CustomBridgeClick);
            interfaceBtn.onClick.RemoveListener(InterfaceTest);
            iEnumeratorBtn.onClick.RemoveListener(IEnumeratorTest);
            delegateBtn.onClick.RemoveListener(DelegateTest);
        }

        private void InitWidget()
        {
            patchBtn.onClick.AddListener(PatchBtnClick);
            interpretTgl.onValueChanged.AddListener(InterpretClick);
            attributeBtn.onClick.AddListener(AttributeBtnClick);
            mthBtn.onClick.AddListener(MthBtnClick);
            classBtn.onClick.AddListener(ClassBtnClick);
            customBridgeTgl.onValueChanged.AddListener(CustomBridgeClick);
            interfaceBtn.onClick.AddListener(InterfaceTest);
            iEnumeratorBtn.onClick.AddListener(IEnumeratorTest);
            delegateBtn.onClick.AddListener(DelegateTest);
        }

        private void PatchBtnClick()
        {
            Debug.Log("修复函数");

            ifixInfo.text = string.Format("Add函数由*修改为+  结果为:{0}", Add(10, 9));
        }

        [IFix.Patch]
        private int Add(int a,int b)
        {
            return a * b;
        }

        private void InterpretClick(bool isOn)
        {
            interpretGroup.SetActive(isOn);
        }
        private void CustomBridgeClick(bool isOn)
        {
            customBridgeGroup.SetActive(isOn);
        }
        [IFix.Patch]
        private void AttributeBtnClick()
        {
            ifixInfo.text = "测试IFix";
            Debug.Log("新增属性");
            IFixName = "新增属性IFixName";
            ifixInfo.text = iFixName;
        }
        [IFix.Patch]
        private void MthBtnClick()
        {
            ifixInfo.text = "测试IFix";
            Debug.Log("新增方法");
            Sub();
        }
        [IFix.Patch]
        private void ClassBtnClick()
        {
            ifixInfo.text = "测试IFix";
            Debug.Log("新增类");
            ifixInfo.text = InterpretNewClass.GetNewClass();
        }

        [IFix.Patch]
        private void InterfaceTest()
        {
            ifixInfo.text = "测试IFix";
            DebugHelper.Log("测试Interface");
            ISubSystem subSystem = new SubSystem();
            ifixInfo.text = subSystem.Print();
        }
        [IFix.Patch]
        private void IEnumeratorTest()
        {
            ifixInfo.text = "测试IFix";
            DebugHelper.Log("测试IEnumerator");
            StartCoroutine(IFixIEnumerator());
        }
        [IFix.Patch]
        private void DelegateTest()
        {
            ifixInfo.text = "测试IFix";
            DebugHelper.Log("测试Delegate");
        MyDelegate myDelegate = new MyDelegate(TestDelegate());
        ifixInfo.text = string.Format("测试Delegate解果----:{0}", myDelegate(100, 200));
    }

    [IFix.Interpret]
    public IEnumerator IFixIEnumerator()
    {
        yield return new WaitForSeconds(1);
        ifixInfo.text = "测试--------IFixIEnumerator";
    }

    public delegate int MyDelegate(int a, int b);

    [IFix.Interpret]
    public MyDelegate TestDelegate()
    {
        return (a, b) => a + b;
    }

    [IFix.Interpret]
    public void Sub()
    {
        ifixInfo.text = "新增函数Sub";
    }
}

[IFix.Interpret]
public static class InterpretNewClass
{
    public static string GetNewClass()
    {
        return "新增类InterpretNewClass";
    }
}


public interface ISubSystem
    {
        bool running { get; }
        string Print();
    }

[IFix.Interpret]
public class SubSystem : ISubSystem
{
    public bool running { get { return true; } }
    public string Print()
    {
        return "SubSystem1.Print";
    }
}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值