Android下加载libUnreal.so文件

news/2025/10/26 11:36:06/文章来源:https://www.cnblogs.com/kekec/p/19166181

Android下加载libUnreal.so文件

Android 下动态加载so库

具体有两种方式:

(1)调用 load(String filename) 方法

System.load("/data/data/com.example.mygame/files/libUnreal.so"); // 传递进去的是全路径

(2)调用 loadLibrary(String libname) 方法

System.loadLibrary("Unreal"); // 传递进去的是so文件的名称

 

具体的Android系统代码如下:

// https://android.googlesource.com/platform/libcore/+/15d8280/luni/src/main/java/java/lang/System.java
public final class System {// 。。。 。。。/*** Loads and links the dynamic library that is identified through the* specified path. This method is similar to {@link #loadLibrary(String)},* but it accepts a full path specification whereas {@code loadLibrary} just* accepts the name of the library to load.** @param pathName*            the path of the file to be loaded.*/public static void load(String pathName) {Runtime.getRuntime().load(pathName, VMStack.getCallingClassLoader());}/*** Loads and links the library with the specified name. The mapping of the* specified library name to the full path for loading the library is* implementation-dependent.** @param libName*            the name of the library to load.* @throws UnsatisfiedLinkError*             if the library could not be loaded.*/public static void loadLibrary(String libName) {Runtime.getRuntime().loadLibrary(libName, VMStack.getCallingClassLoader());}// 。。。 。。。
}// https://android.googlesource.com/platform/libcore/+/15d8280/luni/src/main/java/java/lang/Runtime.java
public class Runtime {// 。。。 。。。/*** Loads and links the dynamic library that is identified through the* specified path. This method is similar to {@link #loadLibrary(String)},* but it accepts a full path specification whereas {@code loadLibrary} just* accepts the name of the library to load.** @param pathName*            the absolute (platform dependent) path to the library to load.* @throws UnsatisfiedLinkError*             if the library can not be loaded.*/public void load(String pathName) {load(pathName, VMStack.getCallingClassLoader());}/** Loads and links the given library without security checks.*/void load(String pathName, ClassLoader loader) {if (pathName == null) {throw new NullPointerException("pathName == null");}String error = doLoad(pathName, loader);if (error != null) {throw new UnsatisfiedLinkError(error);}}/*** Loads and links the library with the specified name. The mapping of the* specified library name to the full path for loading the library is* implementation-dependent.** @param libName*            the name of the library to load.* @throws UnsatisfiedLinkError*             if the library can not be loaded.*/public void loadLibrary(String libName) {loadLibrary(libName, VMStack.getCallingClassLoader());}/** Searches for a library, then loads and links it without security checks.*/void loadLibrary(String libraryName, ClassLoader loader) {if (loader != null) {String filename = loader.findLibrary(libraryName);if (filename == null) {throw new UnsatisfiedLinkError("Couldn't load " + libraryName +" from loader " + loader +": findLibrary returned null");}String error = doLoad(filename, loader);if (error != null) {throw new UnsatisfiedLinkError(error);}return;}String filename = System.mapLibraryName(libraryName); // 实现是在System.c里面,返回so的文件名。例如System.mapLibraryName('Unreal') 返回的是libUnreal.soList<String> candidates = new ArrayList<String>();String lastError = null;for (String directory : mLibPaths) {String candidate = directory + filename;candidates.add(candidate);if (IoUtils.canOpenReadOnly(candidate)) {String error = doLoad(candidate, loader);if (error == null) {return; // We successfully loaded the library. Job done.
                }lastError = error;}}if (lastError != null) {throw new UnsatisfiedLinkError(lastError);}throw new UnsatisfiedLinkError("Library " + libraryName + " not found; tried " + candidates);}// 。。。 。。。private String doLoad(String name, ClassLoader loader) {// Android apps are forked from the zygote, so they can't have a custom LD_LIBRARY_PATH,// which means that by default an app's shared library directory isn't on LD_LIBRARY_PATH.// The PathClassLoader set up by frameworks/base knows the appropriate path, so we can load// libraries with no dependencies just fine, but an app that has multiple libraries that// depend on each other needed to load them in most-dependent-first order.// We added API to Android's dynamic linker so we can update the library path used for// the currently-running process. We pull the desired path out of the ClassLoader here// and pass it to nativeLoad so that it can call the private dynamic linker API.// We didn't just change frameworks/base to update the LD_LIBRARY_PATH once at the// beginning because multiple apks can run in the same process and third party code can// use its own BaseDexClassLoader.// We didn't just add a dlopen_with_custom_LD_LIBRARY_PATH call because we wanted any// dlopen(3) calls made from a .so's JNI_OnLoad to work too.// So, find out what the native library search path is for the ClassLoader in question...String ldLibraryPath = null;if (loader != null && loader instanceof BaseDexClassLoader) {ldLibraryPath = ((BaseDexClassLoader) loader).getLdLibraryPath();}// nativeLoad should be synchronized so there's only one LD_LIBRARY_PATH in use regardless// of how many ClassLoaders are in the system, but dalvik doesn't support synchronized// internal natives.synchronized (this) {return nativeLoad(name, loader, ldLibraryPath);}}// TODO: should be synchronized, but dalvik doesn't support synchronized internal natives.private static native String nativeLoad(String filename, ClassLoader loader, String ldLibraryPath);  // 注:该方法为c++实现// 。。。 。。。
}// https://android.googlesource.com/platform/libcore/+/master/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java
// https://www.androidos.net.cn/android/9.0.0_r8/xref/libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java
/*** Base class for common functionality between various dex-based* {@link ClassLoader} implementations.*/
public class BaseDexClassLoader extends ClassLoader {/*** Hook for customizing how dex files loads are reported.** This enables the framework to monitor the use of dex files. The* goal is to simplify the mechanism for optimizing foreign dex files and* enable further optimizations of secondary dex files.** The reporting happens only when new instances of BaseDexClassLoader* are constructed and will be active only after this field is set with* {@link BaseDexClassLoader#setReporter}.*//* @NonNull */ private static volatile Reporter reporter = null;@UnsupportedAppUsageprivate final DexPathList pathList;// 。。。 。。。public BaseDexClassLoader(String dexPath,String librarySearchPath, ClassLoader parent, ClassLoader[] sharedLibraryLoaders,ClassLoader[] sharedLibraryLoadersAfter,boolean isTrusted) {super(parent);// Setup shared libraries before creating the path list. ART relies on the class loader// hierarchy being finalized before loading dex files.this.sharedLibraryLoaders = sharedLibraryLoaders == null? null: Arrays.copyOf(sharedLibraryLoaders, sharedLibraryLoaders.length);this.pathList = new DexPathList(this, dexPath, librarySearchPath, null, isTrusted);// 。。。 。。。
    }public BaseDexClassLoader(ByteBuffer[] dexFiles, String librarySearchPath, ClassLoader parent) {super(parent);this.sharedLibraryLoaders = null;this.sharedLibraryLoadersAfter = null;this.pathList = new DexPathList(this, librarySearchPath);this.pathList.initByteBufferDexPath(dexFiles);// Run background verification after having set 'pathList'.this.pathList.maybeRunBackgroundVerification(this);}@SystemApi(client = MODULE_LIBRARIES)public void addNativePath(@NonNull Collection<String> libPaths) {pathList.addNativePath(libPaths);}@Overridepublic String findLibrary(String name) {return pathList.findLibrary(name);}/*** Returns colon-separated set of directories where libraries should be* searched for first, before the standard set of directories.** @return colon-separated set of search directories** @hide*/@UnsupportedAppUsage@SystemApi(client = MODULE_LIBRARIES)public @NonNull String getLdLibraryPath() {StringBuilder result = new StringBuilder();for (File directory : pathList.getNativeLibraryDirectories()) {if (result.length() > 0) {result.append(':');}result.append(directory);}return result.toString();}// 。。。 。。。
}// https://android.googlesource.com/platform/libcore/+/master/dalvik/src/main/java/dalvik/system/DexPathList.java
// https://www.androidos.net.cn/android/9.0.0_r8/xref/libcore/dalvik/src/main/java/dalvik/system/DexPathList.java
public final class DexPathList {// 。。。 。。。/** List of native library path elements. */// Some applications rely on this field being an array or we'd use a final list here
    @UnsupportedAppUsage/* package visible for testing */ NativeLibraryElement[] nativeLibraryPathElements;/** List of application native library directories. */@UnsupportedAppUsageprivate final List<File> nativeLibraryDirectories;/** List of system native library directories. */@UnsupportedAppUsageprivate final List<File> systemNativeLibraryDirectories;// 。。。 。。。private List<File> getAllNativeLibraryDirectories() {List<File> allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories);allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories);return allNativeLibraryDirectories;}public DexPathList(ClassLoader definingContext, String librarySearchPath) {if (definingContext == null) {throw new NullPointerException("definingContext == null");}this.definingContext = definingContext;this.nativeLibraryDirectories = splitPaths(librarySearchPath, false);this.systemNativeLibraryDirectories =splitPaths(System.getProperty("java.library.path"), true); // 系统的 "java.library.path" 路径   /system/lib/; /vendor/lib/; /product/lib/this.nativeLibraryPathElements = makePathElements(getAllNativeLibraryDirectories());}DexPathList(ClassLoader definingContext, String dexPath,String librarySearchPath, File optimizedDirectory, boolean isTrusted) {if (definingContext == null) {throw new NullPointerException("definingContext == null");}if (dexPath == null) {throw new NullPointerException("dexPath == null");}if (optimizedDirectory != null) {if (!optimizedDirectory.exists())  {throw new IllegalArgumentException("optimizedDirectory doesn't exist: "+ optimizedDirectory);}if (!(optimizedDirectory.canRead()&& optimizedDirectory.canWrite())) {throw new IllegalArgumentException("optimizedDirectory not readable/writable: "+ optimizedDirectory);}}this.definingContext = definingContext;ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();// save dexPath for BaseDexClassLoaderthis.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,suppressedExceptions, definingContext, isTrusted);// Native libraries may exist in both the system and// application library paths, and we use this search order:////   1. This class loader's library path for application libraries (librarySearchPath)://   1.1. Native library directories//   1.2. Path to libraries in apk-files//   2. The VM's library path from the system property for system libraries//      also known as java.library.path//// This order was reversed prior to Gingerbread; see http://b/2933456.this.nativeLibraryDirectories = splitPaths(librarySearchPath, false);this.systemNativeLibraryDirectories =splitPaths(System.getProperty("java.library.path"), true);  // 系统的 "java.library.path" 路径   /system/lib/; /vendor/lib/; /product/lib/this.nativeLibraryPathElements = makePathElements(getAllNativeLibraryDirectories());if (suppressedExceptions.size() > 0) {this.dexElementsSuppressedExceptions =suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);} else {dexElementsSuppressedExceptions = null;}}/*** For BaseDexClassLoader.getLdLibraryPath.*/public List<File> getNativeLibraryDirectories() {return nativeLibraryDirectories;}public String findLibrary(String libraryName) {String fileName = System.mapLibraryName(libraryName);for (NativeLibraryElement element : nativeLibraryPathElements) {String path = element.findNativeLibrary(fileName);if (path != null) {return path;}}return null;}/*** Adds a collection of library paths from which to load native libraries. Paths can be absolute* native library directories (i.e. /data/app/foo/lib/arm64) or apk references (i.e.* /data/app/foo/base.apk!/lib/arm64).** Note: This method will attempt to dedupe elements.* Note: This method replaces the value of {@link #nativeLibraryPathElements}*/@UnsupportedAppUsagepublic void addNativePath(Collection<String> libPaths) {   // 添加新的查找路径  例如:/data/app/com.example.mygame-S72Tv4IGEow5LtbwxPQHpQ==/lib/arm64if (libPaths.isEmpty()) {return;}List<File> libFiles = new ArrayList<>(libPaths.size());for (String path : libPaths) {libFiles.add(new File(path));}ArrayList<NativeLibraryElement> newPaths =new ArrayList<>(nativeLibraryPathElements.length + libPaths.size());newPaths.addAll(Arrays.asList(nativeLibraryPathElements));for (NativeLibraryElement element : makePathElements(libFiles)) {if (!newPaths.contains(element)) {newPaths.add(element);}}nativeLibraryPathElements = newPaths.toArray(new NativeLibraryElement[newPaths.size()]);}// 。。。 。。。
}

