Unity代码热更新和资源热更新

news2025/3/26 19:05:18

知识点来源:人间自有韬哥在,hybridclr,豆包

目录

  • 一、代码热更新
    • 1.代码热更新概述
    • 2.HybridCLR
  • 二、资源热更新
    • 1.资源热更新概述
    • 2.AB包
      • 2.1.AB包的加载
      • 2.2.卸载AB包
      • 2.3.加载AB包依赖包
      • 2.4.获取MD5
      • 2.5.生成对比文件
      • 2.6.更新AB包
    • 3.Addressable
      • 3.1.AssetReference资源标识类
      • 3.2.加载资源
      • 3.3. 异步加载场景
      • 3.4.释放资源
      • 3.5.通过名字加载资源
      • 3.6.通过名字释放资源
      • 3.7.通过名字动态加载场景
      • 3.8.根据资源名或标签名加载多个对象
      • 3.9.根据多种信息加载对象
      • 3.10.根据资源定位信息加载资源
      • 3.11.AsyncOperationHandle

一、代码热更新

1.代码热更新概述

代码热更新是指在应用程序运行过程中,无需重新启动整个程序,就能对代码进行更新和修改,使新的代码逻辑生效。在游戏开发等领域,这一技术极为关键。例如,当游戏上线后发现严重的代码漏洞或需要快速添加新功能时,代码热更新能让开发者迅速修复问题或推送新内容,避免玩家因重新下载和安装完整游戏而流失。

2.HybridCLR

(1)安装 2019.4.40、2020.3.26+、 2021.3.0+、2022.3.0+ 中任一版本
(2)填入https://gitee.com/focus-creative-games/hybridclr_unity.git或https://github.com/focus-creative-games/hybridclr_unity.git

在这里插入图片描述
(3)打开菜单HybridCLR/Installer…, 点击安装按钮进行安装。 耐心等待30s左右,安装完成后会在最后打印 安装成功日志。
在这里插入图片描述
(4)创建 Assets/HotUpdate 目录,在目录下 右键 Create/Assembly Definition,创建一个名为HotUpdate的程序集模块
(5)打开菜单 HybridCLR/Settings, 在Hot Update Assemblies配置项中添加HotUpdate程序集
在这里插入图片描述
(5)配置PlayerSettings

  • 如果你用的hybridclr包低于v4.0.0版本,需要关闭增量式GC(Use Incremental GC) 选项
  • Scripting Backend 切换为 IL2CPP
  • Api Compatability Level 切换为 .Net 4.x(Unity 2019-2020) 或 .Net Framework(Unity 2021+)
    在这里插入图片描述

(6)创建热更新脚本, Assets/HotUpdate/Hello.cs

 using System.Collections;
using UnityEngine;

public class Hello
{
    public static void Run()
    {
        Debug.Log("Hello, HybridCLR");
    }
}

(7)创建Assets/LoadDll.cs脚本

using HybridCLR;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.Networking;

public class LoadDll : MonoBehaviour
{

        void Start()
    {
      // Editor环境下,HotUpdate.dll.bytes已经被自动加载,不需要加载,重复加载反而会出问题。
#if !UNITY_EDITOR
        Assembly hotUpdateAss = Assembly.Load(File.ReadAllBytes($"{Application.streamingAssetsPath}/HotUpdate.dll.bytes"));
#else
      // Editor下无需加载,直接查找获得HotUpdate程序集
        Assembly hotUpdateAss = System.AppDomain.CurrentDomain.GetAssemblies().First(a => a.GetName().Name == "HotUpdate");
#endif
    
        Type type = hotUpdateAss.GetType("Hello");
        type.GetMethod("Run").Invoke(null, null);
    }

}

(8)打包运行

  • 运行菜单 HybridCLR/Generate/All进行必要的生成操作。这一步不可遗漏!!!
  • 将{proj}/HybridCLRData/HotUpdateDlls/StandaloneWindows64(MacOS下为StandaloneMacXxx)目录下的HotUpdate.dll复制到Assets/StreamingAssets/HotUpdate.dll.bytes,注意,要加.bytes后缀!!!
  • 打开Build Settings对话框,点击Build And Run,打包并且运行热更新示例工程。

