惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Cyberwarzone
Cyberwarzone
S
Secure Thoughts
L
LINUX DO - 热门话题
C
Cyber Attacks, Cyber Crime and Cyber Security
C
CERT Recently Published Vulnerability Notes
P
Privacy & Cybersecurity Law Blog
A
Arctic Wolf
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
C
Cybersecurity and Infrastructure Security Agency CISA
大猫的无限游戏
大猫的无限游戏
U
Unit 42
Recorded Future
Recorded Future
Hacker News: Ask HN
Hacker News: Ask HN
F
Full Disclosure
Spread Privacy
Spread Privacy
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Know Your Adversary
Know Your Adversary
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
云风的 BLOG
云风的 BLOG
Stack Overflow Blog
Stack Overflow Blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
F
Fortinet All Blogs
Martin Fowler
Martin Fowler
T
Threatpost
I
InfoQ
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Cloudbric
Cloudbric
L
LangChain Blog
N
Netflix TechBlog - Medium
The Register - Security
The Register - Security
S
Security @ Cisco Blogs
B
Blog
T
Threat Research - Cisco Blogs
T
The Blog of Author Tim Ferriss
The GitHub Blog
The GitHub Blog
D
DataBreaches.Net
Simon Willison's Weblog
Simon Willison's Weblog
P
Proofpoint News Feed
Microsoft Security Blog
Microsoft Security Blog
Blog — PlanetScale
Blog — PlanetScale
B
Blog RSS Feed
MongoDB | Blog
MongoDB | Blog
W
WeLiveSecurity
Forbes - Security
Forbes - Security
O
OpenAI News
C
Check Point Blog
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
The Last Watchdog
The Last Watchdog
H
Help Net Security
SecWiki News
SecWiki News

Android Performance

