android 如何分析应用的内存 (一)——内存总览

android 如何分析应用的内存(一)

如何获取当前应用的内存信息

adb shell dumpsys meminfo packagename

获得如下信息


#应用内存使用,单位KB
Applications Memory Usage (in Kilobytes):
Uptime: 621609791 Realtime: 621609791
##
## Uptime(开机时间):uptime指的是自设备开机以来的累计运行时间,但不包括设备
## 处于深度睡眠(Deep Sleep)状态的时间。也就是说,只有在设备处于活动状态(awake)
## 时,uptime才会增加。在Android开发中,uptime通常用于计算运行时间、执行任务的
## 耗时等。

## Realtime(实时时间):realtime指的是自设备开机以来的累计时间,包括设备
## 处于深度睡眠状态的时间。实际上,realtime是一个连续的、不会被暂停的计时器。
## 在Android开发中,realtime通常用于计算超时、延迟任务、定时任务等。


## pid为805,包名为cn.findpiano.piano的内存信息
## 内存的各个名词的解释,见后面
** MEMINFO in pid 805 [cn.findpiano.piano] **
                   Pss  Private  Private  SwapPss     Heap     Heap     Heap
                 Total    Dirty    Clean    Dirty     Size    Alloc     Free
                ------   ------   ------   ------   ------   ------   ------
  Native Heap   107179   107140        0       17   215040   109702   105337
  Dalvik Heap    17709    17696        0       27    39766    15190    24576
 Dalvik Other    14236    14236        0        0
        Stack     3480     3480        0        0
       Ashmem      156      148        0        0
      Gfx dev    16232     8584        0        0
    Other dev       60       28       32        0
     .so mmap    10837     2484     6324       40
    .jar mmap        8        8        0        0
    .apk mmap    18547      200    17160        0
    .ttf mmap       12        0        0        0
    .dex mmap    16568       16    11416        0
    .oat mmap     1455        0      560        0
    .art mmap     4621     4364       20        4
   Other mmap      705       16      312        0
    GL mtrack     4196     4196        0        0
      Unknown     2914     2908        0        2
        TOTAL   219005   165504    35824       90   254806   124892   129913


## 下面是对上面各个细分项目的一个概述
 App Summary
                       Pss(KB)
                        ------
           Java Heap:    22080  ## Java堆大小
         Native Heap:   107140  ## Native堆大小
                Code:    38168  ## 已经加载进地址空间的代码的大小
               Stack:     3480  ## native栈大小
            Graphics:    12780  ## 图形内存大小,如surface的buffer等
       Private Other:    17680  ## 表示没法分类的其他内存区域
              System:    17677  ## 由系统使用的内存,比如android系统服务使用

               TOTAL:   219005       TOTAL SWAP PSS:       90

 Objects
               Views:      547         ViewRootImpl:        0  
## Views的对象个数,ViewRootImpl的个数。一个Window常有一个ViewRootImpl.
## 因此这个过多,则表示创建了太多的Window比如,Dialog的泄漏等


         AppContexts:        5           Activities:        2  
## AppContexts和Activity的对象个数,如果这个过大,表示存在Activity泄漏。
## 如Activty里面存在相互之间的静态引用。而这些Activity又有可能持有大内存对象。


              Assets:        9        AssetManagers:        4  
## Assets和AsstMangers的对象个数。如果过大,则可能存在资源泄漏。
## 可能没有调用相应的close方法


       Local Binders:       59        Proxy Binders:       34  
## Binder的对象个数。如果它快速变大,可能预示Binder的错误使用


       Parcel memory:       25         Parcel count:      102  
## Parcel的对象个数和内存,如果它变很大,则意味着,进程间通信使用了大量的数据,
## 或许应该考虑换一种方案


    Death Recipients:        3      OpenSSL Sockets:        0  
## Death Recipients表示监听远程Binder调用死亡的个数。如果这个数量过大,
## 则有可能预示着Binder对象的泄漏,或者有Binder但是这个是0,则也有问题


            WebViews:        0                                 
## WebView的个数

 SQL
         MEMORY_USED:     4337 ## 已经使用的内存

  PAGECACHE_OVERFLOW:      871          MALLOC_SIZE:      117  