image

 

在 Java 中,JVM 加载的是 .class 文件,而在 Android 中,Dalvik或ART虚拟机加载的是 dex 文件。

这里的 dex 文件不仅仅指 .dex 后缀的文件,而是指携带 classed.dex 项的任何文件(例如:jar / zip / apk)。

在 Android 中,Java 代码的编译产物是 dex 格式字节码,所以 Android 系统提供了 BaseDexClassLoader 类加载器,用于从 dex 文件中加载类。

PathClassLoader & DexClassLoader 是 BaseDexClassLoader 的子类,但它们都没有重写方法,所以主要的逻辑还是在 BaseDexClassLoader。 

 

UE5动态加载libUnreal.so

GameApplication.java

import android.app.Application;
import android.content.Context;
import android.content.res.Configuration;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.OnLifecycleEvent;
import androidx.lifecycle.ProcessLifecycleOwner;public class GameApplication extends Application implements LifecycleObserver {// 。。。 。。。private static Context context;// 。。。 。。。
    @Overridepublic void onCreate() {super.onCreate();GameApplication.context = getApplicationContext();// 。。。 。。。。
    }public static Context getAppContext() {return GameApplication.context;}// 。。。 。。。
}

 

GameActivity.java

import dalvik.system.BaseDexClassLoader;
import java.util.Collection;
import java.util.Set;public class GameActivity extends NativeActivity implements SurfaceHolder.Callback2,SensorEventListener,Logger.ILoggerCallback,ComponentCallbacks2
{// 。。。 。。。
    @Overridepublic ClassLoader getClassLoader() {ClassLoader baseClassLoader = super.getClassLoader();try {BaseDexClassLoader dexClassLoader = (BaseDexClassLoader) baseClassLoader;if (dexClassLoader.findLibrary("Unreal") == null) {Field pathListField = BaseDexClassLoader.class.getDeclaredField("pathList");pathListField.setAccessible(true);Object pathListObj = pathListField.get(dexClassLoader);Method addNativePathMethod = pathListObj.getClass().getMethod("addNativePath", Collection.class);String filesDir = getApplicationContext().getFilesDir().getAbsolutePath();Log.verbose("Adding '" + filesDir + "' to GameActivity BaseDexClassLoader");Collection<String> paths = Arrays.asList(filesDir);addNativePathMethod.invoke(pathListObj, paths);}} catch (Exception e) {Log.warn("Failed to add native library path due to " + e);}return baseClassLoader;}// 。。。 。。。static{// We need to decide on enabling memory tracing before loading native code.// It's not trivial to get a full command line before GameActivity is constructed, so fallback to a sentinel file.try{Context context = GameApplication.getAppContext();Bundle bundle = context.getPackageManager().getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA).metaData;boolean bShipping =bundle.containsKey("com.epicgames.unreal.GameActivity.BuildConfiguration") &&bundle.getString("com.epicgames.unreal.GameActivity.BuildConfiguration").equals("Shipping");if (!bShipping){ boolean bEnableFullMemoryTracing = new File(GameApplication.getAppContext().getFilesDir() + "/UEEnableMemoryTracing.txt").exists();boolean bEnableLightlMemoryTracing = new File(GameApplication.getAppContext().getFilesDir() + "/UEEnableMemoryTracingLight.txt").exists();if (bEnableFullMemoryTracing){Os.setenv("UEEnableMemoryTracing", "full", true);}else if (bEnableLightlMemoryTracing){Os.setenv("UEEnableMemoryTracing", "light", true);}}}catch (ErrnoException | NameNotFoundException e){Log.debug(e.toString());Log.debug("Cannot set \"UEEnableMemoryTracing\" environment variable");}if (!isUnderTest()){System.load(GameApplication.getAppContext().getFilesDir().getAbsolutePath() + "/libUnreal.so");}}
}

