【Android Framework系列】第17章 Android Q沙箱模式(Scoped Storage)

news2024/12/23 6:39:22

1 背景

上一章节【Android Framework系列】第16章 存储访问框架 (SAF) 主要分析了Android4.4引入的存储访问框架(SAF),本章节我们对Android10(Q)的存储相关进行分析,了解下其限制存储方式。

Google为了让用户更好地控制自己的文件,并限制文件混乱情况,Android Q 更改了App访问设备存储空间的方式。

1.1 Android Q以前

只要程序获得了READ_EXTERNAL_STORAGE权限,就可以随意读取外部的存储公有目录;
只要程序获得了WRITE_EXTERNAL_STORAGE权限,就可以随意在写入外部存储的公有目录上新建文件或文件夹。
在这里插入图片描述

1.2 Android Q以后

于是Google在Android Q中提出了分区存储,意在限制程序对外部存储中公有目录的使用
分区存储对内部存储私有目录和外部存储私有目录都没有影响
在这里插入图片描述

2 沙箱模式

2.1 Android Q规定了App有两种存储空间模式视图:Legacy View、Filtered View。

2.1.1 Legacy View(兼容模式)

当项目targetSdkVersion <= 28时,AndroidQ设备上默认使用兼容模式,存储方式跟Android Q以前,App访问Sdcard一样,拥有完整的访问权限。

2.1.2 Filtered View(沙箱模式)

当项目targetSdkVersion > 28时,AndroidQ设备的App只能直接访问App-specific目录文件,没有权限访问App-specific外的文件。访问其他目录,只能通过MediaStoreSAF、或者其他App提供ContentProvider访问。

2.2 Scoped Storage将存储空间分为两部分

2.2.1 SD卡公共目录:Downloads、Documents、Pictures 、DCIM、Movies、Music、Ringtones

  1. 公共目录的文件在App卸载后,不会删除
  2. 可以通过SAFMediaStore接口访问

2.2…2 data/data私有目录(App-specific目录)

  1. 对于Filtered View AppApp-specific目录只能自己直接访问
  2. App卸载,数据会清除。

2.3 兼容影响

Scoped Storage对于App访问存储方式App数据存放以及App间数据共享,都产生很大影响。

2.4 适配

具体适配参考官方文档

2.4.1 App运行视图

系统通过下列确定App运行模式:

  1. App TargetSDK > 28,默认Filtered View(沙箱模式)
  1. App TargetSDK <= 28,声明了READ_EXTERNAL_STORAGE或者WRITE_EXTERNAL_STORAGE权限,默认Legacy View(兼容模式)
  1. 应用可以通过AndroidManifest.xml,设置requestLegacyExternalStorage,选择对应的方式:
    声明了READ_EXTERNAL_STORAGEWRITE_EXTERNAL_STORAGE权限(没有声明则忽略):
    requestLegacyExternalStorage=true表示Legacy View(兼容模式)
    requestLegacyExternalStorage=false表示Filtered View(沙箱模式)
    (注意:该方式Android11后失效了)

    在这里插入图片描述
  1. 系统应用可以申请android.permission.WRITE_MEDIA_STORAGE系统权限,同样拥有完整存储空间权限,可以访问所有文件,但是这个在CTS测试中,只有没有用户交互、可见的App,才能申请。具体参考《Android Bootcamp 2019 - Privacy Overview.pdf》
  1. App在下列条件都成立时
    ① 声明INSTALL_PACKAGES、或者动态申请INSTALL_PACKAGES权限
    ② 拥有WRITE_EXTERNAL_STORAGE权限
    ③ App拥有外置存储空间Read、Write权限
    但是通过Environment.isExternalStorageLegacy接口判断,返回不一定是Legacy View。

2.4.2 判断当前App运行模式

判断当前App运行什么模式,可以通过这个API判断:

Environment.isExternalStorageLegacy();

2.5 读写公共目录

App启动Filtered View后,只能直接访问自身App-specific目录,所以Android Q,提供了两种访问公共目录的方法:

2.5.1 通过MediaStore定义的Uri

MediaStore提供了下列几种类型的访问Uri,通过查找对应Uri数据,达到访问的目的。
下列每种类型又分为三种UriInternalExternal可移动存储:

Audio

  1. Internal: MediaStore.Audio.Media.INTERNAL_CONTENT_URI
    content://media/internal/audio/media。
  2. External: MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
    content://media/external/audio/media。
  3. 可移动存储: MediaStore.Audio.Media.getContentUri
    content://media//audio/media。

Video

  1. Internal: MediaStore.Video.Media.INTERNAL_CONTENT_URI
    content://media/internal/video/media。
  2. External: MediaStore.Video.Media.EXTERNAL_CONTENT_URI
    content://media/external/video/media。
  3. 可移动存储: MediaStore.Video.Media.getContentUri
    content://media//video/media。

Image

  1. Internal: MediaStore.Images.Media.INTERNAL_CONTENT_URI
    content://media/internal/images/media。
  2. External: MediaStore.Images.Media.EXTERNAL_CONTENT_URI
    content://media/external/images/media。
  3. 可移动存储: MediaStore.Images.Media.getContentUri
    content://media//images/media。

File

  1. MediaStore. Files.Media.getContentUri
    content://media//file。

Downloads

  1. Internal: MediaStore.Downloads.INTERNAL_CONTENT_URI
    content://media/internal/downloads。
  2. External: MediaStore.Downloads.EXTERNAL_CONTENT_URI
    content://media/external/downloads。
  3. 可移动存储: MediaStore.Downloads.getContentUri
    content://media//downloads。
2.5.1.1 获取所有的Volume

对于前面描述的Uri中,getContentUri如何获取所有,可以通过下述方式:

for(String volume:MediaStore.getExternalVolumeNames(this)){
	MediaStore.Audio.Media.getContentUri(volume);
}
2.5.1.2 Uri跟公共目录关系

MediaProvider对于App存放到公共目录文件,通过ContentResolver insert方法中Uri来确定,其中下表中<Uri路径>为相对路径,完整为:
content://media//<Uri路径>。
在这里插入图片描述

2.5.1.3 权限

MediaStore通过不同Uri,为用户提供了增、删(如果通过File Uri无法删除文件,需要通过SAF接口)、改。
App对应的权限如下:
在这里插入图片描述

2.5.1.4 查询文件

通过ContentResolver,根据不同的Uri查询不同的内容:

  try (Cursor c = getContentResolver()
                .query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, PROJECTION, null, null, null)) {
            while (c.moveToNext()) {
                Uri contentUri =
                        ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, c.getLong(0));
            }
        }

PS: MediaStore.Files 进行Query时候,只会显示图片、视频跟音频文件。

2.5.1.5 读取文件

通过ContentResolver query接口,查找出来文件后如何读取,可以通过下面的方式:

  1. 通过ContentResolver openFileDescriptor接口,选择对应的打开方式
    例如”r”表示读,”w”表示写,返回ParcelFileDescriptor类型FD
  2. 访问Thumbnail,通过ContentResolver loadThumbnail接口
    通过传递大小,MediaProvider返回指定大小的Thumbnail
  3. Native代码访问文件
    如果Native代码需要访问文件,可以参考下面方式:
    ①通过openFileDescriptor返回ParcelFileDescriptor
    ②通过ParcelFileDescriptor.detachFd()读取FD
    ③将FD传递给Native层代码
    App需要负责通过close接口关闭FD
   	ParcelFileDescriptor parcelFd = resolver.openFileDescriptor(uri, file0penMode);
	if (parcelFd != null) {
	      int fd = parcelFd.detachFd();
	      // Pass the integer value "fd" into your native code. Remember to call
	      // close(2) on the file descriptor when you're done using it.
	}
2.5.1.6 新建文件

如果需要新建文件存放到公共目录,需要通过ContentResolver insert接口,使用不同的Uri,选择存储到不同的目录。
在这里插入图片描述

2.5.1.7 修改文件

