flutter 专题 五十六 Google 2020开发者大会Flutter专题

由于疫情的原因,今年的Google 开发者大会 (Google Developer Summit) 在线上举行,本次大会以“代码不止”为主题,全面介绍了产品更新以及一系列面向本地开发者的技术支持内容。我比较关注的是移动开发,在本次大会上,关于Flutter 主题的演讲主要从 Flutter 性能方面优化和新功能进行展开。

作为全球增长速度第二的开源项目,越来越多国内开发者使用 Flutter 实现跨平台开发,包括腾讯英语君团队、阿里闲鱼团队等等。其在 开放性上的进步,得益于开源社区、生态建设、对 Web 的支持。
在这里插入图片描述
有兴趣的读者可以通过Google Developer官网进行学习:Google Developer官网

下面我们就来看一下这些新功能和性能上的优化。

Flutter 性能优化

首先为我们带来演讲的是Google 软件工程师李宇骞,他是Flutter 团队的一位软件工程师,主要专注于提升其性能。下面是具体的演讲内容:

2019 下半年,Flutter 团队共收到 23 个量化的性能提升;2020 上半年,Flutter 团队共收到 27 个量化的性能提升。2020 上半年 Flutter 团队共收到来自 78 位开发者的 49 个性能改进。

工具的性能十分重要,性能测试也同样至关重要,拥有良好的性能测试可以:

  • 快速重现问题;
  • 迭代和验证解决方案;
  • 提供数据,激励进一步的工作并防止倒退。

通常,能耗与渲染速度相关,每一帧渲染时间越长则能耗就越高,但能耗并不能衡量渲染速度,因为在某些情况下渲染速度快也可能会导致能耗升高,渲染速度慢也可能不耗能。

CPU 上运行时间虽然短,但由于新的算法利用了更多的 GPU 核心,所以 GPU 能耗反而增加;有些 CPU 上的任务被别的 I/O 或 GPU 任务阻塞,进行了长时间的等待,而等待的时间内并无过多能耗。

因此,在速度之外增加能耗测试是十分必要的。因为 Flutter 团队在 GitHub 上收到的大部分能耗问题都和 iOS 相关,所以此次 Flutter 首先加入了 iOS 的能耗测试,Android 的能耗测试工具会于后续加入。

开发者可以使用 Flutter Gallery App 在 Timeline 中查看 CPU/GPU 的使用率,也可以用集成测试自动检测 CPU/GPU 的使用率。
在这里插入图片描述

Flutter 还新加入了 SkSL 着色器编译预热功能,来帮助开发者消除着色器编译卡顿。如果一个 Flutter 程序第一次渲染某类动画时出现明显的卡顿,但是之后渲染这些动画时,卡顿完全消失,那么这就很可能是着色器编译卡顿。开发者可以使用 --trace-skia,然后检查 Timeline 来确认是否为着色器卡顿。
在这里插入图片描述

值得一提的是,SkSL 可以实现自动化生成与测试,这对于需要持续更新的 Flutter App 来说,可以节省很多的人力。

内存和包体积的测试工具

接下来,是由Flutter 用户体验研究员侯悠扬带来的测试工具专题。侯悠扬于 2017 年加入 Google,并于 2019 年加入 Flutter 团队。她是 Flutter 团队一名用户体验研究员,关注提升 Flutter 产品和开发工具的程序员体验。

此次,Flutter 团队更新了Dart开发工具。Dart 开发工具是面向 Flutter 和 Dart 开发人员的工具套件,包括如下一些小工具:

  • 布局检查(Inspector)
  • 性能调试(Performance)
  • 内存调试(Memory)
  • 网络调试(Network)
  • 包体积调试(App Size)
  • 调试器(Debugger)
  • 日志(Logging)

连接上设备然后运行Flutter应用,点击Android Studio底部工具栏中的【Open DevTools】按钮即可开启调试功能。

内存调试器功能

Flutter的内存调试器提供如下功能:

  • 事件窗格(Dart 和 Android 内存)
  • 手动和自动快照(snapshot)和垃圾回收(GC)
  • 内存分析
  • 内存堆分配累加器(Heap Allocation Accumulators)
  • 通过命令行界面将内存统计信息到处到 JSON 文件

内存测试

内存测试提供如下功能:

  • 通过 ADB 交互直接进行内存测试
  • Dart 开发工具内存测试
  • iOS 内存测试