注1:上面从/data/data/com.example.mygame/files/libUnreal.so外部路径加载so文件,需要保证包中不要打入libUnreal.so,否则在JNI层还是会优先加载包中的libUnreal.so导致崩溃

注2:libUnreal.so要放在App的沙盒目录且设置为可执行,否则可能由于权限问题加载失败。可使用代码为libUnreal.so设置可执行权限

// 方式一:
File file = new File("/data/data/com.example.mygame/files/libUnreal.so");
boolean success = file.setExecutable(true); // 为所有用户添加可执行权限
// 如果只为“所有者”添加权限,使用 file.setExecutable(true, true); // 第1个参数:是否可执行; 第2个参数(可选):是否只对所有者设置(true 为只对 owner,false 为所有用户)

// 方式二:
import java.nio.file.*;
import java.nio.file.attribute.*;
import java.util.*;Path path = Paths.get("/data/data/com.example.mygame/files/libUnreal.so");
Set<PosixFilePermission> perms = Files.getPosixFilePermissions(path);
// 添加可执行权限
perms.add(PosixFilePermission.OWNER_EXECUTE);
perms.add(PosixFilePermission.GROUP_EXECUTE);
perms.add(PosixFilePermission.OTHERS_EXECUTE);
// 可按需添加其他权限
Files.setPosixFilePermissions(path, perms);// 方式三:
Path path = Paths.get("/data/data/com.example.mygame/files/libUnreal.so");
Set<PosixFilePermission> perms = PosixFilePermissions.fromString("rwxr-xr-x");
Files.setPosixFilePermissions(path, perms);