如果需要修改多媒体文件,需要通过ContentResolver query接口查找出来对应文件的Uri。
如果不是自己新建的文件,需要注意2.5.1.3 权限中描述,需要申请WRITE_EXTERNAL_STORAGE权限或者catch RecoverableSecurityException,弹框给用户选择。
在这里插入图片描述
在这里插入图片描述
通过下列接口,获取需要修改文件的FD或者OutputStream

  1. getContentResolver().openOutputStream(contentUri)
    获取对应文件的OutputStream。
  2. getContentResolver().openFile或者getContentResolver().openFileDescriptor
    通过openFile或者openFileDescriptor打开文件,需要选择Mode为”w”,表示写权限。这些接口返回一个ParcelFileDescriptor。
    getContentResolver().openFileDescriptor(contentUri,“w”);
    getContentResolver().openFile(contentUri,“w”,null);
2.5.1.8 删除文件

通过ContentResolver接口删除文件,Uri为query出来的Uri:

getContentResolver().delete(contentUri,null,null);

2.5.2 通过SAF接口

2.5.2.1 SAF简介

SAF,即Storage Access Framework,通过选择不同的DocumentsProvider,提供给用户打开、浏览文件。我们上一章节可以回头看一下

Android默认提供了下列DocumentsProvider
MediaDocumentsProviderExternalStorageProviderDownloadStorageProvider
他们之间差异是:
在这里插入图片描述
在这里插入图片描述

这个图片上,有三个区域,分别是:

  1. MediaDocumentsProvide
  2. DownloadStorageProvider
  3. ExternalStorageProvider
  4. 第三方DocumentsProvider
2.5.2.2 如何使用

具体参考官方文档

大致方法如下:

  1. 选择单个文件在这里插入图片描述
  2. 选择目录
    在这里插入图片描述
    文件管理程序,清理程序,可以通过这个方法获取对应目录以及子目录的全部管理权限。
  3. 新建文件
    在这里插入图片描述
  4. 删除
DocumentsContract.deleteDocument(getContentResolver(),uri);
  1. 修改

①获取OutputStream

getContentResolver().openOutputStream(uri);

②获取可写ParcelFileDescriptor

getContentResolver().openFileDescriptor(contentUri,"w");
getContentResolver().openFile (contentUri,"w",null);

具体Demo参考

2.6 访问App-specific目录

访问App-specific分为两种情况,第一是访问App自身App-specific目录,第二是访问其他App目录文件

2.6.1 App自身App-specific目录

Android Q,App如果启动了Filtered View,那么只能直接访问自己目录的文件:

  1. Environment.getExternalStorageDirectory、getExternalStoragePublicDirectory
    这些接口在Android Q上废弃,App是Filtered View,无法直接访问这个目录。
  2. 通过File(“/sdcard/”)访问
    App是Filtered View,无法直接访问这个目录。
  3. 获取App-specific目录
    获取Media接口:getExternalMediaDirs
    获取Cache接口:getExternalCacheDirs
    获取Obb接口:getObbDirs
    获取Data接口:getExternalFilesDirs

2.6.2 App-specific目录内部多媒体文件

App-specific目录内部多媒体文件:

  1. App自身访问,跟2.6.1 App自身App-specific目录一样
  2. 其他App访问
    ①默认情况下Media Scanner不会扫描App-specific里面的多媒体文件,如果需要扫描需要通过MediaScannerConnection.scanFile添加到MediaProvider数据库中
    访问方式跟2.5 读写公共目录一样。
    ②App通过ContentProvider共享出去

2.6.3 其他App目录文件

App是Filtered View,其他App无法直接访问当前App私有目录,需要通过下面方法:

2.6.3.1 通过SAF文件
  1. 共享App自定义DocumentsProvider

App自定义DocumentsProvider需要做以下步骤:
a)指定DocumentsProvider
在这里插入图片描述

b)DocumentsProvider实现基本接口:
在这里插入图片描述
在这里插入图片描述

  1. 访问App通过ACTION_OPEN_DOCUMENT,启动浏览
    在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

2.6.3.2 共享App实现FileProvider

FileProvider具体使用参考

这边总结一下大概步骤:

  1. 指定App FileProvider
    在这里插入图片描述

  2. 指定文件路径,配置文件必须要放到res/xml中
    在这里插入图片描述

  3. 获取分享Uri
    在这里插入图片描述

  4. 设置权限,并且发送Uri
    在这里插入图片描述
    在这里插入图片描述

  5. 接收App,设置接受的inter-filter
    在这里插入图片描述

  6. 接收并处理Uri
    在这里插入图片描述

2.6.3.3 App自定义私有Provider

