frameworks 之Zygote
frameworks 之Zygote
- Zygote.rc 解析
- Zygote 启动
- ZygoteInit.java
- Zygote.cpp
- Liunx fork
Zygote 中文意思为受精卵。 和其意思一样,该功能负责android系统孵化service 和 app 进程。
本文讲解Zygote的大概流程。涉及的相同的类,如下所示
- system/core/rootdir/init.zygote32.rc
- frameworks/base/cmds/app_process/app_main.cpp
- frameworks/base/core/jni/AndroidRuntime.cpp
- system/core/init/main.cpp
- frameworks/base/core/java/com/android/internal/os/ZygoteInit.java
- frameworks/base/core/java/com/android/internal/os/Zygote.java
- frameworks/base/core/jni/com_android_internal_os_Zygote.cpp
Zygote.rc 解析
启动init进程后,会解析 Zygote.rc文件。该文件位于 system/core/rootdir 文件夹下
其中第一行 zygote 表示进程名, /system/bin/app_process 表示要启动的模块名 ,–zygote --start-system-server 表示参数, class main 表示入口方法。
service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
class main
priority -20
user root
group root readproc reserved_disk
socket zygote stream 660 root system
socket usap_pool_primary stream 660 root system
onrestart exec_background - system system -- /system/bin/vdc volume abort_fuse
onrestart write /sys/power/state on
onrestart restart audioserver
onrestart restart cameraserver
onrestart restart media
onrestart restart netd
onrestart restart wificond
writepid /dev/cpuset/foreground/tasks
critical window=${zygote.critical_window.minute:-off} target=zygote-fatal
Zygote 启动
根据上面的rc文件 全局搜索 grep app_process ./ -rn
可以看到该模块名为app_process的位于 base/cmds/app_process 下。

跳转到该文件夹下 打开 app_main.cpp 文件,查看main 方法
main 方法前面是解析参数,并对变量 zygote, startSystemServer 设置为true, 通过 runtime.start 方法启动, start方法是在继承在 AndroidRuntime 类实现
int main(int argc, char* const argv[])
{// Parse runtime arguments. Stop at first unrecognized option.bool zygote = false;bool startSystemServer = false;bool application = false;String8 niceName;String8 className;// 将变量为true++i; // Skip unused "parent dir" argument.while (i < argc) {const char* arg = argv[i++];if (strcmp(arg, "--zygote") == 0) {zygote = true;niceName = ZYGOTE_NICE_NAME;} else if (strcmp(arg, "--start-system-server") == 0) {startSystemServer = true;} else if (strcmp(arg, "--application") == 0) {application = true;} else if (strncmp(arg, "--nice-name=", 12) == 0) {niceName.setTo(arg + 12);} else if (strncmp(arg, "--", 2) != 0) {className.setTo(arg);break;} else {--i;break;}}// zygote 为true 通过runtime启动,com.android.internal.os.ZygoteInit 为类名if (zygote) {runtime.start("com.android.internal.os.ZygoteInit", args, zygote);} else if (className) {runtime.start("com.android.internal.os.RuntimeInit", args, zygote);} else {fprintf(stderr, "Error: no class name or --zygote supplied.\n");app_usage();LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied.");}
}
通过 全局查找 grep “AndroidRuntime” ./ -rn 得到该类的位置 在 core/jni/AndroidRuntime.cpp 下

