1、Android 图片加载性能优化总结 一、Android Bitmap加载大尺寸图片优化: 压缩原因: 1.imageview大小如果是200*300那么加载个2000*3000的图片到内存中显然是浪费可耻滴行为; 2.最重要的是图片过大时直接加载原图会造成OOM异常(out of memory内存溢出) 所以一般对于大图我们需要进行下压缩处理 看不懂英文的话木有关系,本篇会有介绍 主要处理思路是: 1.获取图片的像素宽高(不加载图片至内存中,所以不会占用资源) 2.计算需要压缩的比例 3.按将图片用计算出的比例压缩,并加载至内存中使用 官网大图片加
2、载教程(上面网址里的)对应代码就是: /** * 获取压缩后的图片 * @param res * @param resId * @param reqWidth 所需图片压缩尺寸最小宽度 * @param reqHeight 所需图片压缩尺寸最小高度 * @return */ public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) { // 首先不加载图片,仅
3、获取图片尺寸 final BitmapFactory.Options options = new BitmapFactory.Options(); // 当inJustDecodeBounds设为true时,不会加载图片仅获取图片尺寸信息 options.inJustDecodeBounds = true; // 此时仅会将图片信息会保存至options对象内,decode方法不会返回bitmap对象 BitmapFactory.decodeResource(res, resId, options); // 计算压缩比例,如inS
4、ampleSize=4时,图片会压缩成原图的1/4 options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); // 当inJustDecodeBounds设为false时,BitmapFactory.decode...就会返回图片对象了 options. inJustDecodeBounds = false; // 利用计算的比例值获取压缩后的图片对象 return BitmapFactory.decodeResource(res, resId,
5、options); } 代码详解: 核心方法是BitmapFactory.decode...(...., options) ...的意思是此外还有一系列的decodeFile/decodeStream等等方法,都是利用options灵活解析获取图片, 只不过解析图片的来源不同罢了,比如网络图片获取,一般就是解析字节流信息然后decode获取图片实例 Options是图片配置信息,参数详细介绍下: inJustDecodeBounds 是否只解析边界 设为true时去decode获取图片,只会加载像素宽高信息 设为false时decode则会完全
6、加载图片 inSampleSize 压缩比例 比如原图200*300,如果值是2时会压缩成100*150; 是4则图片压缩成50*75最好是2的幂数,比如2 4 8 16 ..... outHeight 图片原高度 outWidth 图片原宽度 其他参数自行研究,这里暂时只用到这几个 decodeSampledBitmapFromResource方法内的三段代码对应上面的三步流程 难点在于中间那步,压缩比例的计算,官网同样提供了个calculateInSampleSize方法 其中reqWidth和reqHeight是所需图片限定最小宽高值 /** * 计算压
7、缩比例值 * @param options 解析图片的配置信息 * @param reqWidth 所需图片压缩尺寸最小宽度 * @param reqHeight 所需图片压缩尺寸最小高度 * @return */ public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { // 保存图片原宽高值 final int height = options. o
8、utHeight; final int width = options. outWidth; // 初始化压缩比例为1 int inSampleSize = 1; // 当图片宽高值任何一个大于所需压缩图片宽高值时,进入循环计算系统 if (height > reqHeight || width > reqWidth) { final int halfHeight = height / 2; final int halfWidth = width /
9、2; // 压缩比例值每次循环两倍增加, // 直到原图宽高值的一半除以压缩值后都~大于所需宽高值为止 while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth) { inSampleSize *= 2; } } return inSampl
10、eSize; } 利用此方法获取到所需压缩比例值,最终获取到压缩后的图片~ 以上代码能够看懂的话,下面这段/*扯淡*/可以跳过 逻辑是将原图宽高一半一半的缩减,一直减到宽高都小于自己设定的限定宽高时为止,测试的时候问题来了 原图400*300,我限定值200*150,if满足进入,while循环第一次,400/2/1=200不满足>的条件~结束循环, 最终返回了个inSampleSize=1给我 马丹我限定值正好是原图的一半啊,你应该返回给我2啊~你特么最后返回个1给我,那压缩处理后的图还是400*300!!! 当我将限定值稍微改一下变成195*145稍微降
11、低一点点时~if满足进入,while循环第一次,400/2/1>195满足~ 然后压缩比例1*2变成了2,在下一次while循环时不满足条件结束,最后返回比例值2~ 满足压缩预期 官网的这个方法是: 将图片一半一半的压缩,直到压缩成成大于所需宽高数的那个最低值 大于~不是大于等于,所以就会出现我上面那种情况,我觉得方法不是太好= = 能满足压缩的需求,但是压缩的比例不够准确~ 所以最好改成大于等于,如下(个人意见,仅供参考,在实际压缩中很少遇到恰巧等于的这个情况,所以>和>=差别也不大额~看我这扯扯淡就当对计算比例的逻辑加深个理解吧) while ((halfHeight / i
12、nSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth) { inSampleSize *= 2; } 优化: 还是上面例子,如果限定了200*150,而原图是390*290会是个啥情况? 还是第一次while循环,390/2/1结果是195不满足>200的情况,结束循环,比例值为1,最后图片压缩成400*300 虽然压缩一次以后没有满足大于所需宽高,但是和所需宽高很接近啊!!! 能不能做一个获取压缩成最接近所需宽高数的比例值呢? 我也不知道= = 回头可以慢慢研
13、究, 这个"接近"的定义比较模糊,不好掌握~ 找了几个有名的图片加载开源框架发现也都没有这种处理- -不知道是这样设计是不需要呢,还是没啥用呢 以上,图片的像素大小已经做了缩放,但是图片的大小除了和像素有关,还和色彩样式有关不同的样式决定了图片单个像素占的字节数 比如,图片默认的色彩样式为ARGB_8888,每个像素占4byte(字节)大小 可以看到一共有四种色彩样式 ALPHA_8 每个像素只要1字节~可惜只能代表透明度,没有颜色属性 ARGB_4444 每个像素要2字节~带透明度的颜色~可惜官方不推荐使用了 ARGB_8888 每个像素要4字节~带透
14、明度的颜色, 默认色样 RGB_565 每个像素要2字节~不带透明度的颜色 默认为ARGB_8888,如果想丧心病狂的继续减少图片所占大小~不需要透明度参数的话, 那就可以把色彩样式设为RGB_565 设置方法是在BitmapFactory.decode..获取图片事例时 修改配置参数的inPreferredConfig 参数 opts.inPreferredConfig = Bitmap.Config. RGB_565 ; 想亲自撸一撸试一试压缩图片了吧? 要注意点问题,如果用res包下图片测试的话,你会发现有图片尺寸有点混乱 那是因为在drawable-
15、dpi文件夹中的图片会根据对应对应的屏幕密度值不同自动进行一定的缩放, 比如放在drawable-hdpi里的图片,直接不经过压缩BitmapFactor.decode..出来,会发现bitmap的宽高值是原图的2/3, 测试的时候图片记得放在drawable包下(没有的话自己res下新建一个),否则你会被奇怪的宽高值弄凌乱的,具体变化原因参考源代码处理,或者网上搜搜看。还有就是BitmapFactory.decodeStream方法会偶尔解析图片失败(好像是安卓低版本的一个bug),此时推荐做法是将流转换为字节流处理,然后利用decodeByteArray方法获取图片。 二、
16、Android 加载多张图片的缓存处理 一般少量图片是很少出现OOM异常的,除非单张图片过~大~ 那么就可以用教程一里面的方法了 通常应用场景是listview列表加载多张图片,为了提高效率一般要缓存一部分图片,这样方便再次查看时能快速显示~不用重新下载图片 但是手机内存是很有限的~当缓存的图片越来越多,即使单张图片不是很大,不过数量太多时仍然会出现OOM的情况了~ 本篇则是讨论多张图片的处理问题 图片缓存的一般处理是 1.建立一个图片缓存池,用于存放图片对应的bitmap对象 2.在显示的时候,比如listview对应适配器的getView方法里进行加载图片的工作, 先
17、从缓存池通过url的key值取,如果取到图片了直接显示, 如果获取不到再建立异步线程去下载图片(下载好后同时保存至图片缓存池并显示) 但是缓存池不能无限大啊~不然就会异常了,所以通常我们要对缓存池进行一定控制 需要有两个特性: 总大小有个限制,不然里面存放无限多的图片时会内存溢出OOM异常 当大小达到上限后,再添加图片时,需要线程池能够智能化的回收移除池内一部分图片,这样才能保证新图片的显示保存 异步线程下载图片神马的简单,网上异步下载任务的代码一大堆,下载以后流数据直接decode成bitmap图片即可 难点在与这个图片缓存池的设计,现在网上的
18、实现主要有两种 1.软引用/弱引用 2.LruCache ----------------------------------------------------------------------- 拓展: java中4种引用分类 强引用 平常使用的基本都是强引用,除非主动释放(图片的回收,或者==null赋值为空等),否则会一直保存对象到内存溢出为止~ 软引用 SoftReference 在系统内存不够时,会自动释放部分软引用所指对象~ 弱引用 WeakReference 系统偶尔回收扫描时发现弱引用则释放对象,
19、即和内存够不够的情况无关,完全看心情~ 虚引用 不用了解,其实我也不熟悉 框架基本都比较爱用这个软应用保存图片作为缓存池,这样在图片过多不足时,就会自动回收部分图片,防止OOM 但是有缺点,无法控制内存不足时会回收哪些图片,如果我只想回收一些不常用的,不要回收常用的图片呢? 于是引入了二级缓存的逻辑 即设置两个缓存池,一个强引用,一个软引用, 强引用保存常用图片,软应用保存其他图片~ 强引用因为不会自动释放对象,所以大小要进行一定限定,否则图片过多会异常, 比如控制里面只存放10张图片, 然后每次往里面添加图片的时候,检查如果数量超过10张这个阀值,临界点
20、值时,
就移除强引用里面最不常用的那个图片,并将其保存至软应用缓存池中~
整个缓存既作为一个整体(一级二级缓存都是内存缓存~每次显示图片前都要检查整个缓存池中有没有图片)
又有一定的区分(只回收二级缓存软引用中图片,不回收一级缓存中强引用的图片~)
代码实现
软应用缓存池类型作为二级缓存:
HashMap
21、nkedHashMap
22、
23、velCache.put(eldest.getKey(),
new SoftReference
24、从二级缓存中取出来的,则会存到一级缓存池最前端并检测,如果超过阀值,则将最不常用的一个对象移动到二级缓存中 3.如果缓存中没有,那就网上下载图片,下载好以后保存至一级缓存中,同样再进行检测是否要移除一个对象至二级缓存中 结合现实例子理解下(如果以上逻辑了解可以跳过): 美国篮球,比如有一个最高水平的联赛NBA,还有一个次一级的联赛NBDL~ 一级联赛NBA的排名按最近一次拿冠军的时间由近到远排列, 我们规定,每一季度比赛都要产生一个冠军,冠军可能是已有的任何一个队伍也可能是一个民间来的新队伍~ 而当一个队伍获取冠军的时候就给他加到一级队伍NBA里~ 由于是最近一次拿冠军,所
25、以加进去的时候也是排名第一 NBA作为最高水平,我们对数量是有限制的,所以每次有新冠军产生的时候我们都做一次检测, 如果队伍总数量超过20支,那么就移除排名最低即离上次获冠军时间最长的那个最差队伍. 如果每季度比赛拿冠军相当于一次图片使用操作,那上面三种情况就对应我们例子中的: 1.NBA的队伍拿冠军,相当于这个队伍排名变成了第一名~但NBA队伍总数不变,没有新加入来的 2.NBDL二级联赛拿冠军,则加入到NBA里面,且变成了第一名~由于NBA队伍相当于增加了一个,那就要检测一下是否超过20支并将最差成绩的挤到NBDL中 3.民间来大神了虐了全部的队伍拿了冠军,那直接加
26、入NBA然后变成第一名,同样,检测NBA球队数量判断是否要挤出去一队 NBDL球队相当于软应用的二级缓存池, 不限定数量~ 多少都可以, 直到美国篮联维护全部NBA NBDL球队的资金不够了(相当于图片过多应用内存不足了) 则自动解散一部分球队,落入民间,直到下一次获取总冠军再加入进来(相当于图片从缓存中移除了,下次使用要重新下载)~ 那NBA就相当于一级缓存,经常拿冠军(相当于高频率使用的图片),那我们就不想因为资金不足随机解散几个球队恰好就解散了NBA队伍, 则规定资金不够时只解散二级联赛NBDL的队伍~因为他们获取比赛几率低一点~ 民间队伍存在与联赛系统之外(相当于不存
27、在缓存中的图片), 而任何一个NBA NBDL联赛球队我们都可以理解为都是民间晋级过来的~ 只不过从民间获取总冠军并加入联赛需要一个取名字啊登记啊等等的办手续过程(下载图片),比较麻烦,所以我们要尽可能的少办手续~ 而联赛队伍(包括NBA NBDL)获取总冠军则不需要麻烦的手续,可以直接参加比赛去拿冠军(直接获取显示) 两个联赛,一个常用的限定数量,一个不常用的不限定数量,但是资金不足时自动回收部分二级球队~ 相当于图片的二级缓存 Disk缓存 可以简单的理解为将图片缓存到sd卡中~ 由于内存缓存在程序关闭第二次进入时就清空了,对于一个十分常用的图片比如头像一
28、类的~ 我们希望不要每次进入应用都重新下载一遍,那就要用到disk缓存了,直接图片存到了本地,打开应用时直接获取显示~ 网上获取图片的大部分逻辑顺序是 内存缓存中获取显示(强引用缓存池->弱引用缓存池) -> 内存中找不到时从sd卡缓存中获取显示 -> 缓存中都没有再建立异步线程下载图片,下载完成后保存至缓存中 按照获取图片获取效率的速度,由快到慢的依次尝试几个方法 以文件的形式缓存到SD卡中,优点是SD卡容量较大,所以可以缓存很多图片,且多次打开应用都可以使用缓存, 缺点是文件读写操作会耗费一点时间, 虽然速度没有从内存缓存中获取速度快,但是肯定比重新下载一张图片
29、的速度快~而且还不用每次都下载图片浪费流量~ 所以使用优先级就介于内存缓存和下载图片之间了 注意: sd卡缓存一般要提前进行一下是否装载sd卡的检测, 还要检测sd卡剩余容量是否够用的情况 程序里也要添加注明相应的权限 使用LruCache处理图片缓存 以上基本完全掌握了,每一张图最好再进行一下教程(一)里面介绍的单张缩放处理,那基本整个图片缓存技术就差不多了 但随着android sdk的更新,新版本其实提供了更好的解决方案,下面介绍一下 摘取段对软引用的介绍 Avoid Soft References for Caching In practice,
30、 soft references are inefficient for caching. The runtime doesn't have enough information on which references to clear and which to keep. Most fatally, it doesn't know what to do when given the choice between clearing a soft reference and growing the heap. The lack of information on the value to yo
31、ur application of each reference limits the usefulness of soft references. References that are cleared too early cause unnecessary work; those that are cleared too late waste memory. Most applications should use an android.util.LruCache instead of soft references. LruCache has an effective evicti
32、on policy and lets the user tune how much memory is allotted. 简单翻译一下 我们要避免用软引用去处理缓存 在实践中,软引用在缓存的处理上是没有效率的。运行时没有足够的信息用于判断哪些引用要清理回收还有哪些要保存。 最致命的,当同时面对清理软引用和增加堆内存两种选择时它不知道做什么。 对于你应用的每一个引用都缺乏有价值的信息,这一点限制了软引用让它的可用性十分有限。 过早清理回收的引用导致了无必要的工作; 而那些过晚清理掉的引用又浪费了内存。 大多数应用程序应该使用一个android.util。Lr
33、uCache代替软引用。LruCache有一个有效的回收机制,让用户能够调整有多少内存分配。 简而言之,直接使用软引用缓存的话效果不咋滴~推荐使用LruCache level12以后开始引入的,为了兼容更早版本,android-support-v4包内也添加了一个LruCache类, 所以在12版本以上用的话发现有两个包内都有这个类,其实都是一样的~ 那么这个类是做啥的呢~这里是官方文档 LRU的意思是Least Recently Used 即近期最少使用算法~ 眼熟吧,其实之前的二级缓存中的那个强引用LinkedHashMap的处理逻辑其实就是一个LRU算法 而
34、我们查看LruCache这个类的源代码时发现里面其实也有一个LinkedHashMap,大概扫了眼,逻辑和我们之前自己写的差不多 核心功能基本都是: 当添加进去新数据且达到限制的阀值时,则移除一个最少使用的数据 根据这个新的类做图片加载的话,网上大部分的做法还是二级缓存处理 只不过将LinkedHashMap+软引用 替换成了LruCache+软引用 都是二级缓存,强引用+软引用的结构~因为LruCache和LinkedHashMap都是差不多的处理逻辑 没有移除软引用的使用,而是将两者结合了起来 根据官网的介绍来看其实软引用效果不大,二级缓存的处理的话,虽然能提高一
35、点效果,但是会浪费对内存的消耗~ 所以要不要加个软引用的二级缓存,具体选择就看自己理解和实际应用场景了吧 LruCache我理解是牺牲一小部分效率,换取部分内存~我个人也是倾向于只使用LruCache的实现不用软引用了,也比较简单~ 结合前面举得例子可以理解为,直接取消NBDL二级联赛(软引用)~ 这样能省下好大一笔钱(内存)然后投资联赛其他方面(处理其他逻辑) 并扩展下NBA一级联赛(LruCache)的规模~保证复用效率 LruCache的具体用法 之前对LinkedHashMap有了一定了解了,其实LruCache也差不多 类似于removeEldest
36、Entry方法的回收逻辑,在这个类里面已经处理好了 一般我们只需要处理对阀值的控制就行了 阀值控制的核心方法是sizeOf()方法, 该方法的意思是返回每一个value对象的大小size~ 默认返回的是1~即当maxSize(通过构造方法传入)设为10的时候就相当于限制缓存池只保留10个对象了~ 和上面LinkedHashMap的例子一个意思 但是由于图片的大小不一,一般限定所有图片的总大小更加合适,那我们就可以对这个sizeOf方法进行复写 @Override protected int sizeOf(String key, Bitmap value) {
37、 return value.getRowBytes() * value.getHeight(); } 这样的话,相当于缓存池里每一个对象的大小都是计算它的字节数,则在新建LruCache的时候传入一个总size值就行了, 一般传入应用可用内存的1/8大小 本篇是讨论对于图片数量的控制问题, 再结合教程(一)中的方法对每一张图片进行相应处理~OOM的情况基本就可以避免了~ 内容比较多,有疑惑的地方或者不足的地方大家可以一起探讨 三、Android开源图片框架分析 主要介绍这三个框架,都挺有名的,其他的框架估计也差不多了 Android-Universal-Image
38、Loader ImageLoader Volley(综合框架,包含图片部分) 扯淡时间,可以跳过这段 这些开源框架的源码还是挺复杂的,本人技术有限,有部分分析不对的地方或者不足的地方希望大家一起讨论, 由于有大量的源代码分析,所以造成内容较多且比较杂乱,重点部分我会用红字或者加粗字体标出来,如果没有耐心全部看完的话可以挑部分重点看,可以跳过的部分我也会有说明,大家可以选择性阅读 其实框架的实现和我们教程(一)(二)章的也差不多,只不过在此基础上进行了一些优化,核心部分是几乎没有区别的 由于篇幅有限,暂时只介绍第一个框架(其他其实也都差不多),另外两个在后续教程中详细介绍
39、 首先介绍universal-image-loader(以下简称UIL) 是github社区上star最多的一个项目,可以理解为点赞最多滴,应该是最有名的一个~国内很多知名软件都用它包括淘宝京东聚划算等等 框架其实都差不多,这里详细介绍下使用者最多的一个~之后再分析其他框架时就简单说明一些特性了,重复相似部分不再赘述了~ 使用比较简单,这个框架的github主页上也有快速使用的步骤 基本上就是在application类里的oncreate方法(整个程序开始时运行一次)中进行一下简单的基本配置, 可以根据需要自行进行设定,懒得设定的话框架也提供了一个默认的配置,调用一个方法即可
40、 基本上是配置一些类似于:缓存类型啊,缓存上限值啊,加载图片的线程池数量啊等等 此外在页面内显示的时候还要设置一个显示配置这个配置不同于基本配置,一个项目里可以根据需要创建多个配置对象使用, 这个配置就比较具体了,可以设置是否使用disk缓存(存到sd卡里一般),加载图片失败时显示的图片,默认图片,图片的色彩样式等 配置好以后,就是简单的使用了,创建一个图片加载对象,然后一行代码搞定显示图片功能~参数一般是入你需要显示的图片url和imageview对象 大部分框架其实都这一个尿性,配置稍微麻烦点,但是使用时一般只需要一行,显示方法一般会提供多个重载方法,支持不同需要~
41、由于不是框架使用教程,所以~ 下面结合之前两章的内容着重分析下框架对于单张图片的压缩处理,和多图缓存池的处理 单张图片的压缩 (业界良心的小技巧: 框架肯定也是基于android sdk的, 所以获取图片缩放实例的话,option的inSampleSize参数是肯定要使用的, 我们直接crtl+h打开搜索页面,选择file search, 然后file name patterns选择*.java,即搜索所有java文件,最后在containing text上输入想搜索的内容,这里我们要搜inSampleSize,搜索结果里随便扫一扫,发现BaseImageDecoder里面有个靠谱的
42、方法如下) [mw_shl_code=java,true]protected Options prepareDecodingOptions(ImageSize imageSize, ImageDecodingInfo decodingInfo) { ImageScaleType scaleType = decodingInfo.getImageScaleType(); int scale; if (scaleType == ImageScaleType. NONE) { scale = ImageSizeUtils. computeMinImageSampleSize(image
43、Size); } else { ImageSize targetSize = decodingInfo.getTargetSize(); boolean powerOf2 = scaleType == ImageScaleType.IN_SAMPLE_POWER_OF_2 ; scale = ImageSizeUtils. computeImageSampleSize(imageSize, targetSize, decodingInfo.getViewScaleType(), powerOf2); } if (scale > 1 && loggingEnabled) { L.
44、d(LOG_SUBSAMPLE_IMAGE, imageSize, imageSize.scaleDown(scale), scale, decodingInfo.getImageKey()); } Options decodingOptions = decodingInfo.getDecodingOptions(); decodingOptions. inSampleSize = scale; return decodingOptions; } 简单扫一眼,ImageSize,ImageDecodingInfo神马的明显是自定义的一个类,不要管,我们先挑重点部分看 Op
45、tions decodingOptions = decodingInfo.getDecodingOptions(); decodingOptions.inSampleSize= scale; 方法最后两行可以看出来ImageDecodingInfo类里面保存了一个option对象~ 通过一个方法对其中的inSampleSize进行了设置~ ImageScaleType.NONE 什么意思,扫了眼注释,是图片无压缩~那我们看else里面的需要压缩的computeImageSampleSize方法 方法是具体如何处理的呢~ 我们再继续跟踪computeImageSampleS
46、ize方法~ (业界良心小技巧:按着ctrl不松左键点击方法或者变量或者类,就可以自动跳转到对应的地方了) 方法的代码如下 /** * Computes sample size for downscaling image size ( srcSize ) to view size (targetSize ). This sample * size is used during * {@linkplain BitmapFactory#decodeStream(java.io.InputStream, android.graphics.Rect, and
47、roid.graphics.BitmapFactory.Options)
* decoding image} to bitmap.
*
* Examples:
*
* srcSize(100x100), targetSize(10x10), powerOf2Scale = true -> sampleSize = 8 * srcSize(100x100), targetSize(10x10), powerOf2Scale = false -> sampleSize = 10 * * srcSize* *48、100x100), targetSize(20x40), viewScaleType = FIT_INSIDE -> sampleSize = 5 * srcSize(100x100), targetSize(20x40), viewScaleType = CROP -> sampleSize = 2 *
49、map. For example, inSampleSize == 4 returns an image that is 1/4 the width/height of the original, and 1/16 * the number of pixels. Any value <= 1 is treated the same as 1. * * @param srcSize Original (image) size * @param targetSize Target (view) size * @param viewScaleType {@linkplain ViewScaleType Scale type} for placing image in view * @param powerOf2Scale true - if sample size be a power of 2 (1, 2, 4, 8, ...) * @return Computed sample size */ public static int computeImageSampleSize(ImageSize srcSize, Image
©2010-2025 宁波自信网络信息技术有限公司 版权所有
客服电话:4009-655-100 投诉/维权电话:18658249818