App可以实现自定义ContentProvider,尤其是内部文件共享,但是不希望UI交互。

2.7 MediaStore

2.7.1 MediaStore _data字段

MediaStore中,DATA即(_data)字段,在Android Q中开始废弃。读写文件需要通过openFileDescriptor

2.7.2 MediaStore文件Pending状态

Android Q上,MediaStore中添加了一个IS_PENDING Flag,用于标记当前文件时Pending状态。
其他App通过MediaStore查询文件,如果没有设置setIncludePending接口,查询不到设置为Pending状态的文件,这就给App专享访问此文件。在一些情况下使用,例如在下载的时候:下载中,文件是Pending状态下载完成,文件Pending状态置为0。
在这里插入图片描述

2.7.3 MediaColumns.RELATIVE_PATH设置存储路径

Android Q上,通过MediaStore存储到公共目录的文件,除了2.5.1.2节Uri跟公共目录关系中规定的每一个存储空间的一级目录外,可以通过MediaColumns.RELATIVE_PATH来指定存储的次级目录,这个目录可以使多级,具体代码如下:

  1. ContentResolver insert方法
    通过values.put(Media.RELATIVE_PATH,"Pictures/album/family ")指定存储目录。其中,Pictures是一级目录,album/family是子目录。
  2. ContentResolver update方法
    通过values.put(Media.RELATIVE_PATH,"Pictures/album/family ")指定存储目录。通过update方法,可以移动存储地方。

2.7.4 访问图片Exif Metadata

Android Q上, App如果需要访问图片上的Exif Metadata,需要做下列事情:

  1. 申请ACCESS_MEDIA_LOCATION权限
  2. 通过MediaStore.setRequireOriginal返回新Uri
    Demo Code如下:
    在这里插入图片描述

2.7.5 App Filtered View,访问权限总结

App访问不同目录的权限总结如下:
在这里插入图片描述

2.7.6 应用卸载

如果App在AndroidManifest.xml中声明:android:hasFragileUserData="true"
卸载应用会有提示是否保留App数据:
在这里插入图片描述

2.7.7 App数据迁移

Android Q上,App TargetSDK>=Q默认是Filtered View。App如果是Filtered View,会涉及到数据的迁移,不然会导致旧数据无法使用。可以从下面几方面着手数据迁移:

  1. App需要在Legacy View下才能拥有完整操作存储的权限
  2. App存放在非公共区域的文件,可以通过SAF访问,通过SAF选择目录文件,用户选择访问App文件。
    在这里插入图片描述
  3. App可以将需要保存的文件:
    ImagesVideoAudio放到对应的公共目录,其他文件卸载后不删除文件可以放到Downloads下面。

2.7.8 MediaStore Queries

在使用MediaStore进行query动作的时候,使用Projection时,Column Name要在MediaStore中定义好的。

2.8 WRITE_MEDIA_STORAGE权限

2.8.1 背景

WRITE_MEDIA_STORAGE是一个很大强大的权限,能够允许App获取访问所有存储设备的权限。访问所有存储设备的权限,这个应当只赋予Media Stack。

2.8.2 兼容影响

在Android系统中,规定了WRITE_MEDIA_STORAGE能够获取media_rw用户组:
在这里插入图片描述

  1. 对于所有的可移动存储设备,例如T卡、U盘,在Mount到Android中的时候,对于普通App只有读权限,没有写权限,只有是media_rw用户组App,才能够写可移动存储设备
  2. 对于Android Q上,Scoped Storage,可以通过此权限,设置App运行为兼容模式
  3. Android CTS会进行测试,User-launchable 的App,不能申请此权限
    具体参考《Android Bootcamp 2019 - Privacy Overview.pdf》。

2.8.3 适配

App如果需要访问Media或者外置存储设备,可以通过MediaStore或者Storage Access Framework(SAF)接口

3 总结