(9) 测试更新

  • 修改Assets/HotUpdate/Hello.cs的Run函数中Debug.Log(“Hello, HybridCLR”);代码,改成Debug.Log(“Hello, World”);。
  • 运行菜单命令HybridCLR/CompileDll/ActiveBulidTarget重新编译热更新代码。
  • 将{proj}/HybridCLRData/HotUpdateDlls/StandaloneWindows64(MacOS下为StandaloneMacXxx)目录下的HotUpdate.dll复制为刚才的打包- 输出目录的 XXX_Data/StreamingAssets/HotUpdate.dll.bytes。
  • 重新运行程序,会发现屏幕中显示Hello, World,表示热更新代码生效了!

二、资源热更新

1.资源热更新概述

资源热更新是指在应用程序运行时,对游戏或应用的资源(如图片、音频、视频、模型等)进行更新,而无需重新安装整个应用。这在游戏开发中尤为重要,因为游戏资源通常较大,且随着游戏的运营,需要不断更新资源以提供新的内容和优化体验。例如,一款手机游戏需要定期更新角色皮肤、地图场景、剧情动画等资源,通过资源热更新,玩家无需重新下载整个游戏包,就能获取并使用这些新资源。

2.AB包

2.1.AB包的加载

AB包的同步加载

  1. 加载AB包:使用AssetBundle.LoadFromFile方法可同步地从指定路径加载AB包,并返回加载后的AB包对象。由于通常在StreamingAssets中会多拷贝一份,所以可以直接从该路径加载。
    示例代码:
AssetBundle modelAssetBundle = AssetBundle.LoadFromFile(Application.streamingAssetsPath + "/" + "model");

注意:同一个AB包不能重复加载,否则会报错,如以下代码会引发错误:

//AssetBundle modelAssetBundle2 = AssetBundle.LoadFromFile(Application.streamingAssetsPath + "/" + "model");//报错
  1. 加载AB包中的资源:通过AssetBundle.LoadAsset方法,可从一个已加载的AB包中同步加载指定资源。
    示例代码:
// 不建议只传名字,因为可能会得到同名不同类型的资源
GameObject cube = modelAssetBundle.LoadAsset("Cube", typeof(GameObject)) as GameObject;
Instantiate(cube);
// 同一个AB包可用于加载多个不同资源
GameObject sphere = modelAssetBundle.LoadAsset<GameObject>("Sphere");
Instantiate(sphere);

AB包的异步加载

  1. 加载AB包AssetBundle.LoadFromFileAsync方法用于异步地从指定路径加载AB包,返回一个异步加载AB包的类AssetBundleCreateRequest
  2. 加载AB包中的资源AssetBundle.LoadAssetAsync方法可异步地从一个已加载的AB包中加载指定资源。异步加载也有多个重载方法,使用时需注意避免仅传名字的方式(可能得到同名不同类型资源),泛型加载在Lua中不支持,建议传入资源的Type。
    示例协程代码:
IEnumerator LoadAssetBundleAsync<T>(string AssetBundleName, string resourceName, Type resourceType)
{
    // 异步加载AB包
    AssetBundleCreateRequest assetBundleCreateRequest = AssetBundle.LoadFromFileAsync(Application.streamingAssetsPath + "/" + AssetBundleName);
    yield return assetBundleCreateRequest;

    // 异步加载AB包中的资源
    // 不建议只传名字
    // AssetBundleRequest assetBundleRequest = assetBundleCreateRequest.assetBundle.LoadAssetAsync(resourceName);
    AssetBundleRequest assetBundleRequest = assetBundleCreateRequest.assetBundle.LoadAssetAsync(resourceName, resourceType);
    // 泛型加载在Lua中不支持
    // AssetBundleRequest assetBundleRequest = assetBundleCreateRequest.assetBundle.LoadAssetAsync<T>(resourceName);
    yield return assetBundleRequest;

    // 获取实际加载出的资源并as成实际类型
    image.sprite = assetBundleRequest.asset as Sprite;
}
  1. 使用协程进行异步加载:通过StartCoroutine开启协程进行异步加载,要确保传入的资源类型正确。
    示例代码:
StartCoroutine(LoadAssetBundleAsync<Sprite>("icon", "quanlity_0", typeof(Sprite)));