注3:打包脚本要去掉-archive参数

"cmd.exe" /c ""D:/UnrealEngine/Engine/Build/BatchFiles/RunUAT.bat"  -ScriptsForProject="D:/MyGame/MyGame.uproject" Turnkey -command=VerifySdk -platform=Android -UpdateIfNeeded -EditorIO -EditorIOPort=52576  -project="D:/MyGame/MyGame.uproject" BuildCookRun -nop4 -utf8output -nocompileeditor -skipbuildeditor -cook  -project="D:/MyGame/MyGame.uproject"  -unrealexe="D:\UnrealEngine\Engine\Binaries\Win64\UnrealEditor-Win64-DebugGame-Cmd.exe" -platform=Android  -cookflavor=ASTC -stage -archive -package -build -pak -iostore -compressed -prereqs -archivedirectory="D:/MyGame/Achived" -clientconfig=Development" -nocompile -nocompileuat

 

参考

调试Android项目 -- 减少迭代时间(bDontBundleLibrariesInAPK设置为true)

 

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

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

相关文章

2025 年 10 月不锈钢厨房设备厂家最新推荐,产能、专利、环保三维数据透视

不锈钢厨房设备凭借耐用、易清洁的特性,成为餐饮、酒店、食堂等场景的核心选择,但其产能稳定性、技术专利储备与环保合规性,直接影响采购方的供应保障与长期运营成本。据中国五金制品协会不锈钢制品分会 2025 年 10…