好了,这里总结一下:

  1. App TargetSDK > 28 即 Android10(Q)及以上项目,Google限制了存储沙箱模式,在Android10(Q)以上的设备建议使用私有目录data/data,无法再直接访问外部SD卡存储目录,如需要使用外部SD卡存储目录则需要通过SAFMediaStore接口访问,并且只能访问特定的外部SD卡存储目录,如DownloadsDocumentsPicturesDCIMMoviesMusicRingtones等。这些外部SD卡存储目录(公有目录)所有App都能访问,因此显得不太安全。
  2. App TargetSDK <= 28 即 Android10(Q)以下项目,不受限制,有WRITE_MEDIA_STORAGE,READ_MEDIA_STORAGE权限即可。所以如果实在不想改动外部SD存储,那就将项目的targetSdk改成<=28。
  3. 至于SAFMediaStore接口访问外部SD卡存储特定目录的方法,详细参考上面的说明或参考官方文档。这里有个使用MediaStore方式访问外部SD卡存储特定目录的Demo供小伙伴们参考,别忘记点Star喔

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

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

相关文章

oracle19c 集群部署的问题汇总

1、互信报错 处理过程 01、发现/etc/sysctl.conf中有net.ipv4.icmp_echo_ignore_all1配置&#xff0c;注释后发现还是无法通过 02、# cat /proc/sys/net/ipv4/icmp_echo_ignore_all发现返回1&#xff0c;说明还是禁ping&#xff0c;两个节点执行# echo 0 > /proc/sys/net…

dosbox调试模式下0000:0000地址中内容被修改的原因

跟着王爽老师学习汇编&#xff0c;执行以下指令时&#xff0c;发现自己手动算出来的和dosbox验证的不一致 dosbox用的是debug模式&#xff0c;确保了内存数据和指令都完全一致的情况下&#xff0c;逐步执行&#xff0c;发现写在0000:0000位置的内存数据在执行add命令的时候被修…

改写软件-怎么选择改写软件

什么是改写软件&#xff1f;改写软件是基于自然语言处理技术的工具&#xff0c;它们可以分析一段文字&#xff0c;并将其重新表达&#xff0c;以保持原始意义&#xff0c;但使用不同的词汇和结构。这种技术可用于减少内容的重复&#xff0c;增加多样性&#xff0c;或者简化复杂…

小说推文授权和短剧推广渠道的介绍怎么申请

新赛道小说推文和短剧推广怎么申请授权的&#xff1f; 可以通过”巨量推文“进行授权 只需要通过平台申请对应的推广信息&#xff08;短剧或小说&#xff09;通过后即可展开推广 具体的推广方式也都有资料

阿里云产品试用系列-容器镜像服务 ACR

阿里云容器镜像服务&#xff08;简称 ACR&#xff09;是面向容器镜像、Helm Chart 等符合 OCI 标准的云原生制品安全托管及高效分发平台。 ACR 支持全球同步加速、大规模/大镜像分发加速、多代码源构建加速等全链路提效&#xff0c;与容器服务 ACK 无缝集成&#xff0c;帮助企业…

java服务内存说明及配置详解

java进程内存 JVM内存分布图: 【java进程内存】【堆外内存】 【jvm堆内存】 【堆外内存】 【Metaspace】 【Direct Memory】【JNI Memory】【code_cache】 … 堆外内存泄漏的排查在于【本地内存&#xff08;Native Memory&#xff09;】【Direct Memory】【JNI Memory】 一般…

Android---打开相机拍照

简单实现打开系统系统相机拍一张图片并显示在UI上&#xff0c;适用与个人主页头像的切换。 1. 添加权限。AndroidManifest.xml里添加使用相机的权限。 <uses-permission android:name"android.permission.CAMERA"/> 2. 布局。布局内容比较交单&#xff0c;一…

Ubuntu上安装、使用Redis的详细教程

这篇文章简单地介绍一下怎么在linux虚拟机上完成redis的安装及使用。 目录 1、安装redis 2、使用redis 3、启动/关闭redis 启动redis 启动方式一 启动方式二 启动方式三 重启redis 关闭redis 查看redis状态 4、在宿主机连接redis 5、通过java连接redis 创建maven项…

Webpack监视文件修改,自动重新打包文件

方法一&#xff1a;使用watch监视文件变化 在终端中输入以下指令&#xff1a; npx webpack --watch 我们使用这种方法监听文件变化时只会监听我们计算机本地的文件变化&#xff0c;在开发场景中我们的项目是要部署到服务器中的&#xff0c;因此这种方式并不推荐。 方法二&…