## 表示页面缓存无法满足的字节大小,其中包括sqlite3_malloc的大小,
## sqlite3_malloc分配的大小由MALLOC_SIZE表示

 DATABASES
    ## 1.pgsz页面大小单位是KB 
    ## 2.dbsz 数据库大小单位是KB
    ## 3.Lookaside 中被使用过的内存大小 (Lookaside 是一种内存优化策略,
    ##         可以理解为内存缓存,防止频繁分配内存给系统带来影响)
    ## 4.cache 它表示的是状态缓存。如第一行:128/157/14 表示缓存页命中了128次,
    ##        未命中157次,页个数为14个。 可计算总页数为96/4=24页。
    ##        若未命中次数远远大于命中次数,则可能数据库的建表有很大的问题,
    ##        这回增加查询时间
      pgsz     dbsz   Lookaside(b)          cache  Dbname
         4       96            109     128/157/14  /data/user/0/cn.findpiano.piano/no_backup/androidx.work.workdb
         4        8                         0/0/0    (attached) temp
         4       96             40         3/19/4  /data/user/0/cn.findpiano.piano/no_backup/androidx.work.workdb (1)
         4       56             33         4/50/4  /data/user/0/cn.findpiano.piano/cache/0/record/playrecord.db
         4        8                         0/0/0    (attached) temp
         4       56             65         3/99/4  /data/user/0/cn.findpiano.piano/cache/0/record/playrecord.db (1)
         4       56             81       160/15/3  /data/user/0/cn.findpiano.piano/cache/0/record/playrecord.db (2)
         4       32             94       59/35/12  /data/user/0/cn.findpiano.piano/databases/zx.db
         4       32            103      151/37/14  /data/user/0/cn.findpiano.piano/databases/zx2.db
         4      116             32        56/17/3  /data/user/0/cn.findpiano.piano/databases/cg.db
         4       72             98   3588/1223/25  /data/user/0/cn.findpiano.piano/databases/pushg3.db
         4      356            102 36071/35098/12  /data/user/0/cn.findpiano.piano/databases/find_piano.db
         4        8                         0/0/0    (attached) temp
         4      356            109     55373/28/9  /data/user/0/cn.findpiano.piano/databases/find_piano.db (1)
         4      356             73     55435/25/3  /data/user/0/cn.findpiano.piano/databases/find_piano.db (3)
         4      356             82     55437/68/4  /data/user/0/cn.findpiano.piano/databases/find_piano.db (2)
         4       32             44         9/18/4  /data/user/0/cn.findpiano.piano/databases/gtc3.db
         4       20             47     11277/18/4  /data/user/0/cn.findpiano.piano/databases/dim.db
         4       32             25         0/18/2  /data/user/0/cn.findpiano.piano/databases/dokit-database
         4        8                         0/0/0    (attached) temp
         4       32             80         3/19/5  /data/user/0/cn.findpiano.piano/databases/dokit-database (1)
         4       32             43         0/13/2  /data/user/0/cn.findpiano.piano/databases/dokit-database (2)
         4       44             25         1/16/2  /data/user/0/cn.findpiano.piano/databases/pushsdk.db
         4       36             96       28/25/11  /data/user/0/cn.findpiano.piano/databases/okdownload-breakpoint.db
         4       32            105  4055/11056/25  /data/user/0/cn.findpiano.piano/databases/pushwgs.db

## asset 资源分配
 Asset Allocations
    zip:/data/app/cn.findpiano.piano-7wIog6gXbNfUWoBFAZ5yoA==/base.apk:/assets/iconfont/iconfont.ttf: 204K
    zip:/data/app/cn.findpiano.piano-7wIog6gXbNfUWoBFAZ5yoA==/base.apk:/res/font/sans_regular.otf: 94K
    zip:/data/app/cn.findpiano.piano-7wIog6gXbNfUWoBFAZ5yoA==/base.apk:/res/font/sans_regular.otf: 94K
    zip:/data/app/cn.findpiano.piano-7wIog6gXbNfUWoBFAZ5yoA==/base.apk:/res/font/sans_medium.otf: 95K
    zip:/data/app/cn.findpiano.piano-7wIog6gXbNfUWoBFAZ5yoA==/base.apk:/res/font/sans_medium.otf: 95K
    zip:/data/app/cn.findpiano.piano-7wIog6gXbNfUWoBFAZ5yoA==/base.apk:/assets/iconfont/iconfont.ttf: 204K