DINO版本进化

1.蒸馏 KD 与 自蒸馏 SD普通蒸馏,是有监督(软标签),老师是外部预训练的大模型自蒸馏,无监督,老师是 自己的历史版本EMA2.EMA机制有一个问题,如果学生和老师输出的内容是一致的,那学生什么也学不到。所以老师网…

2025 年 10 月餐饮厨房设备厂家最新推荐,实力品牌深度解析采购无忧之选!

餐饮厨房设备作为餐饮企业运营的核心基础设施,其质量与服务直接关系到食品安全、运营效率及成本控制。据中国烹饪协会 2025 年 10 月发布的《餐饮行业设备采购白皮书》显示,近一年餐饮企业因设备质量问题产生的额外支…

sometime some time sometimes

sometime is an unspecified point in timesome time is a period/span of timesometimes is an adverb about frequency英语国家也有自己的 的地得

基于深度学习神经网络协同过滤模型(NCF)的视频推荐体系

基于深度学习神经网络协同过滤模型(NCF)的视频推荐体系pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas&q…

关于容斥原理

简单的不说了,直接开题。 P1450 [HAOI2008] 硬币购物 题意 有 \(c1, c2, c3, c4\) 四种面值的硬币,询问多次,给你每个硬币最多有多少个,问凑出 \(S\) 面值的方案。 分析 考虑一个 \(f_i\) 是 i 号硬币不满足条件的…

可变字符串