proteus中的各种电阻-可变电阻-排阻

在原理图中使用各类型的电阻是很常见的事情&#xff0c;尤其类似与排阻、可变电阻&#xff0c;但这些电阻对于不熟悉proteus的童鞋来说&#xff0c;一下子可能很难找到&#xff0c;或者很难找心中所想的那个类型&#xff0c;这里分类列出&#xff0c;便于大家使用。 文章目录 一…

Kubernetes集群+Keepalived+Nginx+防火墙实例

文章目录 实验前期规划1.拓扑图结构2.实验要求3.实验环境规划 一.kubeadm 部署 K8S 集群架构1.环境准备2.三个节点安装docker3.三个节点安装kubeadm&#xff0c;kubelet和kubectl4.部署K8S集群&#xff08;1&#xff09;初始化&#xff08;2&#xff09;部署网络插件flannel&am…

腾讯云COS+Picgo+Typora图床搭建

文章目录 什么是图床&#xff1f;配置腾讯云COS整体流程注册腾讯云账号并开通配置COS下载并配置PicGoTypora配置PicGo 可视化管理工具 什么是图床&#xff1f; 百度百科是这样解释的&#xff1a; 图床一般是指储存图片的服务器&#xff0c;有国内和国外之分。国外的图床由于有…

2023-9-22 整数划分

题目链接&#xff1a;整数划分 转化成背包问题 #include <iostream> #include <algorithm>using namespace std;const int N 1010, mod 1e9 7;int n; int f[N];int main() {cin >> n;f[0] 1;// i 相当于第i个物品的体积for(int i 1; i < n; i )// j …

计算机视觉: 三维物体生成

三维物体生成与编辑 论文地址: Controllable Mesh Generation Through Sparse Latent Point Diffusion Models 背景 数据是目前数字化和AI领域最宝贵的财富之一&#xff0c;但是对于目前的开发者来说&#xff0c;收集数据都意味着极大的成本。所以建立一个高效的生成模型能极…

Fair原理篇之Fair逻辑动态化通信实现

Fair 逻辑动态化,是对一期布局动态化的增强。为了实现逻辑动态化,我们当时考虑了多种方案,方案主要集中在这三个方面,一种是对google提供的JIT进行裁切,第二种是自定义解析引擎,第三种借助js的能力。 下面主要讲一下几方面: 架构的标准化通信协议的实现js文件的加载与释…

MongoDB 是什么和使用场景概述(技术选型)

一、从NOSQL(Not Only SQL)说起 常见的数据库可以分为下面的两种类型&#xff1a; RDBMS&#xff08;关系型数据库&#xff09;&#xff1a;常见的关系型数据库有 Oracle、DB2、Microsoft SQL Server、Microsoft Access、MySQL&#xff1b;NoSQL&#xff08;非关系型数据库&a…

计算机视觉与深度学习-循环神经网络与注意力机制-RNN(Recurrent Neural Network)、LSTM-【北邮鲁鹏】

目录 举例应用槽填充&#xff08;Slot Filling&#xff09;解决思路方案使用前馈神经网络输入1-of-N encoding(One-hot)&#xff08;独热编码&#xff09; 输出 问题 循环神经网络&#xff08;Recurrent Neural Network&#xff0c;RNN&#xff09;定义如何工作深度Elman Netwo…

DAB-DETR

DAB-DETR&#xff08;2022ICLR&#xff09; DAB&#xff1a;Dynamic Anchor Boxes 贡献&#xff1a; 明确了不是由于learnable queries导致的收敛缓慢 4D anchor Anchor Update Width & Height Modulated Temperature Tuning 前半部分&#xff1a;Why a positional …

每日一题~将有序数组转换为二叉搜索树

原题链接&#xff1a;108. 将有序数组转换为二叉搜索树 - 力扣&#xff08;LeetCode&#xff09; 题目描述&#xff1a; 思路分析&#xff1a; 今天这道题比较简单&#xff0c;我们看一下数组和树之间的联系很容易就能发现规律。 通过简单观察我们发现&#xff0c;位于数组中…

CSDN博客去水印方法

直接在 创作中心->博客 设置这里关了好像就行了&#xff0c;之前方法是找图片链接?后面的一大串字符给去掉。