【Android Framework系列】第15章 Fragment+ViewPager与Viewpager2相关原理

news/2024/5/19 20:20:31/文章来源:https://blog.csdn.net/u010687761/article/details/132674389

1 前言

上一章节【Android Framework系列】第14章 Fragment核心原理(AndroidX版本)我们学习了Fragment的核心原理,本章节学习常用的Fragment+ViewPager以及Fragment+ViewPager2的相关使用和一些基本的源码分析。

在这里插入图片描述

2 Fragment+ViewPager

我们常用的两个PagerAdapter的实现类,也就是FragmentStatePagerAdapterFragmentPagerAdapter,今天,我们就来学习一下它们的使用方法,并进行对比。

2.1 FragmentPagerAdapter 和 FragmentStatePagerAdapter 的区别

1. fragments对象的处理:
FragmentPagerAdapter:范围外fragments会保存在内存中(detach),但是fragment对应的View会被销毁
FragmentStatePagerAdapter:范围外fragments不会保存在内存中(remove),View也会被销毁。
2. 状态的处理:
FragmentPagerAdapter:范围外fragments对应的SavedState会保存
FragmentStatePagerAdapter:只保存范围内fragments对应的SavedState。这个SavedStateFragment的生命周期回调中供外部传参数,和Activity类似。
3. 适用场景:相同数量的fragmentsFragmentPagerAdapter内存较大,但页面切换更友好;FragmentStatePagerAdapter内存占用少,页面切换稍差。

因此FragmentPagerAdapter适用于Fragment数量少的情况,FragmentStatePagerAdapter适用于Fragment数量多的情况。