该方法前面大部分还是参数变量判断,关键通过 jmethodID startMeth = env->GetStaticMethodID(startClass, “main”,
“([Ljava/lang/String;)V”); 启动 对应的main方法。根据上一步传进来的参数。可以得到启动了 ZygoteInit.java 类
void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)
{/** We want to call main() with a String array with arguments in it.* At present we have two arguments, the class name and an option string.* Create an array to hold them.*/jclass stringClass;jobjectArray strArray;jstring classNameStr;// 省略// 加载传进来的类名,jni 加载对应的main方法char* slashClassName = toSlashClassName(className != NULL ? className : "");jclass startClass = env->FindClass(slashClassName);if (startClass == NULL) {ALOGE("JavaVM unable to locate class '%s'\n", slashClassName);/* keep going */} else {jmethodID startMeth = env->GetStaticMethodID(startClass, "main","([Ljava/lang/String;)V");if (startMeth == NULL) {ALOGE("JavaVM unable to find main() in '%s'\n", className);/* keep going */} else {env->CallStaticVoidMethod(startClass, startMeth, strArray);#if 0if (env->ExceptionCheck())threadExitUncaughtException(env);
#endif}}free(slashClassName);ALOGD("Shutting down VM\n");if (mJavaVM->DetachCurrentThread() != JNI_OK)ALOGW("Warning: unable to detach main thread\n");if (mJavaVM->DestroyJavaVM() != 0)ALOGW("Warning: VM did not shut down cleanly\n");
}
ZygoteInit.java
查看对应的main方法 ,main 里面主要的方法有如下
preload(bootTimingsTraceLog); 预加载了类,资源,opengl,so库等
初始化ZygoteServer
forkSystemServer 启动 SystemServer
runSelectLoop 循环等待消息
public static void main(String[] argv) {...// 初始化参数 startSystemServer 决定启动 systemServer 服务boolean startSystemServer = false;String zygoteSocketName = "zygote";String abiList = null;boolean enableLazyPreload = false;for (int i = 1; i < argv.length; i++) {if ("start-system-server".equals(argv[i])) {startSystemServer = true;} else if ("--enable-lazy-preload".equals(argv[i])) {enableLazyPreload = true;} else if (argv[i].startsWith(ABI_LIST_ARG)) {abiList = argv[i].substring(ABI_LIST_ARG.length());} else if (argv[i].startsWith(SOCKET_NAME_ARG)) {zygoteSocketName = argv[i].substring(SOCKET_NAME_ARG.length());} else {throw new RuntimeException("Unknown command line argument: " + argv[i]);}}...// In some configurations, we avoid preloading resources and classes eagerly.// In such cases, we will preload things prior to our first fork.if (!enableLazyPreload) {bootTimingsTraceLog.traceBegin("ZygotePreload");EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_START,SystemClock.uptimeMillis());// 加载类资源 和so preload(bootTimingsTraceLog);EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_END,SystemClock.uptimeMillis());bootTimingsTraceLog.traceEnd(); // ZygotePreload}...// 创建systemServiver 服务zygoteServer = new ZygoteServer(isPrimaryZygote);if (startSystemServer) {Runnable r = forkSystemServer(abiList, zygoteSocketName, zygoteServer);// {@code r == null} in the parent (zygote) process, and {@code r != null} in the// child (system_server) process.if (r != null) {r.run();return;}}...Log.i(TAG, "Accepting command socket connections");// 等待服务// The select loop returns early in the child process after a fork and// loops forever in the zygote.caller = zygoteServer.runSelectLoop(abiList);
}
其中 preload 里面的 preloadClasses 加载android所需的类 ,加载 preloaded-classes 文件 通过Class.forName 加载类。可通过find -name preloaded-classes 查看该文件的位置该文件通过编译时候拷贝到 system/ect目录下 。