SmartPerfetto 架构文章 Q&A:8 个深度技术问答 从 Trace 到洞察:SmartPerfetto AI Agent 的 Harness Engineering 实战 OpenClaw 常见问题解答:Token 消耗、能干什么、本地模型、隐私安全、使用体验 我把 OpenClaw 跑在本地三周后,发现它根本不是聊天机器人 Android Perfetto 系列 10 - Binder 调度与锁竞争 Android Perfetto 系列 9 - CPU 信息解读 Android Perfetto 系列 8:深入理解 Vsync 机制与性能分析 Android Perfetto 系列 7 - MainThread 和 RenderThread 解读 Android Perfetto 系列 6:为什么是 120Hz?高刷新率的优势与挑战 Android Perfetto 系列 5:Android App 基于 Choreographer 的渲染流程 Android Perfetto 系列 4:使用命令行在本地打开超大 Trace Android ANR 系列 3 :ANR 案例分享 Android ANR 系列 2 :ANR 分析套路和关键 Log 介绍 Android ANR 系列 1 :理解 Android ANR 设计思想 Android Perfetto 系列 3:熟悉 Perfetto View Android Perfetto 系列 2:Perfetto Trace 抓取 Android Perfetto 系列 1:Perfetto 工具简介 Android Perfetto 系列目录 2023 年的方方面面 关于 The Android Performance 知识星球介绍 The Performance Design Of OS OS 设计之性能设计 当 App 有了系统权限,真的可以为所欲为? The Performance 星球茶话会 - 第一期 Systrace 线程 CPU 运行状态分析技巧 - Sleep 和 Uninterruptible Sleep 篇 Systrace 线程 CPU 运行状态分析技巧 - Running 篇 Systrace 线程 CPU 运行状态分析技巧 - Runnable 篇 Android 性能优化的术、道、器 Techniques, Philosophy, and Tools for Android Performance Optimization 回顾 2021 一本讲 Android 流畅性的书,应该有什么内容? Android 系统开发系列(1):Android 12 源代码下载、编译和刷机 Android Systrace 响应速度实战 3 :响应速度延伸知识 Android Systrace 响应速度实战 2 :响应速度实战分析-以启动速度为例 Android Systrace 响应速度实战 1 :了解响应速度原理 Android Systrace 流畅性实战 3 :卡顿分析过程中的一些疑问 Android Systrace 流畅性实战 2 :案例分析 - MIUI 桌面滑动卡顿分析 Android Systrace 流畅性实战 1 :了解卡顿原理 一个「闰」字引发的事故 - 三星系统重启分析 Android App 链式唤醒分析 Android Systrace 基础知识 - SurfaceFlinger 解读 Android 开发者学习路线(2020 版本) 我的 2020 年读书单 Android Systrace 基础知识 - CPU Info 解读 Android Systrace 基础知识 - Triple Buffer 解读 Android Systrace 基础知识 - Binder 和锁竞争解读 「置顶」博客文章目录 Android Systrace 基础知识 - Vsync 解读 Android App 启动优化全记录 Android Systrace 基础知识 - MainThread 和 RenderThread 解读 Android Systrace 基础知识 - Input 解读 Android 中的“后台无效动画“行为分析 Android 基于 Choreographer 的渲染机制详解 Android 中的卡顿丢帧原因概述 - 低内存篇 Android 桌面被杀问题分析案例 Android 中的卡顿丢帧原因概述 - 应用篇 Android 中的卡顿丢帧原因概述 - 系统篇 Android 中的卡顿丢帧原因概述 - 方法论 Android 中的 Activity Launch Mode 详解 Android 中的 Hardware Layer 详解 Android Systrace 基础知识 -- 分析 Systrace 预备知识 Android Systrace 基础知识 - SystemServer 解读 Android Systrace 基础知识 -- Systrace 简介 Android Systrace 基础知识 -- Why 60 fps ? Android Systrace -- 系列文章目录 Android 新的流畅体验,90Hz 漫谈 利器 - 高效工具推荐 Android 无障碍服务导致的整机卡顿案例分析 2018 年度好物推荐 - 给辛勤工作的自己一点奖励 Android 系统开发源码环境搭建 陆奇:除了好代码,工程师怎样才算优秀? 程序员的修炼-08-阅读之美 程序员的修炼-07-游戏与编程 程序员的修炼-06-互联网那些事 程序员的修炼-05-了解你的用户 程序员的修炼-04-关于测试的一些思考 程序员的修炼-03-Web 设计原则 程序员的修炼-02-编程之道 程序员的修炼-01-绝地反击之术 Android 系统不释放内存吗? 关于 Android 系统流畅性的一些思考 知乎 救救你的 StartingWindow 「置顶」Android 性能优化必知必会 2017 年度好物推荐 - 给辛勤工作的自己一点奖励 2017 Android Bottom navigation 规范二:样式、行为与规格 Android Bottom Navigation 规范一:使用方法 Android 中如何计算 App 的启动时间? Android 应用启动优化:一种 DelayLoad 的实现和原理(下篇) Android 应用启动优化 - 一种 DelayLoad 的实现和原理(上篇) Android hwui 中 RenderThread 工作流程 Java7 HashMap 源码分析 Android 代码内存优化建议 - OnTrimMemory 优化 Android 代码内存优化建议 - Android 资源篇 Android 代码内存优化建议 - Android 官方篇 Android 代码内存优化建议 - Java 官方篇 Nexus6 with Android M 开启多窗口模式 细说 Java 单例模式 Android 性能优化典范 - Profile GPU Rendering Android 性能优化典范 - Understanding VSYNC
华为手机刷微博体验更好?技术角度的一些分析和思考
Gracker · 2020-08-20 · via Android Performance

技术群里的小伙伴发了一条微博, https://weibo.com/1808884742/IApbpEVQr, 博主 @王波粒 发现, Mate 30 Pro 有个很特别的现象(建议先去看一下视频)

但是这个视频描述和底下的猜测都不对,我这边总结一下这个现象: 微博这个 App 在华为的手机上,在主页列表上下滑动的情况下依然可以流畅加载图片,而同一个版本的微博客户端,安装到其他手机上,在主页列表上下滑动的情况下,则必须要等到滑动停止之后才会加载图片

下面就针对这个现象,从技术的角度来深入分析产生这种现象的原因,以及我们能从里面学到什么