2.2.卸载AB包

  1. 卸载指定AB包AssetBundle.Unload方法用于卸载指定的AB包,传入的参数bool值表示是否卸载场景上加载出来的AB包资源。
    示例代码:
modelAssetBundle.Unload(false);
  1. 卸载所有已加载的AB包AssetBundle.UnloadAllAssetBundles方法可卸载所有已加载的AB包,同样传入的参数bool值表示是否卸载场景上加载出来的AB包资源。
    示例代码:
AssetBundle.UnloadAllAssetBundles(false);

2.3.加载AB包依赖包

//加载主包 主包的名字和AB包生成路径一样
AssetBundle mainAssetBundle = AssetBundle.LoadFromFile(Application.streamingAssetsPath + "/" + "PC");

//加载AB包依赖关系清单文件
//AssetBundleManifest是Unity中用于管理AB包依赖关系的类。
AssetBundleManifest assetBundleManifest = mainAssetBundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest");

//GetAllDependencies方法 传入AB包名得到AB包依赖的所有AB包
string[] modelDependencieAssetBundleNameArray = assetBundleManifest.GetAllDependencies("model");

//遍历依赖的所有AB包
for (int i = 0;i< modelDependencieAssetBundleNameArray.Length;i++)
{
    Debug.Log(modelDependencieAssetBundleNameArray[i]);//icon
    AssetBundle.LoadFromFile(Application.streamingAssetsPath + "/" + modelDependencieAssetBundleNameArray[i]);//遍历加载所有依赖的AB包
}

GameObject cube2 = modelAssetBundle.LoadAsset("Cube", typeof(GameObject)) as GameObject;
Instantiate(cube2);//生成的cube因为加载了所需要的所有依赖包 不会丢失材质

2.4.获取MD5

通过资源名或者资源大小无法判断资源是否更新,因此需要利用MD5的唯一性来判断资源的更新

private string GetMD5(string filePath)
{
    // 将文件以流的形式打开
    using (FileStream fileStream = new FileStream(filePath, FileMode.Open))
    {
        // 声明一个MD5对象用于生成MD5码
        MD5 md5 = new MD5CryptoServiceProvider();
        
        // 利用ComputeHash方法得到数据的MD5码,返回一个字节数组
        byte[] md5Info = md5.ComputeHash(fileStream);
        
        // 关闭文件流
        fileStream.Close();
        
        // 将字节数组形式的MD5码转换为16进制字符串
        StringBuilder stringBuilder = new StringBuilder();
        for (int i = 0; i < md5Info.Length; i++)
        {
            // 将字节数组中的每个字节转换为两位的16进制字符串,并追加到stringBuilder中 x2代表转成16进制
            stringBuilder.Append(md5Info[i].ToString("x2"));
        }
        
        // 返回生成的MD5字符串
        return stringBuilder.ToString();
    }
}

2.5.生成对比文件

[MenuItem("AB包工具/创建对比文件")]
public static void CreateABCompareFile()
{
    //获取文件夹信息
    DirectoryInfo directory = Directory.CreateDirectory(Application.dataPath + "/ArtRes/AB/PC/");

    //获取该目录下的所有文件信息
    FileInfo[] fileInfos = directory.GetFiles();

    //用于存储信息的 字符串
    string abCompareInfo = "";

    foreach (FileInfo info in fileInfos)
    {
        //没有后缀的 才是AB包 我们只想要AB包的信息
        if(info.Extension == "")
        {
            //拼接一个AB包的信息
            abCompareInfo += info.Name + " " + info.Length + " " + GetMD5(info.FullName);
            //用一个分隔符分开不同文件之间的信息
            abCompareInfo += '|';
        }
    }

    //因为循环完毕后 会在最后由一个 | 符号 所以 把它去掉
    abCompareInfo = abCompareInfo.Substring(0, abCompareInfo.Length - 1);

    //存储拼接好的 AB包资源信息
    File.WriteAllText(Application.dataPath + "/ArtRes/AB/PC/ABCompareInfo.txt", abCompareInfo);

    //刷新编辑器
    AssetDatabase.Refresh();

    Debug.Log("AB包对比文件生成成功");
}

2.6.更新AB包