更多信息可以通过这篇由 Flutter 工程师撰写的文章进行了解:怎么进行Flutter内存测试

包体积调试器功能

包体积调试器提供如下功能:

  • 可视化了应用程序的总大小,包括功能级别的 Dart AOT 快照;
  • 分析快照和应用包(APK,IPA 等);
  • 分析快照或应用程序包(APK,IPA 等)的差异;
  • 查看软件包级别的应用大小归因数据。

Pigeon与Flutter混合开发

什么是Pigeon

在早期的hybird开发模式中,前端和Native交互时需要native双端为JS提供接口。这种情况下如何规范命名,参数等就成了一个问题,如果单独维护一份协议文件,三端依照协议文件进行开发,很容易出现协议更改后,没有及时同步,又或者在实际开发过程没有按照规范,可能导致各种意外情况。

同样,在Flutter插件包的开发中,因为涉及到Native双端代码开发能力,Dart侧暴露统一的接口给使用者,也会出现同样的问题,此时Pigeon应运而生,Pigeon是Flutter官方推荐插件管理工具,可以使用来解决和优化 Native 插件开发上 platform channel 相关的问题。

Flutter官方提供的Pigeon插件,通过dart入口,生成双端通用的模板代码,Native部分只需通过重写模板内的接口,无需关心methodChannel部分的具体实现,入参,出参也均通过生成的模板代码进行约束。接口新增,或者参数修改,只需要在dart侧更新协议文件,生成双端模板,即可达到同步更新,有效的避免了参数修改,参数新增带来的双端代码不同步的问题,下面是Pigeon工作原理示意图。
在这里插入图片描述

下面是Pigeon给出的示例:
在这里插入图片描述

可以看到接入Pigeon后整体代码简洁了不少,而且规范了类型定义。

Pigeon接入

接下来我们看一下如何从零接入Pigeon。截止目前,Pigeon已经发布了0.1.15版本,如下图所示。
在这里插入图片描述
首先,新建一个名为testpigeon的Flutter项目,打开项目的pubspec.yaml文件,并添加如下依赖代码。

dependencies:pigeon: ^0.1.15

然后,按照官方的要求在项目目录下新建一个pigeons目录,作为存放dart侧的入口文件,内容为接口、参数、返回值的定义等,以及后面通过pigeon的命令,生产native端代码。接下来,新建一个message.dart 文件,并添加如下。

import 'package:pigeon/pigeon.dart';class SearchRequest {String query;
}class SearchReply {String result;
}@HostApi()
abstract class Api {SearchReply search(SearchRequest request);
}

在上面的message.dart 文件中,通过 @HostApi() 注解标示了通信对象和接口,之后我们只需要执行如下命令,就可以生成对应代码到工程中。

flutter pub run pigeon  --input pigeons/message.dart

其实上面的命令是下面命令的简写方式:

flutter pub run pigeon  --input pigeons/message.dart  --dart_out lib/pigeon.dart  --objc_header_out ios/Runner/pigeon.h --objc_source_out ios/Runner/pigeon.m --java_out android/app/src/main/java/Pigeon.java --java_package "com.xzh.testpigeon"

命令的参数的含义如下:

  • –input:引入了我们创建的 message.dart 文件;
  • –dart_out:输出了 dart 模板文件;
  • –objc_header_out 和 --objc_source_out 输出了 object-c 文件;
  • –java_out 输出了 java 文件;

命令执行后 dart 文件输出到 lib 目录下, object-c 文件输出到了 ios/Runner 目录下,java 文件输出到指定的 com.xzh.testpigeon" 包名路径下,之后就可以开始正式接入。然后我们分别使用Android Studio和Xcode打开原生工程代码。

Android 工程代码

使用Android Studio打开Flutter项目的原生Android工程,生成的代码如下:

// Autogenerated from Pigeon (v0.1.15), do not edit directly.
// See also: https://pub.dev/packages/pigeonpackage com.xzh.testpigeon;import io.flutter.plugin.common.BasicMessageChannel;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.StandardMessageCodec;
import java.util.ArrayList;
import java.util.HashMap;/** Generated class from Pigeon. */
@SuppressWarnings("unused")
public class Pigeon {/** Generated class from Pigeon that represents data sent in messages. */public static class SearchReply {private String result;public String getResult() { return result; }public void setResult(String setterArg) { this.result = setterArg; }HashMap toMap() {HashMap<String, Object> toMapResult = new HashMap<>();toMapResult.put("result", result);return toMapResult;}static SearchReply fromMap(HashMap map) {SearchReply fromMapResult = new SearchReply();Object result = map.get("result");fromMapResult.result = (String)result;return fromMapResult;}}/** Generated class from Pigeon that represents data sent in messages. */public static class SearchRequest {private String query;public String getQuery() { return query; }public void setQuery(String setterArg) { this.query = setterArg; }HashMap toMap() {HashMap<String, Object> toMapResult = new HashMap<>();toMapResult.put("query", query);return toMapResult;}static SearchRequest fromMap(HashMap map) {SearchRequest fromMapResult = new SearchRequest();Object query = map.get("query");fromMapResult.query = (String)query;return fromMapResult;}}/** Generated interface from Pigeon that represents a handler of messages from Flutter.*/public interface Api {SearchReply search(SearchRequest arg);/** Sets up an instance of `Api` to handle messages through the `binaryMessenger` */static void setup(BinaryMessenger binaryMessenger, Api api) {{BasicMessageChannel<Object> channel =new BasicMessageChannel<>(binaryMessenger, "dev.flutter.pigeon.Api.search", new StandardMessageCodec());if (api != null) {channel.setMessageHandler((message, reply) -> {HashMap<String, HashMap> wrapped = new HashMap<>();try {@SuppressWarnings("ConstantConditions")SearchRequest input = SearchRequest.fromMap((HashMap)message);SearchReply output = api.search(input);wrapped.put("result", output.toMap());}catch (Exception exception) {wrapped.put("error", wrapError(exception));}reply.reply(wrapped);});} else {channel.setMessageHandler(null);}}}}private static HashMap wrapError(Exception exception) {HashMap<String, Object> errorMap = new HashMap<>();errorMap.put("message", exception.toString());errorMap.put("code", exception.getClass().getSimpleName());errorMap.put("details", null);return errorMap;}
}

上面生成的 Pigeon.java 代码中包含了 Api 接口用于开发者实现交互逻辑,同时开发者可以通过 SearchRequest 获取 dart 发送过来的请求,通过 SearchReply 返回数据给 dart 。然后,还需要在Android的入口文件MainActivity 中实现 Api 接口来完成数据交互,代码如下。

public class MainActivity extends FlutterActivity {private class MyApi implements Pigeon.Api {@Overridepublic Pigeon.SearchReply search(Pigeon.SearchRequest request) {Pigeon.SearchReply reply = new Pigeon.SearchReply();reply.setResult(String.format("Hi %s!", request.getQuery()));return reply;}}@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);GeneratedPluginRegistrant.registerWith(this);Pigeon.Api.setup(getFlutterView(), new MyApi());}
}

首先,我们继承 Pigeon.Api 实现了 MyApi 对象,然后在 search() 方法中通过 request.getQuery() 获取 dart 的请求数据,并且通过 Pigeon.SearchReply 的 setResult 返回 数据给dart 端,最后通过Pigeon.Api.setup(getFlutterView(), new MyApi())启动。

iOS

使用Xcode打开Flutter项目的iOS工程,把生成的 pigeon.h 和 pigeon.m 文件 link 到 Xcode 工程里,之后如下代码所示在 AppDelegate.h 引入 Api 协议。

#import <Flutter/Flutter.h>
#import <UIKit/UIKit.h>
#import "pigeon.h"@interface AppDelegate : FlutterAppDelegate<Api>@end

接下来,在 AppDelegate.m 中实现 search 接口,并在收到的 dart 消息后基于回复,最后调用 ApiSetup()方法将完成注册。

#include "AppDelegate.h"
#include "GeneratedPluginRegistrant.h"@implementation AppDelegate- (BOOL)application:(UIApplication *)applicationdidFinishLaunchingWithOptions:(NSDictionary *)launchOptions {[GeneratedPluginRegistrant registerWithRegistry:self];// Override point for customization after application launch.FlutterViewController* controller =(FlutterViewController*)self.window.rootViewController;ApiSetup(controller.binaryMessenger, self);return [super application:application didFinishLaunchingWithOptions:launchOptions];
}-(SearchReply *)search:(SearchRequest*)input error:(FlutterError **)error {SearchReply* result = [[SearchReply alloc] init];result.result  = [NSString stringWithFormat:@"%s%@","Hi ",input.query];return result;
}@end