这个现象有什么特别呢 ?

  1. 从技术上讲,滑动列表停止后再加载图片是目前列表滑动优化中一个比较常见的优化项,很多主流 App 也都是这么做的 ,做这种处理主要是因为 如果在列表滑动的时候,碰到图片视频就加载,那么会加载很多无用的图片&&视频,浪费资源不说,还可能会影响真正用户看到的图片的加载速度 (加载一般都有并行上限和队列,队列里面无效的图片太多,后来的图片就得排队等待)。 这里比较 特别的就是同一个版本的微博 APK,在华为的机型上与在其他机型上表现不一致,作为一个系统优化工程师,这个还是值得去搞清楚的(大胆猜测是微博针对华为的机型做了优化),那么这个优化的内容是什么?
  2. 从用户体验的角度来讲,列表滑动的同时加载图片,用户可以更早地看到图片,减少图片占位白图的显示时间,可以提升滑动的体验
  3. 第三个现象就得认真体验才会感觉到: 华为手机上的微博在松手后的滑动曲线和其他手机上的微博在松手后的滑动曲线是不一样的,华为的微博列表松手后的滑动曲线速度更慢,更柔和,结束的时候也不会太突兀,与系统默认的列表滑动曲线明显不一样

上面三个是从现象上来说的,下面就从技术上来验证,从最后的结果来看,华为和微博的合作从结果看是很成功的,可以作为一个案例推广到其他头部 App,同时作为 Android 开发者,对华为这种非常细致的体验优化真的是非常敬佩

背景备注

  1. 由于 “列表滑动的同时加载图片” 这个功能由微博官方服务器控制,可以随时开启或者关闭,所以文章中所说的 “同一个版本的微博客户端,安装到其他手机上,在主页列表上下滑动的情况下,则必须要等到滑动停止之后才会加载图片” 这个现象在 “列表滑动的同时加载图片” 这个功能开启后,现象就会变成 “主页列表上下滑动的时候就会加载图片”
  2. 在 2020-6 月左右分析这个问题的时候,“列表滑动的同时加载图片” 这个功能还是关闭的,只有华为手机做了优化才有效果,其他手机是 “滑动停止之后才会加载图片”
  3. 在 2020-8 月再看这个问题的时候,“列表滑动的同时加载图片” 这个功能在其他手机上已经开启
  4. 华为的 PerfSDK 还有效果么?答案是有,具体分析可以看下文,因为有了这个 SDK,不仅对微博有好处(减少图片加载个数),对华为也有好处(提升微博主页列表在华为手机上的滑动体验,即 Fling 曲线优化) ;而粗暴开启 “列表滑动的同时加载图片” 的其他手机,如果性能不足,开启后反而会增加卡顿出现的概率(微博官方应该有性能监控数据可以看到)
  5. 反编译的微博版本:10.8.1

结论先行

“微博这个 App 在华为的手机上,在主页列表上下滑动的情况下依然可以流畅加载图片” 这个现象是因为华为和微博做了联合优化,主要是为了优化微博列表滑动时候的用户体验,其优化点如下

  1. 华为提供了一个简单的接口打包成 SDK 提供给微博,这个接口可以让微博的列表监听到列表的当前速度(Velocity),在速度高于阈值或者低于阈值的时候,都会及时通知 App
  2. 微博拿到这个速度回调之后,就可以根据列表的滑动速度来决定是否要在滑动过程中加载图片,一旦列表的滑动速度低于设定的阈值,就开启图片加载;一旦列表的滑动速度高于设定的阈值,就关闭图片加载
  3. 华为检测到这个应用使用了 SDK,就可以将优化过后的滑动曲线应用在这个 App 的列表 Fling 阶段,提升用户体验

对细节感兴趣的同学可以继续阅读,有能力的同学看完后可以修改 Framework 相关代码,编译一个 SDK,然后自己写个 Demo 接入 SDK,就可以打通我下面所说的所有内容了,我自己在 AOSP 的代码上实现了一遍,Demo 也可以正常运行,有兴趣可以跟我私下交流

微博+华为是怎么优化?

现象分析