PSS:这是一种按照比列来划分内存的统计方法。比如一个共享库占1G。由两个进程共享,那么PSS的计算方式就是,将1G一分为2。 分别为512M
按照上图,举例如下:Native Head 按照PSS统计方法,占据107179KB

Private Dirty:其中Private表示只有本进程可访问。Dirty表示还没有写回磁盘。举例如下:如上图第一行表示,分配给本进程的,已经被修改,但未写入磁盘的Native Heap大小为107140KB。

Private Clean: 其中Private表示只有本进程可访问。Clean表示,已经写回磁盘。举例:如上图第一行表示,分配给本进程的,已经写入磁盘的Native Heap为0

SwapPss Dirty:表示,将物理内存中的部分内容,放入swap中。因为内存紧张,把暂时用不到的东西放入Swap区域中。举例,如上图第一行,Native Heap放入Swap区域的
有17KB。其中Pss表示的就是上面介绍的按比例统计方法。可见如果SwapPss一直在更改,或者变大,说明设备进入了内存紧张的阶段,或者说,这个应用有毛病,如果swap一直变化很大,也说明出现内存抖动

注意:Android的swap并不像其他操作系统一样,将内存信息,交换到外存中。它是将内存暂时不用的区域,压缩之后,放入另外一个内存区域中。这个内存区域叫做zRam. zRam不是固定大小的,它会动态变化。只有当zRam都没法放下时,就会触发LMK,叫做low memory killer守护进程,进行应用的清理,滕出空间。

Heap Size:表示堆的大小,堆是动态分配内存的地方。举例。如上图第一行,表示:分配给Native Heap的堆大小为215040KB。

Heap Alloc和Heap Free:表示堆中已经分配和未分配的大小分别是多少。这两者之和就是Heap Size的大小。

NativeHeap 和Dalvik Heap:表示的是动态分配内存的区域。其中Dalvik Heap表示的java虚拟机使用的动态内存分配区域,如new 一个对象。剩下的就是NativeHeap,如c/c++分配的内存等等。举例,第一行第一列。按照Pss统计,Native Heap的大小为107179KB,其中只能被本应用使用的大小有107140KB,因为堆不需要写入磁盘即手机的Flash中,所以,Private Clean为0。而写入Swap区域的有17KB

可以看到Pss的数据量,大于Private Dirty的数据量,主要有如下的原因:native heap 含有被其他应用进程共享的部分。主要有两部分。1. 共享的so库,如libc,openGL所占用的heap空间。2.Binder在内核中分配的内存,也会被划入多个进程中。

除此之外,对于java的Bitmap对象,可能会看见Native Heap被大量增加,而Dalvik Heap少量增加的现象,这是因为Bitmap对象会在native heap中存储对应的数据

Dalvik Other:表示除了heap之外的其他内存。比如类,方法表,线程栈等等。举例如下:当java虚拟机需要加载一个类的时候,先从压缩包里面读出数据,然后分配内存,将这个类对应的元数据放入这部分内存中。这部分内存就会划入Dalvik other中

Stack:表示的是native的栈的大小。Stack是方法调用进行本地变量存储的区域。如果Stack过大,可能存在过多的线程,也可能存在深度递归操作
需要注意的是,java也有线程Stack,不过这部分被划分在Dalvik heap中

Ashmem:这是android独有的,匿名共享内存区域。即这部分可以被其他进程共享。如 1. Binder创建的数据传输区域。2.android的图形缓冲区域(早期android才会使用,现在android使用Graphic Buffer)3.共享的内存数据结构,如PackageManager会使用Ashmem维护一个所有应用程序的列表,这样可以被任何应用进程访问。4. WebView:Ashmem被用于存储共享的图片,字体等资源,这样可以避免每个进程都加载这些资源

在Ashmem中提到了Binder,在Native Heap中也提到了Binder。那么Binder的哪些数据属于Native Heap,那些属于Ashemem?

Binder的运行时环境,如Binder服务,Binder对象,Binder分配的数据结构,都属于Native Heap中。
Binder大量传输数据时,使用的Ashmem,则属于Ashemem

Gfx dev:表示图形设备,使用的内存,如存储纹理,顶点,帧缓冲等等。这部分内存由GPU程序来分配和管理,应用程序使用特定的API,如opengl或vulkan,进行交互。这部分内存可能是专用内存,也可能是通用内存的一部分,这取决于驱动的实现,android一般情况下是通用内存