遍历远端 AB 包字典
发现本地 AB 包字典没有对应 AB 包就直接下载
发现本地 AB 包字典有对应 AB 包但是 MD5 码不同就更新
遍历远端 AB 包字典时边遍历边移除本地 AB 包字典可能存在的 AB 包,这样本地 AB 包字典剩下的就是要删除的 AB 包

3.Addressable

3.1.AssetReference资源标识类

  • AssetReference:通用资源标识类,可用于加载任意类型资源
  • AssetReferenceAtlasedSprite:图集资源标识类
  • AssetReferenceGameObject:游戏对象资源标识类
  • AssetReferenceSprite:精灵图片资源标识类
  • AssetReferenceTexture:贴图资源标识类
  • AssetReferenceTexture2D
  • AssetReferenceTexture3D
  • AssetReferenceT<T>:指定类型标识类

通过声明不同类型标识类对象,可在Inspector窗口中筛选关联的资源对象,示例代码:

public AssetReference assetReference;
public AssetReferenceAtlasedSprite assetReferenceAtlasedSprite;
public AssetReferenceGameObject assetReferenceGameObject;
public AssetReferenceSprite assetReferenceSprite;
public AssetReferenceTexture assetReferenceTexture;

public AssetReferenceT<AudioClip> assetReferenceTAudioClip;
public AssetReferenceT<RuntimeAnimatorController> assetReferenceTRuntimeAnimatorController;
public AssetReferenceT<TextAsset> assetReferenceTTextAsset;

public AssetReferenceT<Material> assetReferenceTMaterial;

public AssetReference AssetReferenceScene;

3.2.加载资源

  • 注意事项
    • 所有Addressables加载相关都使用异步加载。
    • 加载资源时建议选择Simulate Groups(advanced)模拟模式,而非Use Existing Build(requires built groups)(从AB包模式中加载)。
  • 相关方法
    • AssetReference.LoadAssetAsync:异步加载可寻址资源,返回异步操作处理者AsyncOperationHandle对象。
    • AsyncOperationHandle.Completed:完成回调,添加监听函数进行对完成事件的监听。
    • AsyncOperationHandle.Status:异步加载状态,一般用于判断是否成功。
    • AsyncOperationHandle.Result:异步加载出来的资源。
    • AssetReference.IsDone 判断资源是否加载
    • AssetReference.Asset 加载出来的资源

示例代码

// 异步加载可寻址资源
AsyncOperationHandle<GameObject> asyncOperationHandle = assetReference.LoadAssetAsync<GameObject>();
// 添加完成回调监听
asyncOperationHandle.Completed += OnAsyncOperationHandleCompleted;

// 加载成功后的回调函数
private void OnAsyncOperationHandleCompleted(AsyncOperationHandle<GameObject> asyncOperationHandle)
{
    if (asyncOperationHandle.Status == AsyncOperationStatus.Succeeded)
    {
        Instantiate(asyncOperationHandle.Result);
    }
      // 使用标识类创建
    // if(assetReference.IsDone)
    // {
    //     Instantiate(assetReference.Asset);
    // }
}

3.3. 异步加载场景

使用AssetReference.LoadSceneAsync异步加载场景,并添加Completed回调。

AssetReferenceScene.LoadSceneAsync().Completed += (asyncOperationHandle) =>
{
    print("场景加载结束");
};

3.4.释放资源

  • 使用AssetReference.ReleaseAsset释放资源,建议在加载成功且用完资源后,在加载成功的回调中释放,避免没加载成功就释放。
  • 注意事项:
    • 执行释放资源方法后,资源标识类中的资源会置空,但AsyncOperationHandle类中的对象不为空。
    • 释放资源不会影响场景中已实例化出来的对象,但会影响使用的资源(如AB包模式下赋值材质后释放会丢失材质)。

示例代码:

assetReference.LoadAssetAsync<GameObject>().Completed += (asyncOperationHandle) =>
{
    if (asyncOperationHandle.Status == AsyncOperationStatus.Succeeded)
    {
        GameObject cube = Instantiate(asyncOperationHandle.Result);
        assetReference.ReleaseAsset();

        assetReferenceTMaterial.LoadAssetAsync().Completed += (materialAsyncOperationHandle) =>
        {
            cube.GetComponent<MeshRenderer>().material = materialAsyncOperationHandle.Result;
            assetReferenceTMaterial.ReleaseAsset();
            print(materialAsyncOperationHandle.Result);
            print(assetReferenceTMaterial.Asset);
        };
    }
};