我们在滑动微博列表的时候,一个滑动操作主要由下面三部分组成

  1. 手指接触屏幕,上下滑动微博主页列表,但是手指 没有离开屏幕 ,这个阶段我们称之为阶段一,技术术语为 SCROLL_STATE_TOUCH_SCROLL
  2. 手指上下滑动的时候 离开屏幕 (必须有一个上滑或者下滑的速度),微博列表有了一个惯性,根据惯性的方向继续滑动,这个阶段我们称之为阶段二,技术术语为 SCROLL_STATE_FLING
  3. 列表惯性滑动后停止,这 个 阶段我们称之为阶段三 , 技术术语为 SCROLL_STATE_I DLE

而华为和微博的优化主要在阶段一和阶段二

阶段一优化

  1. 优化前:只要手指不离开屏幕,图片加载功能关闭
  2. 优化后:只要手指不离开屏幕,列表就不会滑动太快,这时候图片加载功能开启

阶段二优化

  1. 滑动图片加载优化
  2. 优化前:只要列表滑动不停止,图片加载功能关闭
  3. 优化后:图片加载功能是否开启取决于当前列表滑动的速度
    1. 列表滑动速度太快,这时候图片加载功能关闭
    2. 列表滑动速度掉落到一个阈值,图片加载功能开启
  4. 列表 Fling 曲线优化
  5. 优化前:列表滑动的曲线是默认值,滑动时间比较短,停止的时候比较突兀,不柔和
  6. 优化后:列表滑动的曲线是华为经过优化的,滑动时间比较长,停止的时候比较柔,不突兀,比较接近 iPhone 的列表滑动曲线

技术分析

技术分析的代码主要来源于微博 apk 的反编译,微博版本 10.8.1,通过反编译的代码可以看到, 微博主页在初始化的时候,会接入华为提供的 PerfSDK,从而获得监听列表滑动速度的能力

阶段一优化的技术分析

列表的 ScrollStateChange 是标识列表状态的一个回调,微博在 ScrollStateChange 这个回调中会根据当前的状态来决定是否加载图片, 从下面的代码逻辑来看

  1. 滑动图片加载优化生效 的时候,如果 State != 2,那么就允许 ImageLoader 加载图片,State 为 2 也就是 SCROLL_STATE_FLING,熟悉列表滑动的同学应该知道,SCROLL_STATE_FLING  就是 滑动列表的时候手指松手后列表继续滑动的那一段 ,叫 fling,毕竟只有 fling 的时候才有 Velocity,松手后会根据这个值的大小计算滑动曲线和滑动时长
  2. 滑动图片加载优化不生效 的时候,就到了常规的列表滑动优化:即列表停止之后才开始加载图片 :State !=0,0 即 SCROLL_STATE_IDLE

阶段二优化的技术分析

微博的主页在初始化的时候,会给首页的 ListView 注册一个 HwPerfVelocityCallback,从名字可以看出来,这个回调是监听 Velocity 的,也就是滑动的速度,两个回调:

  1. HwPerfonVelocityDownToThreshold : 当速度降低到阈值之后,打开 ImageLoader 的图片加载功能
  2. HwPerfonVelocityUpToThreshold: 当速度升高到阈值之后,关闭 ImageLoader 的图片加载功能

下图为反编译后的源码

至于滑动曲线,则需要查看华为的 Framework 的代码,由于代码量比较大,这里只贴一下 OverScroller.java 中的 update 方法,具体感兴趣的可以自己去翻一番华为的 Framework 代码

计算 Distance 的代码

计算 Velocity 的代码

关于滑动曲线的解释,大家可以看这一篇知乎回答,其中对比了 iOS 和 Android 的滑动曲线的不同 :为什么 iOS 的过渡动画看起来很舒服?

其他厂商处理

上面图中代码最后一段还有一个判断开关, 如果 boolean a = HwPerfUtil.m14290a 这个返回的是 false,这就是说有可能华为这个优化关闭了,有可能是非华为机器,那么会 判断 Android 版本号和全局 Feature 开关

对应的 FeedAbManager 就是一个 Feature 管理器,可以在线开关某些 Feature

而 m52580k 的实现如下

可以看到这里还受到一个全局的 Feature 配置:feed_scroll_loadimage_enable,这个 Feature 是服务端可以配置的