Other dev:表示其他设备,使用的内存,如编解码器,这部分内存由驱动程序进行管理和分配,同Gfx dev相同它可能是专有内存,也可能是通用内存的一部分。如果这个数值过大,可能表示应用程序使用了过多的资源,甚至是使用完成之后,没有释放

xxx mmap:表示映射xxx所使用的内存大小。举例如下:.so mmap 表示so库映射大小为10837KB。但是需要注意的是:oat和art映射部分,这两部分仅仅表示所有应用共享的oat文件和art文件

何为oat文件,art文件,dex文件,odex文件,vdex文件?
android为了更快的运行字节码,对字节码进行了优化,优化之后的代码保存为dex格式。dex优化包含:字节码压缩,dex使用LEB128的压缩算法,为了适应移动设备的小容量存储。寄存器分配优化:因为Dlavik虚拟机的寄存器被设计的很少,为了在少量寄存器下的高速执行,需要对字节码进行优化

还可针对dex文件进行进一步的优化,这个过程可以发生在编译时候,也可以发生在安装的时候,将dex优化为odex。这些优化包含有:方法调用优化,类加载验证优化等等

android 5.0之后,为了更彻底的优化dex,在编译时候,或者安装时候,将dex直接转换机器指令,并将其保存在oat文件中。而机器码因架构的不同而不同。同时针对同一架构的不同型号cpu,还可以有更进一步的优化。所以oat文件会在不同设备之间表现一定的差异。因此有了oat文件之后,其实并不需要odex文件,但为了兼容以前的版本,依然允许运行odex文件

而上述的.oat mmap即为所有应用程序共用的oat文件的映射内存大小。它不包括本应用进程独有的oat文件的映射。如将android的framework部分,优化之后放在system分区的framework/arm64/boot-framework.oat下面。应用如果需要使用framework中的api,就会将内存映射到这部分,并修改.oat mmap的映射大小。如果是本应用的oat,是表现上图的哪一块区域呢?答案是没有表现,后面会通过读取/proc/pid/mmap来查看

art文件则是对一些内部实现的直接表示,如一些字符串,如果要新建一个字符串对象,可能需要在堆中重新分配内存。但是有些对象是一直存在且不变的。此时可以将其打包成.art文件,并在使用时通过mmap映射即可

而上图中的.art mmap则表示的是所有应用程序共用的art文件部分。

other mmap:则表示其他的内存映射,比如使用的其他设备空间等等。

GL mtracks:代表了由 OpenGL ES 图形库所使用的内存大小,常常由GPU驱动来管理。他与GFx dev之间的区别就是,Gfx dev表示的是图形设备使用的内存,而GL mtracks表示是open gl 使用的内存。如果分配大量内存,则可以看见Gfx dev的内存会远小于GL mtracks.

Unkonwn:无法进行分类的内存数据,这部分数据,通常是native分配的。如虚拟机自身使用的堆空间

如何获取当前应用的内存映射信息

## root权限
adb shell cat /proc/pid/maps > maps-pid.log

或者使用bugreportz

## 使用bugreportz
adb shell bugreportz
##pull 出对应的zip包,解压之后,会看到fs/data/proc找到对应的pid文件夹,
## 就能看到里面的maps文件,即为对应的内存映射文件

先打开一例,叙述如下:

##每一行,都有六列,分别表示如下
## 1. 7d5380000-7d6390000:该段内存区域的起始地址和结束地址
## 2. rw-s:该段区域的权限。r-读,w-写,x-执行,s-共享,p-私有。
##       如果没有则用短横线代替“-”
## 3. 0035f000:该区域的偏移地址
## 4. 00:0e:表示该设备文件的主设备号和次设备号
## 5. 15754:该文件的文件节点号
## 6. /dev/kgsl-3d0:文件的名字
7d5380000-7d6390000 rw-s 0035f000 00:0e 15754                            /dev/kgsl-3d0

那么有没有这段内存的详细描述呢?答案是有的。在同一个目录的smaps文件中,如下

12c00000-13bc0000 rw-p 00000000 00:01 16726                              /dev/ashmem/dalvik-main space (region space) (deleted)
Size:              16128 kB 
## 大小