接实例化对象
使用AssetReference.InstantiateAsync异步实例化对象,该方法有一些重载可调整位置角度等,相当于自动完成LoadAssetAsync并在Completed回调中实例化对象的操作,一般适用于GameObject预设体。

assetReferenceGameObject.InstantiateAsync();

通过标签相关特性约束标识类对象
意味着assetReference只能关联标签为”SD”, “HD”, “FHD”的资源

[AssetReferenceUILabelRestriction("SD", "HD", "FHD")]
public AssetReference assetReference;

3.5.通过名字加载资源

动态加载单个资源
在Unity中,可使用Addressables来动态加载单个资源,需引用UnityEngine.AddressableAssetsUnityEngine.ResourceManagement.AsyncOperations命名空间。使用Addressables.LoadAssetAsync方法,传入资源名或标签名来动态加载单个资源。

示例代码

Addressables.LoadAssetAsync<GameObject>("Red").Completed += (asyncOperationHandle) =>
{
    if (asyncOperationHandle.Status == AsyncOperationStatus.Succeeded)
        Instantiate(asyncOperationHandle.Result);
};

** 注意事项**

  • 重复加载相同资源不会报错,但没有对应的Key会报错。
  • 若存在同名或同标签的同类型资源,会自动加载找到的第一个满足条件的对象。
  • 若存在同名或同标签的不同类型资源,可根据泛型类型来决定加载哪一个。

3.6.通过名字释放资源

使用Addressables.Release方法释放资源,要在资源加载完成且使用完毕后进行释放。

asyncOperationHandle = Addressables.LoadAssetAsync<GameObject>("Cube");
asyncOperationHandle.Completed += (handle) =>
{
    if (handle.Status == AsyncOperationStatus.Succeeded)
        Instantiate(handle.Result);
    Addressables.Release(handle);
};

// 在对象被删除后释放是较为合理的
private void OnDestroy()
{
    Addressables.Release(asyncOperationHandle);
}

3.7.通过名字动态加载场景

使用Addressables.LoadSceneAsync动态加载场景,可手动激活异步加载的场景。

Addressables.LoadSceneAsync("SampleScene", UnityEngine.SceneManagement.LoadSceneMode.Single, false)
    .Completed += (handle) =>
{
    Debug.Log("异步场景加载完成");
    handle.Result.ActivateAsync().completed += (a) =>
    {
        Debug.Log("异步激活场景完成");
        Addressables.Release(handle);
    };
};

3.8.根据资源名或标签名加载多个对象

使用Addressables.LoadAssetsAsync传入一个Key异步加载多个资源。

// 回调函数处理资源obj
Addressables.LoadAssetsAsync<Object>("Cube", (obj) =>
{
    print("回调函数处理资源" + obj.name);
});

// asyncOperationHandle完成事件中处理资源handle.Result
AsyncOperationHandle<IList<Object>> asyncOperationHandle = Addressables.LoadAssetsAsync<Object>("Red", (obj) =>
{
    // print(obj.name);
});
asyncOperationHandle.Completed += (handle) =>
{
    foreach (var item in handle.Result)
    {
        print("asyncOperationHandle完成时间中处理资源" + item.name);
    }
    Addressables.Release(handle);
};

3.9.根据多种信息加载对象

使用Addressables.LoadAssetsAsync传入Key列表异步加载多个资源,可指定合并模式。

List<string> keyList = new List<string>() { "Cube", "Red" };
Addressables.LoadAssetsAsync<Object>(keyList, (obj) => { print("根据多种信息加载对象" + obj.name); },
    Addressables.MergeMode.Intersection);

3.10.根据资源定位信息加载资源

根据名字或者标签获取资源定位信息加载资源
可使用Addressables.LoadResourceLocationsAsync异步加载单Key资源定位信息,再通过Addressables.LoadAssetAsync传入ResourceLocation资源定位信息进行资源加载。

示例代码如下:

// 异步加载资源定位信息 返回列表型handle
AsyncOperationHandle<IList<IResourceLocation>> asyncOperationHandle = Addressables.LoadResourceLocationsAsync("Cube", typeof(GameObject));
asyncOperationHandle.Completed += (handle) =>
{
    if (handle.Status == AsyncOperationStatus.Succeeded)
    {
        // handle.Result是所有满足条件的资源的资源定位信息列表
        foreach (var resourceLocation in handle.Result)
        {
            // 打印出资源名
            print(resourceLocation.PrimaryKey);
            // 根据资源定位信息异步加载资源
            Addressables.LoadAssetAsync<GameObject>(resourceLocation).Completed += (obj) => { Instantiate(obj.Result); };
        }
    }
    else
    {
        Addressables.Release(asyncOperationHandle);
    }
};

根据名字标签组合信息获取资源定位信息加载资源
使用Addressables.LoadResourceLocationsAsync异步加载多Key资源定位信息,结合资源名和标签名的组合、合并模式以及资源类型进行加载。

示例代码如下:

// 异步加载组合型资源定位信息 返回列表型handle
AsyncOperationHandle<IList<IResourceLocation>> asyncOperationHandle2 = Addressables.LoadResourceLocationsAsync(new List<string>() { "Cube", "Sphere", "SD" }, Addressables.MergeMode.Union, typeof(Object));
asyncOperationHandle2.Completed += (handle) =>
{
    if (handle.Status == AsyncOperationStatus.Succeeded)
    {
        // 资源定位信息加载成功
        foreach (var resourceLocation in handle.Result)
        {
            // 使用定位信息来加载资源
            print("******");
            print(resourceLocation.PrimaryKey); // 资源名
            print(resourceLocation.InternalId); // 资源路径
            print(resourceLocation.ResourceType.Name); // 资源类型

            Addressables.LoadAssetAsync<Object>(resourceLocation).Completed += (obj) =>
            {
                // Instantiate(obj.Result);
            };
        }
    }
    else
    {
        Addressables.Release(asyncOperationHandle2);
    }
};

3.11.AsyncOperationHandle

获取加载进度
通过 AsyncOperationHandle.GetDownloadStatus 可获取下载状态,包含以下属性:

  • DownloadStatus.Percent:下载进度
  • DownloadStatus.DownloadedBytes:当前已下载字节数
  • DownloadStatus.TotalBytes:总共需下载字节数

示例代码:

IEnumerator LoadAssetCoroutine()
{
    AsyncOperationHandle<GameObject> asyncOperationHandle = Addressables.LoadAssetAsync<GameObject>("Cube");
    while (!asyncOperationHandle.IsDone)
    {
        DownloadStatus downloadStatus = asyncOperationHandle.GetDownloadStatus();
        print(downloadStatus.Percent);
        print(downloadStatus.DownloadedBytes + "/" + downloadStatus.TotalBytes);
        yield return 0;
    }
    if (asyncOperationHandle.Status == AsyncOperationStatus.Succeeded)
    {
        Instantiate(asyncOperationHandle.Result);
    }
    else
        Addressables.Release(asyncOperationHandle);
}
StartCoroutine(LoadAssetCoroutine());

无类型句柄转换

  • 有类型的 AsyncOperationHandle 可隐式转换为无类型的 AsyncOperationHandle
  • 使用 AsyncOperationHandle.Convert<T>() 可将无类型句柄转换为有类型的泛型对象。

示例代码:

AsyncOperationHandle<Texture2D> asyncOperationHandleTexture2D = Addressables.LoadAssetAsync<Texture2D>("Cube");
// 有类型转无类型
AsyncOperationHandle tempAsyncOperationHandle = asyncOperationHandleTexture2D;
// 无类型转有类型
asyncOperationHandleTexture2D = tempAsyncOperationHandle.Convert<Texture2D>();

这种转换便于在 Addressables 管理器字典中使用 AsyncOperationHandle 类型。

强制同步加载资源
使用 AsyncOperationHandle.WaitForCompletion 可等待异步加载完成后继续执行后续代码。

示例代码:

print("1");
asyncOperationHandleTexture2D.WaitForCompletion();
print("2");
print(asyncOperationHandleTexture2D.Result.name);