这里就是处理其他厂商的逻辑

最后一个问题:滑动点击

滑动点击是个什么问题呢?列表在滑动的过程中,如果用户点击列表的某一个 Item,那么根据 Android 的事件分发机制,这时候列表的 Item 并不会被点击到,而是整个列表先收到点击事件,然后 触发列表滑动停止;列表停止的时候点击 Item 才会触发 Item 的点击

上面阶段二的优化中,在优化了滑动曲线之后,列表处于 Fling 状态的时间变长,如果用户想点击去某一个 Item 查看详情,那么需要先点击一下让列表停止,然后再点击一下才能进去,这就是这一节想说的 :滑动点击问题

滑动(Fling 状态)和点击其实是需要一个平衡的,这个平衡需要开发者自己去把控:

滑动(Fling 状态)的时间越短,列表越容易停下,用户点击列表越容易触发 Item 的点击,但是容易停止带来的问题就是不够柔和。想象你在粗糙的水泥地上滑出去一块石头,这石头没有滑动多久就会停止,不管是扔石头的你还是旁边看你扔石头的我,都不会觉得这有什么美感,但是没得选。这个的 代表其实就是 Android 原生的 Fling 曲线

滑动(Fling 状态)的时间越长,滑动(Fling 状态)的时间越长,列表越不容易停下,用户点击列表越不容易触发 Item 的点击,如果曲线优化的好,给人的感觉就是很柔和,符合物理规律,想象你在光滑的冰面上滑出去一块冰,冰面越滑,冰块滑动的时间就越长,越不容易停下。这其中的极端代表就是 iOS 的 Fling 曲线。说 iOS 极端是因为,iOS 的滑动曲线调的太磨叽了,时间长不说,停的异常慢,很多时候你都需要点击一下列表让他先停止,然后再进行下一步的点击动作。而小米的 MIUI12 对这个也进行了调整,效果要比 iOS 好一些,如果再和三方进行类似华为和微博的合作,体验会更上一层楼

滑动点击问题其实也可以通过厂商和 App 合作来解决,比如,当滑动到整个滑动距离的 98%(或者 95%) 之后,用户点击列表不再是让列表停止,而是列表内的 item 响应这个点击。这个思想来源于 Launcher 的代码,Launcher 的每一页在左右滑动的时候,如果滑动还没有停止但是用户比较手速快点击了某个 icon 想启动,那么这时候不会触发 Page 停止,而是直接响应 icon 的点击启动应用操作

延伸阅读

列表滑动图片加载的性能考虑

前文有提到这个问题,滑动的时候进行图片加载主要有两个问题:

  1. 如果用户滑动非常快,比如是想找昨天发的某个微博,那么今天发的所有的带图片的微博在用户滑动的时候是没必要加载的,因为用户的目标不是这些图片,而 App 去加载这些图片,而程序员是不会为用户提前加载你未看到的数据,因为加载过多的数据不仅容易发生数据复用、缓存过多、内存溢出等错误,还会对服务器造成不必要的资源请求。
  2. 如果用户滑动非常快,那么图片加载队列势必有许多无效的资源(对这一刻的用户来说),而用户真正想看的图片反而排在了加载队列后面,造成加载速度变慢,也会影响用户的体验

滑动中加载图片最大的风险其实就是造成卡顿,因为图片加载本身就是一个比较重的操作,而高帧率的手机上,一帧的时间被压缩到很短,任何小的不确定性都有可能造成卡顿

所以厂商+应用的这个优化: 快速滑动不加载图片,慢速的时候再加载,然后优化滑动曲线 ,其实对厂商和应用都是非常有益处的

列表滑动监听背景知识

下面的 AbsListView 的 OnScrollListener 里面标注了列表滑动的三个状态

  1. 滑动停止:SCROLL_STATE_IDLE
  2. 手指在屏幕上滑动:SCROLL_STATE_TOUCH_SCROLL
  3. 手指离开屏幕,列表靠惯性继续滑动:SCROLL_STATE_FLING