Rss:               10228 kB 
## Rss统计下的大小

Pss:               10228 kB 
## Pss统计下的大小

Shared_Clean:          0 kB 
## 共享的,已经写回闪存的大小

Shared_Dirty:          0 kB 
## 功效的,已经修改但还未写回闪存的大小

Private_Clean:         0 kB 
## 同shared_clean,但是是私有的

Private_Dirty:     10228 kB 
## 同shared_dirty,但是是私有的

Referenced:        10228 kB 
## Referenced 字段显示了该内存区域中被操作系统认为正在使用(被引用)的内存数量

Anonymous:         10228 kB 
## 表示匿名,即没有特定的文件与之对应,完完全全是内存中的。

AnonHugePages:         0 kB 
## 表示匿名巨页的大小。巨页是比常规内存的页大很多的页,这样可以,
## 减少页表项,提高地址转换效率

Swap:                  0 kB 
## 内存被放入swap区域的大小

SwapPss:               0 kB 
## 内存被放入swap区域的大小,按Pss统计

KernelPageSize:        4 kB 
## 内核页大小

MMUPageSize:           4 kB 
## MMU页大小

Locked:                0 kB 
## 已经锁定的大小

VmFlags: rd wr mr mw me ac  
## 内存标志,具体的内存标志,还有很多我不是很熟悉,不过可以直接查看linux操作手册。man proc进行查看,现摘录如下:
# rd  - readable
# wr  - writable
# ex  - executable
# sh  - shared
# mr  - may read
# mw  - may write
# me  - may execute
# ms  - may share
# gd  - stack segment grows down
# pf  - pure PFN range
# dw  - disabled write to the mapped file
# lo  - pages are locked in memory
# io  - memory mapped I/O area
# sr  - sequential read advise provided
# rr  - random read advise provided
# dc  - do not copy area on fork
# de  - do not expand area on remapping
# ac  - area is accountable
# nr  - swap space is not reserved for the area
# ht  - area uses huge tlb pages
# sf  - perform synchronous page faults (since Linux 4.15)
# nl  - non-linear mapping (removed in Linux 4.0)
# ar  - architecture specific flag
# wf  - wipe on fork (since Linux 4.14)
# dd  - do not include area into core dump
# sd  - soft-dirty flag (since Linux 3.13)
# mm  - mixed map area
# hg  - huge page advise flag
# nh  - no-huge page advise flag
# mg  - mergeable advise flag
# um  - userfaultfd missing pages tracking (since Linux 4.3)
# uw  - userfaultfd wprotect pages tracking (since Linux 4.3)

android中常见的一些特殊文件名字的意义:

  1. 映射java虚拟机使用的空间,前缀常常为:/dev/ashmem/dalvik-
12c00000-13400000 rw-p 00000000 00:01 9000                               /dev/ashmem/dalvik-main space (region space) (deleted)

## 1. /dev/ashmem 是 Android 特有的一种匿名共享内存接口,
##   它提供了一种在进程之间共享内存的方式
## 2. dalvik-main space 是指这个内存区域被 Dalvik 虚拟机
##   (或 ART,它是 Dalvik 的后续版本)用作其主内存空间。主要用于存储 Java 对象。
## 3. (region space) 表示这个内存区域采用了 "region space" 的内存管理策略。
##  这是 Android 8.0(API 级别 26)引入的一种新的内存管理策略,它将内存分为
##  多个相同大小的区域
## 4. delete 表示文件已经被删除,这里是虚拟设备,不存在文件,故为delete
## 5. 因此,/dev/ashmem/dalvik-main space (region space) 这个条目表示,
##    这个内存区域是由 Dalvik 或 ART 虚拟机使用的,用作其主内存空间,
##    存储 Java 对象,并采用了 "region space" 的内存管理策略。

73c1b000-73ca0000 rw-p 00000000 00:01 8997                               /dev/ashmem/dalvik-zygote space (deleted)
## 在fork子进程之前,zygote的堆空间

77c1b000-97c1b000 rw-p 00000000 00:01 9002                               /dev/ashmem/dalvik-free list large object space (deleted)
## 这是创建java大对象的内存区域

上面是一些常见的内存空间,后面有机会,再来介绍虚拟机内部的空间划分和排布规则

  1. 映射编译好的oat,art,vdex,so等