static void preload(TimingsTraceLog bootTimingsTraceLog) {preloadClasses();preloadResources();nativePreloadAppProcessHALs();preloadSharedLibraries();preloadTextResources();
}private static final String PRELOADED_CLASSES = "/system/etc/preloaded-classes";private static void preloadClasses() {// 加载文件InputStream is;try {is = new FileInputStream(PRELOADED_CLASSES);} catch (FileNotFoundException e) {Log.e(TAG, "Couldn't find " + PRELOADED_CLASSES + ".");return;}...// 循环遍历 通过 Class.forName 加载类文件while ((line = br.readLine()) != null) {// Skip comments and blank lines.line = line.trim();if (line.startsWith("#") || line.equals("")) {continue;}Trace.traceBegin(Trace.TRACE_TAG_DALVIK, line);try {// Load and explicitly initialize the given class. Use// Class.forName(String, boolean, ClassLoader) to avoid repeated stack lookups// (to derive the caller's class-loader). Use true to force initialization, and// null for the boot classpath class-loader (could as well cache the// class-loader of this class in a variable).Class.forName(line, true, null);count++;} catch (ClassNotFoundException e) {if (line.contains("$$Lambda$")) {if (LOGGING_DEBUG) {missingLambdaCount++;}} else {Log.w(TAG, "Class not found for preloading: " + line);}} catch (UnsatisfiedLinkError e) {Log.w(TAG, "Problem preloading " + line + ": " + e);} catch (Throwable t) {Log.e(TAG, "Error preloading " + line + ".", t);if (t instanceof Error) {throw (Error) t;} else if (t instanceof RuntimeException) {throw (RuntimeException) t;} else {throw new RuntimeException(t);}}Trace.traceEnd(Trace.TRACE_TAG_DALVIK);}
}
runSelectLoop 里面是一个死循环,poll等待消息 如果没消息来 就会卡在这 ,如果第一次进来,调用 acceptCommandPeer,接着 acceptCommandPeer 又会调用 createNewConnection方法 创建 ZygoteConnection。
..// poll等待消息 如果没消息来 就会卡在这try {pollReturnValue = Os.poll(pollFDs, pollTimeoutMs);} catch (ErrnoException ex) {throw new RuntimeException("poll failed", ex);}...//如果第一次进来,调用 acceptCommandPeer,创建 ZygoteConnectionif (pollIndex == 0) {// Zygote server socketZygoteConnection newPeer = acceptCommandPeer(abiList);peers.add(newPeer);socketFDs.add(newPeer.getFileDescriptor());} else if (pollIndex < usapPoolEventFDIndex) {ZygoteConnection connection = peers.get(pollIndex);boolean multipleForksOK = !isUsapPoolEnabled()&& ZygoteHooks.isIndefiniteThreadSuspensionSafe();// 执行创建final Runnable command =connection.processCommand(this, multipleForksOK);}
processCommand 方法里面会调用 forkAndSpecialize 继续调用 nativeForkAndSpecialize 创建进程,而 nativeForkAndSpecialize又会调用 ForkCommon 创建。
if (parsedArgs.mInvokeWith != null || parsedArgs.mStartChildZygote|| !multipleOK || peer.getUid() != Process.SYSTEM_UID) {// Continue using old code for now. TODO: Handle these cases in the other path.// 创建进程pid = Zygote.forkAndSpecialize(parsedArgs.mUid, parsedArgs.mGid,parsedArgs.mGids, parsedArgs.mRuntimeFlags, rlimits,parsedArgs.mMountExternal, parsedArgs.mSeInfo, parsedArgs.mNiceName,fdsToClose, fdsToIgnore, parsedArgs.mStartChildZygote,parsedArgs.mInstructionSet, parsedArgs.mAppDataDir,parsedArgs.mIsTopApp, parsedArgs.mPkgDataInfoList,parsedArgs.mAllowlistedDataInfoList, parsedArgs.mBindMountAppDataDirs,parsedArgs.mBindMountAppStorageDirs);try {if (pid == 0) {// in childzygoteServer.setForkChild();zygoteServer.closeServerSocket();IoUtils.closeQuietly(serverPipeFd);serverPipeFd = null;return handleChildProc(parsedArgs, childPipeFd,parsedArgs.mStartChildZygote);} else {// In the parent. A pid < 0 indicates a failure and will be handled in// handleParentProc.IoUtils.closeQuietly(childPipeFd);childPipeFd = null;handleParentProc(pid, serverPipeFd);return null;}} finally {IoUtils.closeQuietly(childPipeFd);IoUtils.closeQuietly(serverPipeFd);}}
Zygote.cpp
forkSystemServer 会调用 Zygote.forkSystemServer 方法,而forkSystemServer 又会调用
nativeForkSystemServer 方法, 最终调用到 Zygote.cpp 里面的方法。直接查找 Zygote.cpp 可以看到该文件位于 frameworks/base/core/jni 。
/* Request to fork the system server process */pid = Zygote.forkSystemServer(parsedArgs.mUid, parsedArgs.mGid,parsedArgs.mGids,parsedArgs.mRuntimeFlags,null,parsedArgs.mPermittedCapabilities,parsedArgs.mEffectiveCapabilities);
static int forkSystemServer(int uid, int gid, int[] gids, int runtimeFlags,int[][] rlimits, long permittedCapabilities, long effectiveCapabilities) {ZygoteHooks.preFork();int pid = nativeForkSystemServer(uid, gid, gids, runtimeFlags, rlimits,permittedCapabilities, effectiveCapabilities);// Set the Java Language thread priority to the default value for new apps.Thread.currentThread().setPriority(Thread.NORM_PRIORITY);ZygoteHooks.postForkCommon();return pid;}
看到关键语句pid 可以看出该方法是fork 出进程id, 调用的是liunx 自带fork方法 孵化出进程,当返回的id为0时候 代表是新进程,可以看到会调用 ForkCommon 方法。
pid_t pid = zygote::ForkCommon(env, true,fds_to_close,fds_to_ignore,true);if (pid == 0) {// System server prcoess does not need data isolation so no need to// know pkg_data_info_list.SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits, permitted_capabilities,effective_capabilities, MOUNT_EXTERNAL_DEFAULT, nullptr, nullptr, true,false, nullptr, nullptr, /* is_top_app= */ false,/* pkg_data_info_list */ nullptr,/* allowlisted_data_info_list */ nullptr, false, false);}
查看 ForkCommon 方法。里面调用了 Fork方法 创建进程
// 创建进程
pid_t pid = fork();if (pid == 0) {if (is_priority_fork) {setpriority(PRIO_PROCESS, 0, PROCESS_PRIORITY_MAX);} else {setpriority(PRIO_PROCESS, 0, PROCESS_PRIORITY_MIN);}// The child process.PreApplicationInit();// Clean up any descriptors which must be closed immediatelyDetachDescriptors(env, fds_to_close, fail_fn);// Invalidate the entries in the USAP table.ClearUsapTable();// Re-open all remaining open file descriptors so that they aren't shared// with the zygote across a fork.gOpenFdTable->ReopenOrDetach(fail_fn);// Turn fdsan back on.android_fdsan_set_error_level(fdsan_error_level);// Reset the fd to the unsolicited zygote socketgSystemServerSocketFd = -1;} else {ALOGD("Forked child process %d", pid);}
Liunx fork
fork函数是 Liunx ,fork() 返回的pid pid等于0 表示三fork新进程执行 不等于0 原来的进程执行代码。新建forkTest.c文件,
touch forkTest.c 内容如下
#include <unistd.h>
#include <stdio.h>int main(void){printf("main current process pid == %d \n", getpid());// 创建进程int pid = fork();// 这里会分开2个线程 pid等于0 表示三fork新进程执行 不等于0 原来的进程执行代码if (pid == 0) {printf("fork newProgress child process pid = %d parent pid = %d \n", getpid(), getppid());} else {printf("this process pid = %d forkPid = %d parent pid = %d \n", getpid(),pid, getppid());}return 0;
}
然后执行 gcc forkTest.c -o forkTest 编译为二进制文件 ,在执行 ./forkTest 命令执行 查看打印
main current process pid == 7766
this process pid = 7766 forkPid = 7767 parent pid = 7144
fork newProgress child process pid = 7767 parent pid = 7766
相关文章:
frameworks 之Zygote
frameworks 之Zygote Zygote.rc 解析Zygote 启动ZygoteInit.javaZygote.cppLiunx fork Zygote 中文意思为受精卵。 和其意思一样,该功能负责android系统孵化service 和 app 进程。 本文讲解Zygote的大概流程。涉及的相同的类,如下所示 system/core/rootd…...
基于考研题库小程序V2.0实现倒计时功能板块和超时判错功能
V2.0 需求沟通 需求分析 计时模块 3.1.1、功能描述←计时模块用于做题过程中对每一题的作答进行30秒倒计时,超时直接判错,同时将总用时显示在界面上;记录每次做题的总用时。 3.1.2、接口描述←与判定模块的接口为超时判定,若单题用时超过 …...
idm站点抓取可以用来做什么 idm站点抓取能抓取本地网页吗 idm站点抓取怎么用 网络下载加速器
在下载工具众多且竞争激烈的市场中,Internet Download Manager(简称IDM)作为一款专业的下载加速软件,仍然能够赢得众多用户的青睐,这都要得益于它的强大的下载功能。我们在开始使用IDM的时候总是有很多疑问,…...
maven7——(重要,构建项目)maven项目构建(命令)
Maven的常用命令管理项目的生命周期 clean命令 清除编译产生的target文件夹内容,可以配合相应命令在cmd中使用,如mvn clean package, mvn clean test D:\工作\公司培训-4班\day20\day20\untitled1>mvn clean compile命令 该命令可以…...
容联云发布容犀大模型应用,重塑企业“营销服”|WAIC 2024
7月6日,在2024世界人工智能大会上,容联云成功举办主题为“数智聚合 产业向上”的生成式应用与大模型商业化实践论坛。 论坛上,容联云发布了容犀智能大模型应用升级,该系列应用包括容犀Agent Copilot、容犀Knowledge Copilot、容犀…...
Docker 安装字体文件
由于 Docker 容器的隔离性,与宿主机是独立的运行环境,如果需要用到宿主机的字体文件就需要进行安装。 例如在导出 PDF 文件时,如果缺少字体文件,就会产生乱码(常表现为中文变成方框)。 Docker 字体文件的安…...
C/C++ 移动追加内容到文件尾部。
1、通过C语言文件函数库 1.1、通过追加到尾部字符命令 FILE* f fopen(file_path.data(), "ab"); 1.2、不通过追加到尾部字符命令 FILE* f fopen(path, "rb"); if (NULL ! f) { fseek(f, 0, SEEK_END); } Unix 平台(Linux/Android/MacOS…...
ISO/OIS的七层模型②
OSI模型是一个分层的模型,每一个部分称为一层,每一层扮演固定的角色,互不干扰。OSI有7层,从上到下分别是: 一,每层功能 7.应用层(Application layer ):应用层功能&#x…...
美团到家平台业务探索
背景 到家业务发展已经近10年,目前最为火热的应该有美团到家、抖音到家等,这种极具挑战性的业务,值得学习和思考。 既然是服务平台化,那一定是兼容了多种业务以及多种模式。 挑战 订单、骑手规模大,供需匹配过程的…...
React -- useState状态更新异步特性——导致获取值为旧值的问题
useState状态异步更新 问题导致的原因解决办法进一步分析后续遇到的新问题 问题 const [isSelecting, setIsSelecting] useState(false);useEffect(() > {const handleKeyDown (event) > {if (event.key Escape) {if(isSelectingRef){//.......setIsSelecting(!isSele…...
哪款开放式耳机是2024年最值得购买的?五大品质好物揭秘
相比于入耳式耳机压耳、堵耳,佩戴不稳固等缺陷,开放式耳机的佩戴舒适性和安全性都更胜一筹,这几年成为了越来越多年轻人的“音乐搭子”。面对市面上各式各样的开放式耳机,相信大家在挑选上就得下大把功夫,选择上也有困…...
深圳天童美语:小暑习俗知多少
小暑已至,炎炎夏日正当时。在这个充满生机的节气里,除了我们熟悉的吃冰、游泳等消暑方式,还有许多有趣且富含文化内涵的小暑习俗。今天,深圳天童美语就带你一起解锁这些习俗,感受那份独特的夏日风情! …...
递归参数中递增运算符的使用
backtrack(k,n,sum,i1); backtrack(k,n,sum,i); 在 C 中,递增运算符 i 和表达式 i1 之间有显著的区别: i 是后置递增运算符,表示先使用 i 的当前值,然后将 i 加 1。i1 是一个简单的算术运算,返回 i 的当前值加 1&…...
Python功能制作之获取CSDN所有发布文章的对应数据
大家好,今天我要分享的是一个实用的Python脚本,它可以帮助你批量获取CSDN博客上所有发布文章的相关数据,并将这些数据保存到Excel文件中。此外,脚本还会为每篇文章获取一个质量分,并将这个分数也记录在Excel中。让我们…...
Backend - C# 基础知识
目录 一、程序结构 (一)内容 1. 命名空间声明 Namespace 2. 一个 class 类 3. class 方法(类方法) 4. class 属性 5. 一个 main 方法(程序入口) 6. 语句&表达式 7. 注释 (二)举例…...
HTML5新增的input元素类型:number、range、email、color、date等
HTML5 大幅度地增加与改良了 input 元素的种类,可以简单地使用这些元素来实现 HTML5 之前需要使用 JavaScript 才能实现的许多功能。 到目前为止,大部分浏览器都支持 input 元素的种类。对于不支持新增 input 元素的浏览器,input 元素被统一…...
00 Debian字符界面如何支持中文
作者:网络傅老师 特别提示:未经作者允许,不得转载任何内容。违者必究! Debian字符界面如何支持中文 《傅老师Debian知识库系列之00》——原创 前言 傅老师Debian知识库特点: 1、拆解Debian实用技能; 2、…...
以太网中的各种帧结构
帧结构(Ethernet Frame Structure)介绍 以太网信号帧结构(Ethernet Signal Frame Structure),有被称为以太网帧结构,一般可以分为两类 —— 数据帧和管理帧。 按照 IEEE 802.3,ISO/IEC8803-3 …...
C++入门基础题:数组元素逆序(C++版互换方式)
1.题目: 数组元素逆置案例描述: 请声明一个5个元素的数组,并且将元素逆置. (如原数组元素为:1,3,2,5,4;逆置后输出结果为:4,5,2,3,1) 2.图解思路: 3.代码演示: #include<iostream>using namespace std;int main(){int a…...
3款自己电脑就可以运行AI LLM的项目
AnythingLLM、LocalGPT和PrivateGPT都是与大语言模型(LLM)相关的项目,它们允许用户在本地环境中与文档进行交互,但它们在实现方式和特点上存在一些差异。AnythingLLM使用Pinecone和ChromaDB来处理矢量嵌入,并使用OpenA…...
Docker 离线安装指南
参考文章 1、确认操作系统类型及内核版本 Docker依赖于Linux内核的一些特性,不同版本的Docker对内核版本有不同要求。例如,Docker 17.06及之后的版本通常需要Linux内核3.10及以上版本,Docker17.09及更高版本对应Linux内核4.9.x及更高版本。…...
【OSG学习笔记】Day 18: 碰撞检测与物理交互
物理引擎(Physics Engine) 物理引擎 是一种通过计算机模拟物理规律(如力学、碰撞、重力、流体动力学等)的软件工具或库。 它的核心目标是在虚拟环境中逼真地模拟物体的运动和交互,广泛应用于 游戏开发、动画制作、虚…...
从零实现富文本编辑器#5-编辑器选区模型的状态结构表达
先前我们总结了浏览器选区模型的交互策略,并且实现了基本的选区操作,还调研了自绘选区的实现。那么相对的,我们还需要设计编辑器的选区表达,也可以称为模型选区。编辑器中应用变更时的操作范围,就是以模型选区为基准来…...
如何将联系人从 iPhone 转移到 Android
从 iPhone 换到 Android 手机时,你可能需要保留重要的数据,例如通讯录。好在,将通讯录从 iPhone 转移到 Android 手机非常简单,你可以从本文中学习 6 种可靠的方法,确保随时保持连接,不错过任何信息。 第 1…...
华为云Flexus+DeepSeek征文|DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建
华为云FlexusDeepSeek征文|DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建 前言 如今大模型其性能出色,华为云 ModelArts Studio_MaaS大模型即服务平台华为云内置了大模型,能助力我们轻松驾驭 DeepSeek-V3/R1,本文中将分享如何…...
论文笔记——相干体技术在裂缝预测中的应用研究
目录 相关地震知识补充地震数据的认识地震几何属性 相干体算法定义基本原理第一代相干体技术:基于互相关的相干体技术(Correlation)第二代相干体技术:基于相似的相干体技术(Semblance)基于多道相似的相干体…...
Java毕业设计:WML信息查询与后端信息发布系统开发
JAVAWML信息查询与后端信息发布系统实现 一、系统概述 本系统基于Java和WML(无线标记语言)技术开发,实现了移动设备上的信息查询与后端信息发布功能。系统采用B/S架构,服务器端使用Java Servlet处理请求,数据库采用MySQL存储信息࿰…...
华为OD机考-机房布局
import java.util.*;public class DemoTest5 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseSystem.out.println(solve(in.nextLine()));}}priv…...
学习一下用鸿蒙DevEco Studio HarmonyOS5实现百度地图
在鸿蒙(HarmonyOS5)中集成百度地图,可以通过以下步骤和技术方案实现。结合鸿蒙的分布式能力和百度地图的API,可以构建跨设备的定位、导航和地图展示功能。 1. 鸿蒙环境准备 开发工具:下载安装 De…...
【Veristand】Veristand环境安装教程-Linux RT / Windows
首先声明,此教程是针对Simulink编译模型并导入Veristand中编写的,同时需要注意的是老用户编译可能用的是Veristand Model Framework,那个是历史版本,且NI不会再维护,新版本编译支持为VeriStand Model Generation Suppo…...