Dart测试

最后我们在 Dart 代码中新建一个测试的代码,如下所示。

import 'pigeon.dart';void main() {testWidgets("test pigeon", (WidgetTester tester) async {SearchRequest request = SearchRequest()..query = "Aaron";Api api = Api();SearchReply reply = await api.search(request);expect(reply.result, equals("Hi Aaron!"));});
}

Flutter 在阿里巴巴的应用

首先,主持人为我们介绍了Flutter的历史,介绍围绕美观、高效、流程和开放等几个方面来介绍Flutter。
在这里插入图片描述
接下来,阿里巴巴的无线技术专家门柳介绍Flutter在阿里巴巴的应用,闲鱼是阿里巴巴Flutter技术实践的先驱,也是国内最早尝试Flutter技术的大型互联网公司,而阿里巴巴旗下的淘宝也不甘示弱,也在某些模块结成Flutter,不过大多是业务级别的模块,而没有像闲鱼那样大规模使用。我们可以从下图看到Flutter在阿里巴巴的使用情况。

在这里插入图片描述
那为什么,这么多的移动应用开始使用Flutter来进行开发呢?首先,让我们来了解下跨平台技术的发展历程。
在这里插入图片描述
可以发现,移动跨平台开发经历了大约四个阶段:

  • 早期的WebView加载方案
  • 原生API桥接的Hybrid方案
  • 原生渲染方案(Web语法+原生UI)
  • 自绘渲染(独立布局/渲染)

而Flutter就是采用的自绘渲染方案,有兴趣的童鞋可以研究以下Flutter的架构。为什么选择Flutter进行跨平台应用开发呢,下面是Flutter所具有的一些优势:
在这里插入图片描述
不过,Flutter也不是万能的,Flutter目前处于快速迭代的阶段,所以保险起见,我们只在一些常规的业务开发和模块化的UI界面开发和部分游戏中使用Flutter。
在这里插入图片描述
总结起来,就是在一些富交互类应用和新型的应用中使用Flutter,对于视频、直播等渲染要求高的则继续使用原生进行开发。

那使用Flutter进行应用开发时,有哪些经验和问题需要注意呢?下图显示了阿里巴巴在使用Flutter进行应用开发时遇到的一些问题,大家使用时需要规避。
在这里插入图片描述
首先遇到的问题是,由于Flutter使用的是Dart进行开发,无疑增加了开发者的学习成本。其次,对于大型应用来说,如何保证代码质量,如何在多个平台运行自动化测试脚本也是一个问题;并且由于Flutter作为一门新的技术,如何快速的将老得业务迁移过来也是大家需要考虑的问题。总结一下,就是调试、测试、状态管理、缓和导航栈管理、跨平台兼容以及如何寻找解决方案的问题。
在这里插入图片描述
尽管Flutter已经提供了很多的工具,但是如何将它融入到阿里巴巴的客户端开发工作流中,是大家需要考虑的问题。
在这里插入图片描述
首先,为了提升开发效率,降低初期的接入成本,我们将Flutter Toolkit融入到Alibab DevOps工作流中,并自研了一些工具、打包和发布平台以及搭建调试环境。接下来,我们基于现存的技术积累,研发了一些中间件。
在这里插入图片描述
下面来看一个实例,即如何解决多图列表页面的内存占用问题。这类问题的特征如下:

  • 页面很长,图片很多,首次加载时间很长
  • 大量图片同时加载并生成纹理,内存飙升
  • Sliver中每项Cell拆分粒度很大,单个Cell占用多屏,难以回收
    在这里插入图片描述
    对于列表Flutter列表内存回收的问题,大家可以阅读 细化 Flutter List 内存回收,解决大 Cell 问题这篇文章。

对于上面的多图长列表的内存问题,我们可以从以下几个方面着手进行优化:

  • 拆分Cell,使每一项变得更小
  • 根据坐标判断图片是否在屏幕内,进而进行图片的懒加载和回收
  • 提前获取图片的宽高大小,减少布局和重绘
  • 以图片为单位进行纹理回收,而不是Sliver中的每项Cell为单位
  • 外接原生图片库,实现共享本地缓存