708b4000-708c1000 rw-p 00000000 08:0a 761948                             /data/dalvik-cache/arm64/system@framework@*
## 表示使用java虚拟机缓存下的各种文件,如art

71327000-7132a000 r--p 00000000 103:02 1459                              /system/framework/arm64/*
## 表示映射的是system/framwork下面的文件,如各种服务编译之后的oat
  1. 映射native的空间
71b47000-71b49000 rw-p 00000000 00:00 0                                  [anon:.bss]
## 1. anon表示匿名。.bss表示静态数据段。表示程序中使用的,未初始化的,
##   全局静态变量和全局外部变量。
## 2. .bss既可以是动态分配,也可以是静态编译好的。此处的anon即表示为动态分配的。

7185a00000-7193a00000 rw-p 00000000 00:00 0                              [anon:libc_malloc]
## 为c/c++代码使用malloc和new分配内存的区域

717ce2e000-717ce2f000 ---p 00000000 00:00 0                              [anon:thread signal stack guard page]
717ce2f000-717ce33000 rw-p 00000000 00:00 0                              [anon:thread signal stack]
717ce33000-717ce34000 ---p 00000000 00:00 0                              [anon:bionic TLS guard]
717ce34000-717ce37000 rw-p 00000000 00:00 0                              [anon:bionic TLS]
717ce37000-717ce38000 ---p 00000000 00:00 0                              [anon:bionic TLS guard]
717ce38000-717ce39000 ---p 00000000 00:00 0                              [anon:thread stack guard page]
717ce39000-717cf35000 rw-p 00000000 00:00 0                              [stack:9166]

## 1.这是一块连续的内存区域,最底下的是线程的堆栈区域[stack:9166]
##   其中9166是对应的线程id
## 2.stack:9166上面则是,线程堆栈区域的保护区域

## 3.再上面是线程的本地存储区域(Thread local storeage,TLS).
##     在TLS的上下,分别有一块保护区域,叫做TLS guard
## 4.在TLS的上面,则是这个线程的信号堆栈区域,signal stack
## 5.同样,在signal stak上面有保护区域,叫做signal stack guard page
## 6.保护区域是为了防止溢出,一旦访问到保护区域,则程序报错



72bd43f000-72bd440000 r--p 00000000 00:00 0                              [vvar]
72bd440000-72bd441000 r-xp 00000000 00:00 0                              [vdso]
## [vdso]:虚拟动态共享对象(Virtual Dynamic Shared Object)是一种内
## 核机制,允许在用户空间的应用程序直接访问内核提供的某些功能,
## 从而避免系统调用的开销。VDSO 包含一些在内核态和用户态之间共享的函数,
## 通常用于实现高效的时间相关系统调用,
## 例如 gettimeofday() 和 clock_gettime()。VDSO 的主要目的是减少系统
## 调用的开销,从而提高程序的性能。

## [vvar]:虚拟变量(Virtual Variable)是一种内核提供的只读内存区域,
## 用于存储与时间相关的内核变量。
## 这些变量通常包括实时时钟(RTC)以及内核的时间戳计数器(TSC)。用户空间
## 的应用程序可以通过 [vvar] 内存区域直接访问这些变量,
## 从而提高对时间相关信息的访问速度。

注意:若发现[stack:pid]这些区域太多,则可能需要考虑线程是否溢出。如线程没有正确的关闭,或者创建了太多的临时线程等

  1. 映射硬件设备
7d5380000-7d6390000 rw-s 0035f000 00:0e 15754                            /dev/kgsl-3d0
## 表示映射的是/dev/kgsl-3d0设备,这是一个GPU设备。按照规则,
## 往这个区域写入数据,就可以操作GPU
  1. 映射文件
717cbcc000-717cbcd000 r--p 00000000 08:0a 6678559                        /data/media/0/download/others.$~&FochIvRkczjco7HwvK80VNSWqX4Z.png

## 717cbcc000-717cbcd000这个区域对应的内容,就是png中的内容

注意:如果发现这种区域过多,则需要考虑是否有重复加载,或者问加你没有正确关闭等。

第一篇主要完成了,内存总览,包括一些基本概念
下一篇,将介绍如何查看内存中的细节,如某个指针对应的内容是什么,堆栈里面的数据有哪些等等

猜你喜欢

转载自blog.csdn.net/xiaowanbiao123/article/details/130748976