注意

  • Unity 2020.1 及之前版本,执行该代码会等待所有未完成的异步加载操作完成。
  • Unity 2020.2 及之后版本,加载已下载资源时性能影响较小。
    总体不建议使用该方式加载资源,因其可能阻塞主线程。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2321220.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

于纷扰中寻静谧:正念观照的智慧之旅

在现代社会的快节奏浪潮中&#xff0c;我们仿若被裹挟前行的浮萍&#xff0c;生活的压力与信息的洪流冲刷着内心的宁静&#xff0c;焦虑与迷茫如影随形。而正念观照&#xff0c;恰似一叶扁舟&#xff0c;能引领我们在心灵的海洋中回归自我&#xff0c;探寻那片澄澈之境。 正念…

环境评价分析中土地利用现状图的制作方法

在环境评价中&#xff0c;土地利用现状图是重要的基础图件&#xff0c;用于分析项目区域的土地利用类型、分布格局及其生态环境特征。 以下是制作土地利用现状图的详细步骤和方法&#xff1a; 一、前期准备工作 确定制图范围和比例尺 根据评价范围确定制图区域边界 常用比例…

编程题记录3

九宫幻方 题目链接&#xff1a;https://www.lanqiao.cn/problems/100/learning/?page1&first_category_id1&second_category_id3&tags%E7%9C%81%E8%B5%9B&tag_relationintersection 先旋转、镜像得到所有的情况&#xff0c;可以发现情况是可以暴力得出的。…

sql语句给表添加一个递增列

SSMS–》视图-》数据库(表)-》新建查询 ALTER TABLE [表名] DROP COLUMN ID ALTER TABLE [表名] ADD ID INT IDENTITY(1,1)执行完以上操作&#xff0c;会在表的最后一列添加一个自增字段 接下来如何把最后一个字段放到第一个字段呢&#xff1f; 假如sqlserver 表test 有以下…

vue java 实现大地图切片上传

文章目录 一、项目背景二、页面三、代码1.前端2.mock-i18n.js文件3.xx.js文件定义方法4.配置文件 application.properties5.后端方法 四、易错点易错点1&#xff1a;前端要进行分片切割&#xff0c;然后再分片上传。易错点2&#xff1a;后端配置文件要配置。易错点3&#xff1a…

langchain+ollama+deepseek的部署(win)

ANACONDA 安装 官网&#xff1a;Download Anaconda Distribution | Anaconda 配置系统环境 在系统变量中配置 检查是否配置成功 通过 cmd 窗口输入&#xff1a; conda info 如图&#xff1a;表示成功 配置你的虚拟环境 二、安装 ollama allama 安装 官网地址&#xff1a…

deepseek实战教程-第四篇开放平台接口文档使用

第二篇讲解了如何本地安装大模型&#xff0c;然后编写一个基于jsspringboot的项目&#xff0c;通过页面实现对话的功能。实际上&#xff0c;上面的demo用到是deepseek提供的接口&#xff0c;那么deepseek共提供了多少接口呢&#xff1f;这就要讨论到deepseek的接口库了&#xf…

一站式电脑工具箱,功能全面且实用

小明工具箱是一款集成了系统设置、维护工具、实用工具、图像处理等四大类工具的电脑工具箱&#xff0c;涵盖了上百种实用工具&#xff0c;能够满足用户在文件管理、文本处理、系统优化、图像处理等多方面的需求。 初次使用&#xff0c;需双击软件&#xff0c;便会自动将工具解压…

那些正常的动态规划

文章目录 前言动态规划到底是啥&#xff1f; 线性dp最长上升子序列子集和子序列和子串的区别内容分析 最大上升子序列例题1——[NOIP2004 提高组] 合唱队形分析 最长公共子序列最长公共子串 平面dp例题2——[NOIP2000 提高组] 方格取数分析 例题3——[NOIP2008 提高组] 传纸条分…

华为交换相关

端口模式 &#xff08;1&#xff09;access&#xff1a;只能属于单个VLAN&#xff0c;一般用于连接计算机端口 &#xff08;2&#xff09;trunk&#xff1a;端口允许多个VLAN通过&#xff0c;可以接收和发送多个VLAN报文&#xff0c;默认情况下只有管理VLAN不携带标签信息 &…