在这里插入图片描述
最后,我们来看一下Flutter在阿里巴巴的体系化建设。首先,Flutter的体系化建设主要从基础能力建设、研发平台和可持续迭代等几个方面着手。
在这里插入图片描述
下面是Flutter在阿里巴巴平台建设的具体的一些方案。
在这里插入图片描述
目前,Flutter在阿里巴巴已经经过了大规模的应用,并且我们自己的技术体系建设也在稳步推荐中,后面会将建设的一些成果通过社区分享出来。

附: Google 开发者大会

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

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

相关文章

开源模型应用落地-qwen模型小试-Qwen3-8B-快速体验-pipeline方式(二)

一、前言 阿里云最新推出的 Qwen3-8B 大语言模型,作为国内首个集成“快思考”与“慢思考”能力的混合推理模型,凭借其 80 亿参数规模及 128K 超长上下文支持,正在重塑 AI 应用边界。该模型既可通过轻量化“快思考”实现低算力秒级响应,也能在复杂任务中激活深度推理模式,以…

「动态规划::背包」01背包 / AcWing 2(C++)

概述 AcWing 2&#xff1a; 有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。 第 i 件物品的体积是 v[i]&#xff0c;价值是 w[i]。 求解将哪些物品装入背包&#xff0c;可使这些物品的总体积不超过背包容量&#xff0c;且总价值最大。 输出最大价值。 输入格式 第一…

Java 中的 设计模式详解

一&#xff1a;设计模式概述 &#xff08;1&#xff09;概述 &#xff08;2&#xff09;分类 创建型 行为型 结构型 二&#xff1a;软件设计模式 2.1 开闭原则 &#xff08;1&#xff09;定义 在程序需要进行拓展的时候&#xff0c;不能修改原有代码 使用到接口和抽象类&#x…

阿里qiankun微服务搭建

主服务 chat vue3 ts vite 子服务 ppt react 18 vite 子服务 agent 主服务 npm i vite-plugin-qiankun mian.ts import ./style/base.scss import virtual:svg-icons-register import { createApp } from vue import { createPinia } from piniaimport App from ./App.vue im…

安装WSL2,配置Ubuntu图像化界面

目录 一、前言二、安装WSL三、安装图像化界面四、参考 一、前言 Windows 子系统下的 Linux 子系统&#xff08;WSL&#xff0c;Windows Subsystem for Linux&#xff09;是微软推出的一项功能&#xff0c;允许用户在 Windows 系统中原生运行 Linux 环境&#xff0c;无需安装虚…

图像畸变-径向切向畸变实时图像RTSP推流

实验环境 注意&#xff1a;ffmpeg进程stdin写入两张图片的时间间隔不能太长&#xff0c;否则mediamtx会出现对应的推流session超时退出。 实验效果 全部代码 my_util.py #进度条 import os import sys import time import shutil import logging import time from datetime i…

Redis Sentinel 和 Redis Cluster 各自的原理、优缺点及适用场景是什么?

我们来详细分析下 Redis Sentinel (哨兵) 和 Redis Cluster (集群) 这两种方案的原理和使用场景。 Redis Sentinel (哨兵) 原理: Sentinel 本身是一个或一组独立于 Redis 数据节点的进程。它的核心职责是监控一个 Redis 主从复制 (Master-Slave) 架构。多个 Sentinel 进程协同…

基于机器学习的电影票房预测

目录 摘 要(完整下载链接附在文末) Abstract 1 绪 论 1.1 研究背景概述 1.2 国内外相关领域研究进展 1.3 电影票房预测技术概览 1.3.1 利用人口统计学特征的方法 1.3.2 基于机器学习的预测模型 2 机器学习相关理论介绍与分析 2.1 机器学习算法理论 2.1.1卷积…

SVMSPro平台获取HTTP-FLV规则

SVMSPro平台获取HTTP-FLV规则 HTTP-FLV的服务端口为&#xff1a;53372&#xff0c;如需要公网访问需要开启这个端口 这里讲的是如何获取长效URL&#xff0c;短效&#xff08;时效性&#xff09;URL也支持&#xff0c;下回讲 一、如何获取HTTP-FLV实时流视频 http://host:po…

ARM架构的微控制器总线矩阵

在 ARM 架构的微控制器&#xff08;MCU&#xff09;中&#xff0c;总线矩阵&#xff08;Bus Matrix&#xff09; 是总线系统的核心互连结构&#xff0c;负责协调多个主设备&#xff08;如 CPU、DMA、以太网控制器等&#xff09;对多个从设备&#xff08;如 Flash、SRAM、外设等…