两个回调

  1. 列表状态变化时的回调 :onScrollStateChanged
  2. 列表滑动时候的回调:onScroll
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public interface OnScrollListener {


public static int SCROLL_STATE_IDLE = 0;


public static int SCROLL_STATE_TOUCH_SCROLL = 1;


public static int SCROLL_STATE_FLING = 2;


public void onScrollStateChanged(AbsListView view, int scrollState);


public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount);
}

列表滑动状态的变化

TOUCH_SCROLL、FLING、IDLE 三个状态对应的列表滑动操作如下

  1. TOUCH_SCROLL: 手指滑动 List 阶段,但是手指没有离开屏幕,这时候上下滑动都是 TOUCH_SCROLL
  2. FLING: 手指滑动 List 后抬手到 List 停止的阶段(必须有一个上滑或者下滑的速度,否则不会进入 Fling)
  3. IDLE:List 停止阶段

这三个状态的变化情况如下

  1. 手指滑动列表,停止后松手:IDLE  -> TOUCH_SCROLL -> IDLE
  2. 手指滑动列表,松手后列表继续滑动,然后停止:IDLE  -> TOUCH_SCROLL -> FLING -> IDLE

列表的 Fling 曲线计算

Fling 触发之后,每一帧都会调用 update 函数来更新 distance 和 mCurrVelocity,所以我们只需要监听 mCurrVelocity 的值,超过一定的阈值,就可以回调给 App

frameworks/base/core/java/android/widget/OverScroller.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
boolean update() {
final long time = AnimationUtils.currentAnimationTimeMillis();
final long currentTime = time - mStartTime;
double distance = 0.0;
switch (mState) {
case SPLINE: {
final float t = (float) currentTime / mSplineDuration;
final int index = (int) (NB_SAMPLES * t);
float distanceCoef = 1.f;
float velocityCoef = 0.f;
if (index < NB_SAMPLES) {
final float t_inf = (float) index / NB_SAMPLES;
final float t_sup = (float) (index + 1) / NB_SAMPLES;
final float d_inf = SPLINE_POSITION[index];
final float d_sup = SPLINE_POSITION[index + 1];
velocityCoef = (d_sup - d_inf) / (t_sup - t_inf);
distanceCoef = d_inf + (t - t_inf) * velocityCoef;
}
distance = distanceCoef * mSplineDistance;
mCurrVelocity = velocityCoef * mSplineDistance / mSplineDuration * 1000.0f;
break;
}
case BALLISTIC: {
final float t = currentTime / 1000.0f;
mCurrVelocity = mVelocity + mDeceleration * t;
distance = mVelocity * t + mDeceleration * t * t / 2.0f;
break;
}
case CUBIC: {
final float t = (float) (currentTime) / mDuration;
final float t2 = t * t;
final float sign = Math.signum(mVelocity);
distance = sign * mOver * (3.0f * t2 - 2.0f * t * t2);
mCurrVelocity = sign * mOver * 6.0f * (- t + t2);
break;
}
}
mCurrentPosition = mStart + (int) Math.round(distance);
return true;
}

厂商应用联合优化

微博这个优化就是厂商和应用之间联合优化的一个案例,应用对用户体验的极致追求,让这种合作在未来会变得更加频繁,像微信、快手、抖音这些…

下面这个招聘是拼多多的一个 JD,看职位描述是专门对接厂商的优化,也可以看出应用对厂商的合作越来越重视。之前厂商和应用是魔高一尺道高一丈的关系,互相攻防导致最终体验受损的还是用户;而现在这种厂商和应用合作的关系,不仅提升了双方的体验,也会带动 Android 生态圈向好的方面去发展

本文其他地址

微信公众号 - https://mp.weixin.qq.com/s/wJKOvU7CqP3vM0TG7rO66g
知乎专栏(求个赞) - https://zhuanlan.zhihu.com/p/191460094

关于我 && 博客

下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师!

  1. 博主个人介绍 :里面有个人的微信和微信群链接。
  2. 本博客内容导航 :个人博客内容的一个导航。
  3. 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可)
  4. Android性能优化知识星球 : 欢迎加入,多谢支持~

一个人可以走的更快 , 一群人可以走的更远

微信扫一扫