Chrome Performance 面板完全指南:从卡顿到丝滑的终极调试术

1.写在前面 前端性能调试是优化网页加载速度和运行效率的关键步骤&#xff0c;Chrome DevTools 的 Performance 面板 是核心工具; 2.Performance 面板使用步骤 ★ 基础 打开面板 在 Chrome 中按 F12 → 切换到 Performance 标签页。 开始录制 方式一&#xff1a;点击 ⚫️ 圆…

JDK 24:Java 24 中的新功能

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家&#xff0c;历代文学网&#xff08;PC端可以访问&#xff1a;历代文学&#xff0c;移动端可微信小程序搜索“历代文学”&#xff09;总架构师&#xff0c;15年工作经验&#xff0c;精通Java编程&#xff0c;高并发设计&#xf…

ubuntu服务器server版安装,ssh远程连接xmanager管理,改ip网络连接。图文教程

ventoy启动服务器版iso镜像&#xff0c;注意看server名称&#xff0c;跟之前desktop版ubuntu不一样。没有gui界面。好&#xff0c;进入命令行界面。语言彻底没汉化了&#xff0c;选英文吧&#xff0c;别的更看不懂。 跟桌面版ubuntu类似&#xff0c;选择是否精简系统&#xff0…

python机器学习——新手入门学习笔记

一&#xff0c;概论 1.什么是机器学习 定义&#xff1a; 机器学习是从数据中自动分析获得模型&#xff0c;并利用模型对未知数据进行预测。 其实就是通过问题和数据&#xff0c;发现规律&#xff0c;并进行预测&#xff0c;与人脑相似。目的就是从历史数据当中获得规律&#x…

LabVIEW 与 PLC 通讯的常见方式

在工业自动化和数据采集系统中&#xff0c;PLC&#xff08;可编程逻辑控制器&#xff09; 广泛用于控制和监测各种设备&#xff0c;而 LabVIEW 作为强大的图形化编程工具&#xff0c;常用于上位机数据处理和可视化。为了实现 LabVIEW 与 PLC 的高效通讯&#xff0c;常见的方法包…

深度学习 Deep Learning 第9章 卷积网络 CNN

深度学习 Deep Learning 第9章 卷积网络 章节概述 本章深入探讨了卷积网络的原理、变体及其在深度学习中的应用。卷积网络通过卷积操作实现了参数共享和稀疏连接&#xff0c;显著提高了模型的效率和性能。本章首先介绍了卷积操作的基本形式及其在不同数据维度上的应用&#x…

Tekton系列之实践篇-从触发到完成的完整执行过程

以下介绍的是基于 Gitee 仓库 的 Tekton 工作流程 操作流程 定义task 克隆代码的task # task-clone.yaml apiVersion: tekton.dev/v1beta1 kind: Task metadata:name: git-clone spec:workspaces:- name: source # 工作目录params:- name: repo-url # 你的 Gitee 仓库地址…

【简单学习】Prompt Engineering 提示词工程

一、Prompt 1、Prompt 是什么&#xff1f; Prompt 是一种人为构造的输入序列&#xff0c;用于引导 GPT 模型根据先前输入的内容生成相关的输出。简单来说&#xff0c;就是你向模型提供的 “提示词”。 在 ChatGpt 中&#xff0c;我们可以通过设计不同的 prompt&#xff0c;让…

零基础入门网络爬虫第5天:Scrapy框架

4周 Srapy爬虫框架 不是一个简单的函数功能库&#xff0c;而是一个爬虫框架 安装&#xff1a;pip install scrapy 检测&#xff1a;scrapy -h Scrapy爬虫框架结构 爬虫框架 爬虫框架是实现爬虫功能的一个软件结构和功能组件集合爬虫框架是一个半成品&#xff0c;能够帮助…

C#设计模式快速回顾

知识点来源&#xff1a;人间自有韬哥在&#xff0c;豆包 目录 一、七大原则1. 单一职责原则 (Single Responsibility Principle)2. 开放封闭原则 (Open-Closed Principle)3. 里氏替换原则 (Liskov Substitution Principle)4. 接口隔离原则 (Interface Segregation Principle)5…