AI赋能金融:智能投顾、风控与反欺诈的未来

AI赋能金融&#xff1a;智能投顾、风控与反欺诈的未来 系统化学习人工智能网站&#xff08;收藏&#xff09;&#xff1a;https://www.captainbed.cn/flu 文章目录 AI赋能金融&#xff1a;智能投顾、风控与反欺诈的未来摘要引言一、智能投顾&#xff1a;从经验驱动到人机协同…

【机器学习】朴素贝叶斯

目录 一、朴素贝叶斯的算法原理 1.1 定义 1.2 贝叶斯定理 1.3 条件独立性假设 二、朴素贝叶斯算法的几种常见类型 2.1 高斯朴素贝叶斯 (Gaussian Naive Bayes) 【训练阶段】 - 从数据中学习模型参数 【预测阶段】 - 对新样本 Xnew​ 进行分类 2. 2 多项式朴素贝叶斯 (…

鸿蒙 ArkTS 组件 通用事件 通用属性 速查表

ArkTS 组件 组件 通用事件 速查表 通用事件事件名称简要说明点击事件onClick(event: Callback<ClickEvent>, distanceThreshold: number): T相较于原有 onClick 接口&#xff0c;新增 distanceThreshold 参数作为点击事件移动阈值&#xff0c;当手指的移动距离超出所设…

Java云原生+quarkus

一、Java如何实现云原生应用&#xff1f; 传统的 Java 框架&#xff08;如 Spring Boot&#xff09;虽然功能强大&#xff0c;但在云原生场景下可能显得笨重。以下是一些更适合云原生的轻量级框架&#xff1a; Quarkus(推荐) 专为云原生和 Kubernetes 设计的 Java 框架。支持…

C语言教程(二十三):C 语言强制类型转换详解

一、强制类型转换的概念 强制类型转换是指在程序中手动将一个数据类型的值转换为另一种数据类型。在某些情况下,编译器可能不会自动进行类型转换,或者自动转换的结果不符合我们的预期,这时就需要使用强制类型转换来明确指定要进行的类型转换。 二、强制类型转换的语法 强制类…

Spring Boot × K8s 监控实战-集成 Prometheus 与 Grafana

在微服务架构中&#xff0c;应用的可观测性至关重要。Kubernetes 已成为容器化部署的标准&#xff0c;但其自身的监控能力有限&#xff0c;需要与其他工具集成才能实现详细的运行数据采集与分析。 本文将通过 Spring Boot Kubernetes Prometheus Grafana 实战&#xff0c;打…

phpstudy修改Apache端口号

1. 修改Listen.conf文件 本地phpstudy安装目录&#xff1a; 2.其他问题 ① 修改httpd.conf不起作用 ② 直接通过控制面板配置好像有延迟缓存

(done) 吴恩达版提示词工程 6. 转换 (翻译,通用翻译,语气风格变换,文本格式转换,拼写检查和语法检查)

视频&#xff1a;https://www.bilibili.com/video/BV1Z14y1Z7LJ/?spm_id_from333.337.search-card.all.click&vd_source7a1a0bc74158c6993c7355c5490fc600 别人的笔记&#xff1a;https://zhuanlan.zhihu.com/p/626966526 6. 转换任务&#xff08;Transforming&#xff0…

什么是静态住宅ip,跨境电商为什么要用静态住宅ip

在数字时代&#xff0c;IP地址不仅是设备联网的“ID”&#xff0c;更是跨境电商运营中的关键工具。尤其对于需要长期稳定、安全操作的场景&#xff0c;静态住宅IP逐渐成为行业首选。 一、什么是静态住宅IP&#xff1f; 静态住宅IP&#xff08;Static Residential IP&#xff0…

Qemu-STM32(十七):STM32F103加入AFIO控制器

概述 本文主要描述了在Qemu平台中&#xff0c;如何添加STM32F103的AFIO控制器模拟代码&#xff0c;AFIO是属于GPIO引脚复用配置的功能。 参考资料 STM32F1XX TRM手册&#xff0c;手册编号&#xff1a;RM0008 添加步骤 1、在hw/arm/Kconfig文件中添加STM32F1XX_AFIO&#x…