业网页推广时出现网页兼容问题,不仅在pc端开发中会碰到,而且在APP开发当中也会经常碰到。
1、禁止图片点击放大
部分安卓手机点击图片会放大,如需要禁止放大,只需要设置css属性,这样会让img标签的点击事件失效,如果想要给图片添加点击事件就要给上面再写一层。
方案:img{ pointer-events:none; }
2、禁止iOS设别长串数字为电话
方案:<meta name="format-detection" content="telephone=no" />
3、禁止赋值、选中文本
方案:设置CSS属性,-webkit-user-select:none
4、一些情况下对非可点击元素如(label,span)监听点击事件,不会在iOS下触发
方案:css属性增加 cursor:pointer
5、上下拉动滚动条时卡顿、慢
方案:body{ -webkit-overflow-scrolling:touch;overflow-scrolling:touch; }
6、安卓不会自动播放视频
安卓手机的autoplay没有生效,需要手动触发一下
方案:window.addEventListener('touchstart',()=>{ audio.play(); },false)
7、半透明的遮罩层改为全透明
在iOS上,当点击一个链接或者通过js绑定了点击事件的元素时,会出现一个半透明的背景,当手指离开屏幕,灰色背景消失,出现“闪屏”
方案:html,body{ -webkit-tap-highlight-color:rgba(0,0,0,0); }
8、ios系统全屏状态下浏览器上下滚动时会出现底色问题
移动端项目要求是全屏滚动,用的是 fullpage,上下滚动时安卓正常,苹果手机浏览器上下滚动时会出现底色问题
方案:document.body.addEventListener('touchmove',function(e) { e.preventDefault();}, {passive:false})。
个功能也是很常见了,一般都是为了方便用户操作,比如复制订单编号。不废话,下面就来看看具体是怎么操作的。
tv_order_copy.setOnClickListener {
//获取剪切板管理器
val cm: ClipboardManager = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
//设置内容到剪切板
cm.primaryClip = ClipData.newPlainText(null, item.orderId)
ToastUtils.show(context, "已复制")
}
非常简单,首先获取剪切板管理器,然后设置内容即可可以设置的内容有3种类型:
tv_order_clear.setOnClickListener {
val cm: ClipboardManager = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
//要api28以上
cm.clearPrimaryClip()
} else {
cm.primaryClip = ClipData.newPlainText(null, null)
}
}
在api等级28以上,直接调用clearPrimaryClip()即可,以下,重新设置为空即可。
iv_order_get.setOnClickListener {
val cm: ClipboardManager = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
if (cm.hasPrimaryClip() && cm.primaryClip != null) {
//cm.primaryClip.itemCount
val text = cm.primaryClip.getItemAt(0).text
ToastUtils.show(context, text.toString())
}
}
在有内容的情况下,这里的代码是取的第一个,即getItemAt(0).text,如果有多个的情况下,且有取多个的需求,这里的下标取值就要根据cm.primaryClip.itemCount来动态设置了。
代在进步,第三套少儿广播体操!不好意思,搞错频道了,重来!时代在进步,Android的版本也是快速的进行着迭代着,从我们以前最常见的Android 4.4一直发展到了今天的Android 11版本(即Android K到Android R),Android版本的快速迭代对于消费者来说是一件普天同庆的大好事情,但是对于我们开发者来说各种适配各种改造有时候吃翔的心情都有了。而对于Android版本的适配和各种改造的第一步就是从编译Android源码开始,可是不幸的是随着Android版本的迭代连编译Android源码的相关流程都发生了翻天覆地的变化,正所谓工欲利其事必先利器,所以我们今天的这篇博客将带领读者一起来到Android各个版本的源码编译发展和编译具体操作步骤!
这里我们需要注意一下Android各个版本的对应关系如下:
Android 5.x (Lollipop)简称Android L版本Android 6.0 (MarshMallow) 简称Android M版本Android 7.x (Nougat)简称Android N版本Android 8.x (Oreo)简称Android O版本Android 9.0 (Pie)简称Android P版本Android 10.0 (Q)简称Android Q版本Android 11.0 (R)简称Android R版本
并且这里还有一点需要特别注意,本文演示的Android R版本是以高通平台为基准进行的。
俗话说天时地利人和缺一不可,而这其中的地利翻译过来说的就是环境因素了,人的成长离不开环境因素,而我们的Android编译也离不开编译环境的构建!虽然我们本篇博客的主题是Android源码编译指南,但是我们还是有必要抽出一个章节来简单说明下Android编译环境的构建和初始化过程,以及初始化完毕后常见的命令。
本章节重点偏向的是Android编译环境的构建,而不是编译环境构建的原理分析(如果是原理分析,那内容就多了,关于Android编译墙裂推荐一篇博客来自IBM社区理解 Android Build 系统,你值得拥有!)。
虽然Android的版本一直在迭代着,但是Android编译环境的构建步骤还是比较良心的依然没有多大的变化(注意这里的措辞,只是步骤),对于有过Android源码开发经验的读者来说是再为熟悉不过的了,通常是如下二部曲:
$ source build/envsetup.sh
$ lunch aosp-eng
12
虽然各位对上述的命令应该已经烂熟于心了,但是这里我还是简单说明一下:
这里补充一点对Android的源码编译类型简单说明一下,它可以分为如下三种功能,每种类型的特点如下:
在编译环境初始化完成后,我们就可以使用各种各种编译环境提供的指令和make编译命令族来开启Android的构建之旅了,这里我简单的总结了下,我们在Android编译中可能会用到的编译环境提供的指令和make编译命令族,如下:
指令说明 croot 切到Android源码树的根目录(当你深入Android源码树的子目录,想回到根目录的时候此命令就非常实用了)m相当于在源码树的根目录执行make,并且该命令不一定要在根目录下执行mm编译当前目录路径下的所有模块(包括include进来的,但是不包括存在依赖关系模块)mma编译当前目录路径下的所有模块(包括include进来的,且包括存在依赖关系模块)mmm[module_path]编译指定目录路径下的所有模块(包括include进来的,但是不包括存在存在依赖关系模块)mmma[module_path]编译指定目录路径下的所有模块(包括include进来的,包括存在存在依赖关系模块)cgrep对C/C++文件执行grep(即grep的时候只搜寻C/C++文件类型,注意这里也包括.h文件类型)jgrep对Java文件执行grep(即grep的时候只搜寻Java文件类型)resgrep在所有res/.xml文件上执行 grep即grep的时候只搜寻res/.xml文件类型)printconfig显示当前Android编译的相关配置信息add_lunch_combo在lunch命令的的菜单中添加一个条目
这里我们对上述表格中的"不包括存在依赖关系的模块"简单解释一下:
1.依赖关系模块这个要怎么说呢,这里我们举个例子!譬如模块A的编译需要依赖模块B,此时的B是一个so库。
2.假如我们通过mm或者mmm编译模块A的时候,此时B模块还没有编译那么此时就会报错
3.假如我们使用的是mma或者mmma编译模块A,假如依赖的模块B还没有编译,那么会先将模块B编译OK,然后编译模块A(当然这里只是举栗子,可能A还依赖C,D同理也会先编译)
Android的Build编译系统处理常见的make单命令之外,还提供了其它的一系列make命令族,这里我们简单过下:
指令说明make update-api更新API文件,在framework API改动之后,需要首先执行该命令来更新API,公开的API记录在frameworks/base/api目录下makeAndroid默认系统编译指令,会编译出整个系统的所有镜像(其实质最终执行的是make droid)make droid同上make sdk编译出Android的SDK开发套件make clean-sdk清理SDK的编译产物make dist执行整个编译,并将 MAKECMDGOALS变量定义的输出文件拷贝到 /out/dist目录下,
这个命令在实际中用的比较少make all编译所有内容,不管当前产品的定义中是否会包含,官方解释如下:
builds everything make droid does,plus everything whose LOCAL_MODULE_TAGS do not include the “droid” tag. The build server runs this to make sure that everything that is in the tree and has an Android.mk builds.make help帮助信息命令,显示当前Android版本主要支持的make命令make snod从已经编译出的包快速构建系统镜像(譬如你重新单独编译了某个模块,然后想快速进行打包到system.img,可以使用此命令加快速度)make clean-$(LOCAL_MODULE)Let you selectively clean one target. For example, you can type make clean-libutils and t will delete libutils.so and all of the intermediate files.
即清理掉一个指定模块的编译结果和中间产物make clean-$(LOCAL_PACKAGE_NAME)Let you selectively clean one target. For example, you can type
make clean-Home and it will clean just the Home app…
即清理掉一个指定模块的编译结果和中间产物make cleandeletes all of the output and intermediate files for this configuration. This is the same as rm -rf out/<configuration>/
通常删除的是整个Android源码工程的out/*目录make clobberdeletes all of the output and intermediate files for all configurations. This is the same as rm -rf out/.
这个命令在实际中,应用得比较少make datacleandeletes contents of the data directory inside the current combo
directory. This is especially useful on the simulator and emulator, where the persistent data remains present between builds.
这个命令在实际中应用得也比价少make installclean当我们在执行切换编译目标时可以执行make installclean,用以清除之前编译生成的文件,但是又不会将整个out目录清空,这样可以加快编译目标的构建速度make LOCAL_MODULE编译一个指定的模块,LOCAL_MODULE 为模块的名称,这种编译方法通常运用在整个Android工程没有构建,但是想快速编译一个模块时可以使用,可以加快单个模块构建速度make targetswill print a list of all of the LOCAL_MODULE names you can make.make libandroid_runtime编译所有JNI framework内容。make framework编译所有Javaframework内容(做Android framework开发的小伙们对这条命令应该是再熟悉不过的了)。make services编译系统服务和相关内容make bootimage编译生成boot.imgmake recoveryimage编译生成recovey.imgmake cacheimage编译生成cache.imgmake systemimage编译生成system.imgmake vendorimage编译生成vendor.imgmake superimage编译生成superi.img
对上述的make命令有几点需要注意:
1.可能在不同的Android版本有不同表现,且有的可能已经不支持了
2.读者最好对于每个make编译命令,自行使用一番,然后慢慢品尝
有过一定Android开发经验的读者应该知道Android最初是用Android.mk配置来编译源码的(这里的Android.mk本质上有点类似Makefile文件)。但是随着Android版本的迭代,源码工程文件越来越大,包含的模块越来越多,而以Android.mk组织的项目编译花费的时间越来越多。面对这个严峻的问题,Android的妈咪谷歌终于在在Android7.0开始引入了ninja编译系统。相对于make来说ninja在大的项目管理中速度和并行方面有突出的优势,因此Google采用了ninja来取代之前使用的make。由于Android.mk的数量巨大且复杂,不可能把所有的Android.mk改写成ninja的构建规则,因此Google搞了个kati工具,用于将Androd.mk转换成ninja的构建规则文件build.ninja,再使用ninja来进行构建工作。
Android编译的发展依然没有停止进化,果不其然Android8.0开始,Google引入了Android.bp文件来替代之前的Android.mk文件,Android.bp只是纯粹的配置文件,不包括分支、循环等流程控制,本质上就是一个json配置文件。同时还引入Soong这个工具,用于将Android.bp转换为ninja的构建规则文件build.ninja,再使用ninja来进行构建工作。但之前的模块全部是用Android.mk来定义的,google不可能一下子把所有模块都修改成Android.bp,只能逐步替换。无论是Android.mk还是Android.bp最后都是转化成ninja的构建规则,再进行编译的。
如果你对上述的概述,还是觉得太啰嗦了,没有关系。这里我们用更加简单的文字来整体来概括一下Android build系统随着Android版本相应的发展演变过程:
通过前面的章节,我们简单介绍了Android编译系统的发展史!读者会发现,其中突然一下子冒出了许多的概念,这里我们先暂且不对其中涉及的概念深入了解,我们先说说上述几个概念Soong、Blueprint、Kati、Ninja之间的关系,它们之间的关系图如下:
上图是一个静态的整体的关系图,但是在Android源码工程构建过程中的它们之间可以使用如下的转换关系图来表示:
关系图已经给各位看官摆出来了,那么它们之间的转换关系是怎么来的呢,这里我们来说说:
不容易啊,这里我们对涉及到Ninja, kati, Soong, bp关系搞清楚了(各种三角恋)!那么关于它们的概念,接下来我们也得简单介绍介绍,安排上才行!
Kati是专为Android开发的一个基于Golang和C++的工具,主要功能是把Android中的Android.mk文件转换成 Ninja文件。代码路径是build/kati,编译后的产物是ckati。
Kati代码是开源的,可以把它clone下来,如果感兴趣可以查看下其实现原理
这里我们构建一个通过Android.mk配置的LOCAL_MODULE模块,然后通过top命令就可以查看在编译的过程中执行了ckati的命令。
ninja是一个编译框架,会根据相应的ninja格式的配置文件进行编译,但是ninja文件一般不会手动修改,而是通过将Android.bp文件转换成ninja格文件来编译。
Android.bp的出现就是为了替换Android.mk文件。而bp跟mk文件不同,它是纯粹的配置,没有分支、循环等流程控制,不能做算数逻辑运算。如果需要控制逻辑,那么只能通过Go语言编写。Android的妈咪谷歌为了让开发者能更加的快速掌握Android.bp特意提供了androidmk命令(关于它的详细介绍可以参见博客Android.bp入门指南之Android.mk转换成Android.bp,这里就不过多的戏说了)用于Android.mk转换成Android.bp使用,如下转换命令:
$ androidmk Android.mk > Android.bp
1
Soong类似于之前的Makefile编译系统的核心,负责提供Android.bp语义解析,并将之转换成Ninja文件。Soong还会编译生成一个androidmk命令,用于将Android.mk文件转换为Android.bp文件,不过这个转换功能仅限于没有分支、循环等流程控制的Android.mk才有效。
Blueprint是生成、解析Android.bp的工具,是Soong的一部分。Soong负责Android编译而设计的工具,而Blueprint只是解析文件格式,Soong解析内容的具体含义。Blueprint和Soong都是由Golang写的项目,从Android 7.0,prebuilts/go/目录下新增Golang所需的运行环境,在编译时使用。并且因为Soong和Blueprint是Google谷歌为Android.bp特别定制的工具,所以不需要要摘出来单独来操作。
通过前面的章节我们了解了Android编译环境的构建和编译的发展史(前面的章节都是为了后面章节服务做的整体铺垫),那么从本章节开始就要契合本篇博客的主题了将重点分析Android O之后高阶版本的编译的不同之处了。在本篇的博客开始就有说到过是以高通版本的Android R为基线来进行分析的。所以在开始本章节的博客前,有两个知识点需要提前介绍下,一个是Android Q以及之后的动态分区概念,另外一个就是高通Q之后引入的QSSI(或者qssi)的概念!
动态分区是Android的用户空间分区系统。使用此分区系统,您可以在无线下载(OTA)更新期间创建、销毁分区或者调整分区大小。借助动态分区,供应商无需担心各个分区(例如system、vendor和product)的大小。取而代之的是,设备分配一个super分区,其中的子分区可动态地调整大小。单个分区映像不再需要为将来的OTA预留空间。相反,super中剩余的可用空间还可用于所有动态分区(关于动态分区详见谷歌官方Android实现动态分区)。
QSSI 是 Qualcomm Single System Image 的缩写,并且高通平台从Android Q开始支持。高通的官方文档对此的解释是引入QSSI的概念是为了解决Android碎片化问题,把system.img和vendor.img进一步拆分。
并且其编译也和Android原生编译有差别,其差别如下:
高版本Android非QSSI特性的编译流程,依然和以前的版本Android编译变化不大,通常是如下的步骤(这个也是读者最为熟悉的了):
source build/envsetup.sh
lunch xx-userdebug
make
123
这里需要注意的的是通用版本的Android还是可以直接通过make相关的分区进行直接编译的,譬如make superimage或者直接执行make编译
通过前面看到QSSI特性的固件编译流程也和通用版本的有一定的区别,这里的编译分为两种模式,第一种Android的标准编译模式,另外一种就是高通提供的编译脚本。
source build/envsetup.sh
1
lunch qssi-userdebug
make target-files-package
12
lunch xx-userdebug
make target-files-package
12
source build/envsetup.sh
lunch XX-userdebug
./build.sh dist -j32
123
source build/envsetup.sh
lunch xx-userdebug
./build.sh dist qssi_only -j32
123
source build/envsetup.sh
lunch xx-userdebug
./build.sh dist merge_only -j32
123
source build/envsetup.sh
lunch xx-userdebug
./build.sh vendorimage dist target_only -j32
1234
由于Android Q版本以及以上将system和vendor分区合并为super分区,无法通过adb reboot bootloader模式单独刷动态分区里面的img,例如system,vendor,product,odm等镜像,只能刷super.img和其他的分区,但是super.img分区动辄几个G的大小,每次刷机几乎都要等上个好大几分钟,这个酸爽你懂的。
这里有一点需要注意并不是说真的在bootloader模式下不能刷入system分区镜像,可以看到下面的截图是可以刷入的,但是只是镜像没有真的烧录生效,所以读者在实际开发中一定不要做无用功,否则一直烧录,会发现咋咋我修改的东西没有生效呢!
那有没有方法单刷呢,当然有了,不然我也不会多提一句不是。我们可以在fastboot模式下可以单独刷动态分区里面的img,其方法如下:
#推荐进入fastboot模式刷机:
adb reboot fastboot
fastboot getvar is-userspace
is-userspace: yes
Finished. Total time: 0.002s
fastboot flash vendor vendor.img
fastboot flash system system.img
fastboot flash vbmeta vbmeta.img
fastboot flash vbmeta_system vbmeta_system.img
#fastbootd是用户空间的代码,因为动态的逻辑分区只能在应用空间识别
12345678910
1.如果是在linux下fastboot刷机出现权限问题,需要将fastboot的所有者属性改成root
sudo chown root:root fastboot
sudo chmod +s fastboot
2.如果是在windows环境下使用fastboot,很大概率可能不识别fastboot,此时推荐下载360的手机助手借助它安装对应的驱动,这样就能进行相关的识别了,此处是个人经验
3.并且我们在实际操作中需要,注意fastboot是否处于锁定模式,否则会报一下错误(我调试的终端,已经强制锁定了,所以那怕我强制执行了解锁也一直报remote: 'Command not available on locked devices’的异常)
4.我们来使用一个可以动态解锁的fastboot,大功告成!
对于从事Android偏向现Framework开发的读者来说,编译Android的Framework层是再常见不过的操作了,在Android R之上的Framework的编译已经和之前有所不同,具体参见下面解释:
make -j8 framework
make -j8 services
12
make -j8 framework-minus-apex
make -j8 services
12
对于Android开发者来说,当然希望自己在服务器上分配到的资源越多越好,特别是固态硬盘的容量,但是公司当开发者过多的时候,公司就会尽量压缩ssd固态硬盘的盘符空间,只给分配了250G的固态硬盘容量,如果是同时开发编译几个Android低版本的源码,那肯定是够了,但是切到Android R之后,250G肯定是远远不够的,这里我们通过du命令查看一下Android R编译出来之后的情况,如下:
ityuan@pd:~/ssd$ du -sh *
260G qcm2290
ityuan@pd:~/ssd$
ityuan@pd:~/ssd$ ls
qcm2290
ityuan@pd:~/ssd$ cd qcm2290/
ityuan@pd:~/ssd/qcm2290$ du -sh *
8.0K about.html
0 Android.bp
85M art
64M bionic
218M bootable
0 bootstrap.bash
24M build
0 build.sh
3.4M compatibility
1.7G cts
26M dalvik
222M developers
150M development
635M device
4.0K disregard
7.1G external
1.9G frameworks
81M hardware
1.4G kernel
87M libcore
420K libnativehelper
4.0K Makefile
186G out
1.1G packages
3.4G xxxdroid
896K pdk
16M platform_testing
32G prebuilts
12K product
4.0K readme.md
232K resources.arsc
30M sdk
444K shortcut-fe
8.0K sync.sh
704M system
408M test
16K TEST_BP
9.7M toolchain
704M tools
6.9G vendor
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849
这里可以看到一个Android R的源码工程就将整个的ssd硬盘空间占满了,那么有什么办法精简呢。当然有了,我们可以对Android 的out目录进行软连接,链接到其它的分区之中。说干就干!下面我来记录一下具体的步骤:
ityuan@pd:~$ mkdir out
ityuan@pd:~$ ls out/
ityuan@pd:~$
123
ityuan@pd:~$ ln -s /home/tangkw/out /home/tangkw/ssd/qcm2290/out
ityuan@pd:~$ cd ssd/qcm2290/
ityuan@pd:~/ssd/qcm2290$ ll out
lrwxrwxrwx 1 tangkw pd 16 Jan 5 16:54 out -> /home/tangkw/out/
ityuan@pd:~/ssd/qcm2290$
12345
并且在创建软链接中强烈建议使用绝对路径,否则会提示Too many levels of symbolic links的错误
ityuan@pd:~$ cd out/
ityuan@pd:~/out$ ls -l
total 12684
-rw-r--r-- 1 ityuan pd 0 Jan 5 16:56 Android.mk
-rw-r--r-- 1 ityuan pd 160 Jan 5 16:59 build-bengal-cleanspec.ninja
-rwxr-xr-x 1 ityuan pd 10 Jan 5 16:56 build_date.txt
-rw-r--r-- 1 ityuan pd 10 Jan 5 16:56 build.trace.gz
-rwxr-xr-x 1 ityuan pd 1 Jan 5 16:56 casecheck.txt
-rwxr-xr-x 1 ityuan pd 1 Jan 5 16:56 CaseCheck.txt
-rw-r--r-- 1 ityuan pd 0 Jan 5 16:56 CleanSpec.mk
drwxr-xr-x 2 ityuan pd 4096 Jan 5 16:59 empty
-rw-r--r-- 1 ityuan pd 39 Jan 5 16:59 env-bengal-cleanspec.sh
-rw-r--r-- 1 ityuan pd 0 Jan 5 16:56 error.log
drwxr-xr-x 3 ityuan pd 4096 Jan 5 17:00 host
-rwxr-xr-x 1 ityuan pd 4309565 Jan 5 16:56 microfactory_Linux
-rwxr-xr-x 1 ityuan pd 121 Jan 5 16:59 ninja-bengal-cleanspec.sh
-rw-r--r-- 1 ityuan pd 0 Jan 5 16:56 ninja_build
drwxr-xr-x 6 ityuan pd 4096 Jan 5 16:59 soong
-rw-r--r-- 1 ityuan pd 19743 Jan 5 17:00 soong.log
-rwxr-xr-x 1 ityuan pd 8580360 Jan 5 16:56 soong_ui
drwxr-xr-x 3 ityuan pd 4096 Jan 5 16:59 target
-rw-r--r-- 1 ityuan pd 24970 Jan 5 17:00 verbose.log.gz
ityuan@pd:~/out$
1234567891011121314151617181920212223
ityuan@pd:~/ssd$ du -sh *
75G qcm2290
ityuan@pd:~/ssd$ cd qcm2290/
ityuan@pd:~/ssd/qcm2290$
ityuan@pd:~/ssd/qcm2290$
ityuan@pd:~/ssd/qcm2290$ du -sh *
8.0K about.html
0 Android.bp
85M art
64M bionic
218M bootable
0 bootstrap.bash
24M build
0 build.sh
3.4M compatibility
1.7G cts
26M dalvik
222M developers
150M development
635M device
4.0K disregard
7.1G external
1.9G frameworks
81M hardware
1.4G kernel
87M libcore
420K libnativehelper
4.0K Makefile
0 out
1.1G packages
3.4G xxxdroid
896K pdk
16M platform_testing
32G prebuilts
12K product
4.0K readme.md
232K resources.arsc
30M sdk
444K shortcut-fe
8.0K sync.sh
704M system
408M test
16K TEST_BP
9.7M toolchain
704M tools
6.9G vendor
ityuan@pd:~/ssd/qcm2290$
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647
大功告成,当然如果读者是土豪公司,那这个就可以跳过不看了。
在前面我们简单说了下动态分区的概念,即在Android Q以及以后得编译包中,我们找不到了对应的system,vendor等img文件,但是多了一个super.img,system,vendor,product,ODM合并为super分区,这个就是动态分区了。简单来说就是为了在ota的时候能够灵活创建分区和修改分区大小,将system,vendor,odm,product合并成super分区,并在super分区上预留出一定量的free space,这样就可以动态调整这些分区的大小,解决了ota的时候分区不足,以及调整分区的风险.。
当OTA升级之后,需要重新调整分区大小:
码字不易,觉得对你有帮助的话记得收藏顺便小编点个赞哦 ^_^
*请认真填写需求信息,我们会在24小时内与您取得联系。