可变字符串StringBuffer:可变长字符串,运行效率慢,线程安全StringBuilder:可变长字符串,运行效率吧快,线程不安全 package String; /*** StringBuffer和StrinBulider的使用* 和String的区别(1)比String效率高(…

给安卓设置背景色的时候保持默认按钮样式(关于使用setBackgroundColor导致丢失默认按钮样式的问题)

给安卓设置背景色的时候保持默认按钮样式(关于使用setBackgroundColor导致丢失默认按钮样式的问题)核心apiButton.setBackgroundTintList(ColorStateList);void initPatterns(LinearLayout PatternsRoot){for(int i=0;…

分片上传与断点续传实现详解

分片上传与断点续传实现详解 在现代Web应用中,用户经常需要上传大文件,如视频、压缩包等。传统的文件上传方式在面对大文件时容易出现超时、失败等问题,而且一旦上传中断就需要重新上传整个文件,浪费时间和带宽。为…

欧拉定理

欧拉定理 给定整数 \(a,m\),其中 \(\gcd(a,m)=1\),求证: \[a^{\phi(m)} \equiv 1 \pmod m \]其中 \(\phi(m)\) 是欧拉函数,表示 \(\le m\) 中与 \(m\) 互质的数的个数。 欧拉定理证明 考虑模 \(m\) 的一个简化剩余…

Kanass入门到实战(6) - 如何进行缺陷管理 - 指南

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

手把手在 Linux 上安装 Docker 与 Docker Compose(包含 Ubuntu、CentOS 等 11 个发行版)

从零开始:按命令实操在 11 个主流 Linux 发行版(含 Ubuntu、CentOS、Debian 等)上安装 Docker 与 Docker Compose。支持 11 种主流发行版:OpenCloudOS、Anolis OS (龙蜥)、Alinux (阿里云)、Fedora、Rocky Linux、…

2025 年 10 月展示柜厂家最新推荐,精准检测与稳定性能深度解析!

展示柜的稳定性能直接关系到零售商家的日常运营效率与商品陈列效果,为助力商家筛选出经过精准检测、性能可靠的展示柜厂家,2025 年 10 月,中国商业设施协会联合国家轻工业产品质量监督检测中心,开展展示柜行业专项…

数据处理方法汇总

1.层次化 K-means (Hierarchical K-means)不直接把数据分成k个簇,第一层先分k1个簇,第二层每个大簇分为k2个小簇,以此类推,形成聚类树2.平衡采样对每类样本按比例采样3.检索式筛选数据用分类模型,找到数据池和种…

2025 年 10 月展示柜厂家最新推荐,技术实力与市场口碑深度解析!

在零售行业竞争日益激烈的当下,展示柜不仅是商品陈列工具,更成为品牌竞争力的重要体现。为帮助商家精准识别具备核心技术与良好市场口碑的展示柜厂家,2025 年 10 月,中国零售业协会联合全国商业设施质量监督检验中…

一些疑问

1、是否要减肥、如何增肌,增重对增肌的影响,脂肪可以转换为肌肉吗,人在饥饿的时候会先消耗肌肉还是脂肪,怎么保证减肥的时候肌肉不会被消耗,如果要减肥的话饮食怎么搭配,减肥的时候也会瘦脸吗,如何瘦脸 2、为什…

2025 年 10 月外墙涂料厂家最新推荐,高性能与可靠性兼具的优质品牌

2025 年我国外墙涂料行业产品合格率虽提升至 89%,但仍有 11% 的产品存在耐候性差、易脱落等性能问题,给建筑安全与外观维护带来隐患。为帮助用户筛选高性能、高可靠的优质品牌,本次测评联合中国涂料工业协会,依据《…

2025 年 10 月外墙涂料厂家最新推荐,聚焦高端定制需求与全案交付能力

2025 年我国高端外墙涂料市场规模同比增长 12.3%,随着建筑外观个性化需求提升与工程交付标准升级,高端定制与全案交付能力成为采购核心考量。但市场中多数厂家定制能力不足、交付链条断裂,导致项目落地难。为解决这…

深度神经网络 —— 使用RNN循环神经网络进行手写数字识别分类

深度神经网络 —— 使用RNN循环神经网络进行手写数字识别分类代码: import torch import torchvision from torchvision import datasets, transforms #from torch.autograd import Variable import matplotlib.pyplo…

2025年10月长白山亲子酒店推荐榜:四季主题与温泉度假对比排行

带娃去长白山,家长最在意的是“孩子玩得安全、学得开心,大人也能真正放松”。10月正值秋色最盛,山顶初雪、林间红叶,是全年亲子摄影的黄金窗口;可山区昼夜温差大、景点分散,如果酒店本身没有体系化的亲子配套,家…