我们首先来看下FragmentPagerAdapter

	@Overridepublic Object instantiateItem(ViewGroup container, int position) {if (mCurTransaction == null) {mCurTransaction = mFragmentManager.beginTransaction();}final long itemId = getItemId(position);// Do we already have this fragment?//判断请求的Fragment是否已经被生成过String name = makeFragmentName(container.getId(), itemId);Fragment fragment = mFragmentManager.findFragmentByTag(name);if (fragment != null) {if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);// 当前缓存有则直接使用mCurTransaction.attach(fragment);} else {fragment = getItem(position); //调用这个方法来生成新的Fragmentif (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);// 将新生成的Fragment存储起来,以便以后再次用到时,直接attach()mCurTransaction.add(container.getId(), fragment,makeFragmentName(container.getId(), itemId)); }if (fragment != mCurrentPrimaryItem) {fragment.setMenuVisibility(false);if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.STARTED);} else {fragment.setUserVisibleHint(false);}}return fragment;}@Overridepublic void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {Fragment fragment = (Fragment) object;if (mCurTransaction == null) {mCurTransaction = mFragmentManager.beginTransaction();}if (DEBUG) Log.v(TAG, "Detaching item #" + getItemId(position) + ": f=" + object+ " v=" + fragment.getView());mCurTransaction.detach(fragment);if (fragment.equals(mCurrentPrimaryItem)) {mCurrentPrimaryItem = null;}}

FragmentPagerAdapterdestroyItem方法中调用了detach()只是改变Fragment状态,说明只有消除整个adapter时候,才能将生成的Fragment都消除掉,不然就会直接在内存中。

接下来我们对比FragmentStatePagerAdapter

  @Overridepublic Object instantiateItem(@NonNull ViewGroup container, int position) {// If we already have this item instantiated, there is nothing// to do.  This can happen when we are restoring the entire pager// from its saved state, where the fragment manager has already// taken care of restoring the fragments we previously had instantiated.// mFragments中对应位置有Fragment的情况下直接返回if (mFragments.size() > position) {Fragment f = mFragments.get(position);if (f != null) {return f;}}if (mCurTransaction == null) {mCurTransaction = mFragmentManager.beginTransaction();}Fragment fragment = getItem(position);if (DEBUG) Log.v(TAG, "Adding item #" + position + ": f=" + fragment);if (mSavedState.size() > position) {Fragment.SavedState fss = mSavedState.get(position);if (fss != null) {fragment.setInitialSavedState(fss);}}while (mFragments.size() <= position) {mFragments.add(null);}fragment.setMenuVisibility(false);if (mBehavior == BEHAVIOR_SET_USER_VISIBLE_HINT) {fragment.setUserVisibleHint(false);}mFragments.set(position, fragment);mCurTransaction.add(container.getId(), fragment);if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.STARTED);}return fragment;}@Overridepublic void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {Fragment fragment = (Fragment) object;if (mCurTransaction == null) {mCurTransaction = mFragmentManager.beginTransaction();}if (DEBUG) Log.v(TAG, "Removing item #" + position + ": f=" + object+ " v=" + ((Fragment)object).getView());while (mSavedState.size() <= position) {mSavedState.add(null);}mSavedState.set(position, fragment.isAdded()? mFragmentManager.saveFragmentInstanceState(fragment) : null);mFragments.set(position, null);// 缓存中移除fragment,下次使用得重新创建mCurTransaction.remove(fragment);if (fragment.equals(mCurrentPrimaryItem)) {mCurrentPrimaryItem = null;}}

FragmentStatePagerAdapterdestroyItem方法调用的时候把Fragment移除了,因此下次使用需要重新创建Fragment

3 Fragment+ViewPager2

ViewPager2Android Jetpack库中的一个组件,是用于在应用程序中实现页面切换和滑动效果的容器。其实ViewPager2本身继承自RecyclerView。可以复习下我们的【Android Framework系列】第12章 RecycleView相关原理及四级缓存策略分析

3.1 ViewPager2的作用和用途

3.1.1 ViewPager2使用场景

是一个功能强大的滑动容器,可以应用于多种场景中,提供了灵活的页面切换和布局定制功能,使得应用程序界面更加丰富和交互性强,可以用于以下场景:

  1. 实现引导页或欢迎页
    ViewPager2可以用于创建引导页或欢迎页,让用户通过滑动浏览介绍应用程序功能或展示欢迎内容。

  2. 创建图片浏览器
    ViewPager2可以用于创建图片浏览器,允许用户通过滑动来切换不同的图片,并支持缩放和手势交互。

  3. 构建轮播图
    ViewPager2非常适合构建轮播图功能,可以通过适配器动态加载不同的轮播项,并提供自动循环滚动的功能。

  4. 实现选项卡式布局
    结合TabLayoutViewPager2可以用于创建选项卡式布局,让用户通过滑动选项卡来切换不同的内容页面。

  5. 创建垂直滑动页面
    ViewPager不同,ViewPager2支持垂直方向的滑动,因此可以用于创建垂直滑动的页面布局,例如垂直滑动的导航菜单或垂直的新闻列表。

  6. 实现分页数据展示
    ViewPager2可以用于展示分页数据,例如将大量数据按页加载并在每一页中展示一部分内容。

  7. 嵌套滑动布局
    ViewPager2可以与其他滑动组件(如RecyclerView)嵌套使用,实现复杂的滑动布局结构。

  8. 实现自定义的滑动效果
    通过使用自定义的转换器(Transformer),可以实现各种炫酷的页面切换效果,例如渐变、缩放、旋转等。

3.1.2 ViewPager2相较于ViewPager的改进和优势

ViewPager2是对ViewPager的改进版本,提供了更好的性能、更灵活的适配器和更丰富的功能。它是构建滑动页面布局的首选组件,可以在应用程序中实现各种滑动页面的需求,并提供更好的用户体验,大致有以下几点改进和优势:

  1. 支持垂直滑动
    ViewPager2是在ViewPager的基础上进行改进的,最显著的改进之一是支持垂直滑动。而在ViewPager中,只支持水平滑动。这使得ViewPager2在创建垂直布局或特定场景下的垂直滑动功能更加方便和灵活。

  2. 更好的性能和稳定性
    ViewPager2内部实现使用了RecyclerView作为容器,而不再依赖于ViewPager的实现方式。这使得ViewPager2具有RecyclerView的优势,例如更好的性能和内存管理更流畅的滑动体验及更好的布局回收和复用机制。同时,ViewPager2还解决了ViewPager一些已知的问题和不稳定性,如条目位置错乱、刷新数据的不及时等。

  3. 支持使用Fragment作为页面
    ViewPager不同,ViewPager2直接支持使用Fragment作为页面,而无需通过FragmentPagerAdapterFragmentStatePagerAdapter进行适配。这简化了页面管理和生命周期处理,并提供了更直观和一致的使用体验。

  4. 更灵活的适配器
    ViewPager2引入了新的适配器接口,即RecyclerView.Adapter的子类RecyclerView.Adapter。这使得适配器的创建和管理更加灵活,同时提供了更多的功能和扩展性。

  5. 更丰富的功能和接口
    ViewPager2提供了许多新的功能和接口,例如支持页面预加载、更强大的页面切换动画支持、更丰富的回调接口等。这些功能和接口使得开发者能够更好地控制和定制ViewPager2的行为和外观。

3.2 FragmentStateAdapter

上面我们了解了ViewPager2对比ViewPager的一些区别和优化点,下面我们继续看一下ViewPager2对应的Adapter
我们知道ViewPager2继承自RecyclerView,那么它所对应的FragmentStateAdapter必定继承自RecyclerView.Adapter,这点小伙伴们应该能理解。那么我们在RecyclerView中的每个Item加入Fragment作为容器展示。
RecyclerView.Adapter关注的是ViewHolder的复用,但是在FragmentStateAdapter中的Framgent是不会复用的,即有多少个item就应该创建多少个Fragment,那么这其中是如何转换的呢?

我们先来看看FragmentStateAdapter源码:

public abstract class FragmentStateAdapter extendsRecyclerView.Adapter<FragmentViewHolder> implements StatefulAdapter {// 通过FragmentStateAdapter声明中的泛型可以知道,// ViewPager2之所以能够在RecyclerView的基础上能对外屏蔽对ViewHolder的使用,// 其内部是借助FragmentViewHolder实现的,其内部就new了一个FrameLayout。@Overridepublic final FragmentViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {return FragmentViewHolder.create(parent);}@Overridepublic final void onBindViewHolder(final @NonNull FragmentViewHolder holder, int position) {final long itemId = holder.getItemId();final int viewHolderId = holder.getContainer().getId();final Long boundItemId = itemForViewHolder(viewHolderId); // item currently bound to the VHif (boundItemId != null && boundItemId != itemId) {removeFragment(boundItemId);mItemIdToViewHolder.remove(boundItemId);}mItemIdToViewHolder.put(itemId, viewHolderId); // this might overwrite an existing entry// 内部会最终回调到createFragment用来创建当前FragmentensureFragment(position);/** Special case when {@link RecyclerView} decides to keep the {@link container}* attached to the window, but not to the view hierarchy (i.e. parent is null) */final FrameLayout container = holder.getContainer();if (ViewCompat.isAttachedToWindow(container)) {if (container.getParent() != null) {throw new IllegalStateException("Design assumption violated.");}container.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {@Overridepublic void onLayoutChange(View v, int left, int top, int right, int bottom,int oldLeft, int oldTop, int oldRight, int oldBottom) {if (container.getParent() != null) {container.removeOnLayoutChangeListener(this);placeFragmentInViewHolder(holder);}}});}// 回收已经不在item集合中的Fragment,节省内存开销gcFragments();}private void ensureFragment(int position) {long itemId = getItemId(position);if (!mFragments.containsKey(itemId)) {// TODO(133419201): check if a Fragment provided here is a new FragmentFragment newFragment = createFragment(position);newFragment.setInitialSavedState(mSavedStates.get(itemId));mFragments.put(itemId, newFragment);}}void gcFragments() {if (!mHasStaleFragments || shouldDelayFragmentTransactions()) {return;}// Remove Fragments for items that are no longer part of the data-setSet<Long> toRemove = new ArraySet<>();for (int ix = 0; ix < mFragments.size(); ix++) {long itemId = mFragments.keyAt(ix);if (!containsItem(itemId)) {toRemove.add(itemId);mItemIdToViewHolder.remove(itemId); // in case they're still bound}}// Remove Fragments that are not bound anywhere -- pending a grace periodif (!mIsInGracePeriod) {mHasStaleFragments = false; // we've executed all GC checksfor (int ix = 0; ix < mFragments.size(); ix++) {long itemId = mFragments.keyAt(ix);if (!isFragmentViewBound(itemId)) {toRemove.add(itemId);}}}for (Long itemId : toRemove) {removeFragment(itemId);}}// onViewAttachToWindow的时候调用placeFragmentInViewHolder,// 将FragmentViewHolder的container与当前Fragment绑定@Overridepublic final void onViewAttachedToWindow(@NonNull final FragmentViewHolder holder) {placeFragmentInViewHolder(holder);gcFragments();}void placeFragmentInViewHolder(@NonNull final FragmentViewHolder holder) {Fragment fragment = mFragments.get(holder.getItemId());if (fragment == null) {throw new IllegalStateException("Design assumption violated.");}FrameLayout container = holder.getContainer();View view = fragment.getView();if (!fragment.isAdded() && view != null) {throw new IllegalStateException("Design assumption violated.");}// { f:added, v:notCreated, v:notAttached} -> schedule callback for when createdif (fragment.isAdded() && view == null) {scheduleViewAttach(fragment, container);return;}// { f:added, v:created, v:attached } -> check if attached to the right containerif (fragment.isAdded() && view.getParent() != null) {if (view.getParent() != container) {addViewToContainer(view, container);}return;}// { f:added, v:created, v:notAttached} -> attach view to containerif (fragment.isAdded()) {addViewToContainer(view, container);return;}// { f:notAdded, v:notCreated, v:notAttached } -> add, create, attachif (!shouldDelayFragmentTransactions()) {scheduleViewAttach(fragment, container);mFragmentManager.beginTransaction().add(fragment, "f" + holder.getItemId()).setMaxLifecycle(fragment, STARTED).commitNow();mFragmentMaxLifecycleEnforcer.updateFragmentMaxLifecycle(false);} else {if (mFragmentManager.isDestroyed()) {return; // nothing we can do}mLifecycle.addObserver(new LifecycleEventObserver() {@Overridepublic void onStateChanged(@NonNull LifecycleOwner source,@NonNull Lifecycle.Event event) {if (shouldDelayFragmentTransactions()) {return;}source.getLifecycle().removeObserver(this);if (ViewCompat.isAttachedToWindow(holder.getContainer())) {placeFragmentInViewHolder(holder);}}});}}}
public final class FragmentViewHolder extends ViewHolder {private FragmentViewHolder(@NonNull FrameLayout container) {super(container);}// FragmentViewHolder实际上就创建了一个FrameLayout@NonNull static FragmentViewHolder create(@NonNull ViewGroup parent) {FrameLayout container = new FrameLayout(parent.getContext());container.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT));container.setId(ViewCompat.generateViewId());container.setSaveEnabled(false);return new FragmentViewHolder(container);}@NonNull FrameLayout getContainer() {return (FrameLayout) itemView;}
}

通过上面源码分析可以知道,虽然Fragment没有被复用,但是通过复用了ViewHoldercontainer实现了Framgent的交替显示

4 总结

总结一下本章节的内容:

  1. Fragment+ViewPager只能横向滚动,性能相对较差。对应不同的Adapter效果不一样。
  2. Fragment+ViewPager使用FragmentPagerAdapter,范围外fragments会保存在内存中(detach),但是fragment对应的View会被销毁,fragments对应的SavedState会保存,FragmentPagerAdapter内存较大但页面切换更友好,适用于Fragment数量少的情况
  3. Fragment+ViewPager使用FragmentStatePagerAdapter,范围外fragments不会保存在内存中(remove),View也会被销毁。只保存范围内fragments对应的SavedState。这个SavedStateFragment的生命周期回调中供外部传参数,和Activity类似。内存占用少,页面切换稍差。适用于Fragment数量多的情况。
  4. Fragment+ViewPager2能横向或纵向滚动,继承自RecyclerView所以基本有其所有的优点,包括内存占用、缓存管理等。FragmentStateAdapter继承自RecyclerView.Adapter,虽然Fragment没有被复用,但是通过复用了ViewHoldercontainer实现了Framgent的交替显示。

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

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

相关文章

分享78个Python源代码总有一个是你想要的

分享78个Python源代码总有一个是你想要的 源码下载链接&#xff1a;https://pan.baidu.com/s/1ZhXDsVuYsZpOUQIUjHU2ww?pwd8888 提取码&#xff1a;8888 下面是文件的名字。 12个python项目源码 Apache Superset数据探查与可视化平台v2.0.1 API Star工具箱v0.7.2 Archery…

刷刷刷——滑动窗口

文章目录 209. 长度最小的子数组&#xff08;中等&#xff09;题目链接算法原理代码实现 3. 无重复字符的最长子串&#xff08;中等&#xff09;题目链接算法原理代码实现 1004. 最大连续1的个数 III&#xff08;中等&#xff09;题目链接算法原理代码实现 1658. 将 x 减到 0 的…

[矩阵的乘法运算] m*M = c

另人给的一道题&#xff0c;一时没弄出来&#xff0c;后来看WP&#xff0c;复现一下。 对于矩阵运算 m*M c 求m 的情况。 满秩差1半秩 import os import secret import hashlib from Crypto.Util.number import getPrime from Crypto.Util.Padding import padLEN 32def xo…

Denoising diffusion implicit models 阅读笔记

Denoising diffusion probabilistic models (DDPMs)从马尔科夫链中采样生成样本&#xff0c;需要迭代多次&#xff0c;速度较慢。Denoising diffusion implicit models (DDIMs)的提出是为了加速采样过程&#xff0c;减少迭代的次数&#xff0c;并且要求DDIM可以复用DDPM训练的网…

【AI视野·今日CV 计算机视觉论文速览 第252期】Fri, 22 Sep 2023

AI视野今日CS.CV 计算机视觉论文速览 Fri, 22 Sep 2023 Totally 90 papers &#x1f449;上期速览✈更多精彩请移步主页 Interesting: &#x1f4da;SVGCustomization, 基于文本的矢量图生成定制(from 香港城市大学)。 website:https://intchous.github.io/SVGCustomization/ …

基于SpringBoot的社区医院信息平台

目录 前言 一、技术栈 二、系统功能介绍 患者信息管理 护士信息管理 医生信息管理 药品管理员管理 患者添加 安排检查 完成注射列表 三、核心代码 1、登录模块 2、文件上传模块 3、代码封装 前言 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信息系…

【一、虚拟机vmware安装】

安装虚拟机 下载 官方下载地址&#xff1a;https://www.vmware.com/cn.html 大概流程就是&#xff0c;最重要的事最后一步

问题:conda删除虚拟环境,报错no package names supplied

用conda 用 conda remove -n ScratchDet_20200114 删除虚拟 环境ScratchDet_20200114时报错 conda remove -n ScratchDet_20200114CondaValueError: no package names supplied,try "conda remove -h" for more details 解决方法&#xff0c;用下面的命令 conda env…

什么是CORS(跨源资源共享)?如何解决前端中的CORS问题?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ CORS&#xff08;跨源资源共享&#xff09;⭐ 解决前端中的CORS问题的方法⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 欢迎来到前端入门之旅&#xff01;感兴趣的可以订阅本专栏哦&#xff01;这个专栏是为…

LeetCode【174. 地下城游戏】

一片丹心图报国&#xff0c;两行清泪为忠家。——于谦 恶魔们抓住了公主并将她关在了地下城 dungeon 的 右下角 。地下城是由 m x n 个房间组成的二维网格。我们英勇的骑士最初被安置在 左上角 的房间里&#xff0c;他必须穿过地下城并通过对抗恶魔来拯救公主。 骑士的初始健康…

Python进阶学习----一闭三器

目录 ​编辑 前言 一.三器 1. 迭代器&#xff08;Iterator&#xff09; 1.1 什么是可迭代对象 1.2什么是迭代器 1.3案例演示&#xff1a; 以下是一个简单的迭代器示例&#xff0c;遍历一个列表并打印每个元素&#xff1a; 1.4迭代器总结 2. 生成器&#xff08;Generat…

前端uniapp如何转base64使用uniapp插件市场

插件市场 网址 使用 可以下载&#xff0c;也可以引用&#xff0c;我是下载下来的引用 代码 正常使用 pathToBase64(img).then(path > {img pathresolve(path)}).catch(error > {console.error(error)reject(error)})使用出现[object Promise]错误 解决方法 let img …

算法通关村第16关【青铜】| 滑动窗口思想

1. 滑动窗口的基本思想 一句话概括就是两个快慢指针维护的一个会移动的区间 固定大小窗口&#xff1a;求哪个窗口元素最大、最小、平均值、和最大、和最小 可变大小窗口&#xff1a;求一个序列里最大、最小窗口是什么 2. 两个入门题 &#xff08;1&#xff09;子数组最大平…

Docker部署Nginx+FastDFS插件

文章目录 一、部署FastDFS二、部署Nginx(带FastDFS插件)三、FastDFS上传文件Nginx访问验证 一、部署FastDFS 1、准备工作 docker pull qinziteng/fastdfs:5.05 Pwd"/data/software/fastdfs" mkdir ${Pwd}/{storage,tracker} -p2、创建TEST容器&#xff0c;将fastdf…

RK3568平台开发系列讲解(工具命令篇)ADB的安装

🚀返回专栏总目录 文章目录 一、ADB介绍二、Windows 下安装 adb 工具沉淀、分享、成长,让自己和他人都能有所收获!😄 一、ADB介绍 adb 全称 Android Debug Bridge,直译过来就是 Android 调试桥,它是一个通用的命令行工具。adb 做为 Android 设备与 PC 端连接的一个桥梁…

linux c++调用c

参考 【Linux下gcc编译的四个过程】_Deacde_ZY的博客-CSDN博客 C与C如何互相调用_c文件引用c头文件_卍一十二画卍的博客-CSDN博客 Linux动态链接库的创建与使用_linux创建动态库_满天星羽的博客-CSDN博客 c调用c 1.1 例子1&#xff1a; test1.c #include <stdio.h>…

Python|OpenCV-访问并修改图片像素值,鉴别彩色和灰色图像(6)

前言 本文是该专栏的第6篇,后面将持续分享OpenCV计算机视觉的干货知识,记得关注。 在使用OpenCV对图像进行操作的时候,通常需要熟练掌握一些Numpy知识点。因为有的时候需要用到Numpy和OpenCV结合去实现图像的操作,所以说想要写出较好的OpenCV代码的最好方法,就需要有Nump…

【线性代数】为什么 AA* = |A|E

A A ∗ 矩阵相乘&#xff0c;刚好是行列式展开的定义 AA*矩阵相乘&#xff0c;刚好是行列式展开的定义 AA∗矩阵相乘&#xff0c;刚好是行列式展开的定义 矩阵提取一个因子 ∣ A ∣ &#xff0c;所有元素需要除 ∣ A ∣ 矩阵提取一个因子 |A|&#xff0c;所有元素需要除 |A| 矩…

idea集成tomcat(Smart Tomcate插件安装)

当我们在 tomcat 上部署好一个 webapp 后&#xff0c;如果我们要修改代码&#xff0c;就需要重新进行打包和部署&#xff0c;但往往在工作中是需要频繁修改代码&#xff0c;然后再查看成果的&#xff0c;就需要反复的进行打包和部署的过程&#xff0c;这是很麻烦的 通过 Smart …

腾讯云16核服务器配置大全_CVM和轻量服务器汇总

腾讯云16核CPU服务器有哪些配置可以选择&#xff1f;可以选择标准型S6、标准型SA3、计算型C6或标准型S5等&#xff0c;目前标准型S5云服务器有优惠活动&#xff0c;性价比高&#xff0c;计算型C6云服务器16核性能更高&#xff0c;轻量16核32G28M带宽优惠价3468元15个月&#xf…