- 不仅仅是时机不匹配,也存在收到 VSync 但不触发 DisplayLink 回调的情况(并且主线程处于空闲状态),例如上图中的 ❓ 处。
解除 DisplayLink 的帧数限定
我们知道,在 iOS 15 上 Apple 对第三方应用的体现帧率默认做了限定。第三方应用需要在 Info.plist 中添加<key>CADisableMinimumFrameDurationOnPhone</key><true/> 字段才可以解锁 120Hz 的刷新率。
于此同时,在 iOS 15 中,CADisplayLink 等动画相关 API 也新增了一个用于设置偏好帧率的属性:
/* Defines the range of desired callback rate in frames-per-second for this
display link. If the range contains the same minimum and maximum frame rate,
this property is identical as preferredFramesPerSecond. Otherwise, the actual
callback rate will be dynamically adjusted to better align with other
animation sources. */
@property(nonatomic) CAFrameRateRange preferredFrameRateRange
API_AVAILABLE(ios(15.0), watchos(8.0), tvos(15.0));
为了进一步探究新装备上 DisplayLink 和 VSync 信号之间的关系,笔者将测试 App 的 Core Animation 的帧率限定解除,并设置对应的 API,分别在差异的场景重新举行测试:
体现动态内容的场景
动画场景
展示一个速度中等的位移动画,得到下图:
可以很直观地发现,DisplayLink 解锁帧率后的屏幕刷新率基本稳固在 120Hz。并且 VSync 和 DisplayLink 的关系似乎又重新逐一对应了起来。
但是,将动画速度减慢,笔者发现这种对应关系发生了变革:
可以观察到在播放慢速动画时,DisplayLink 的频率依然是设置的 120Hz,但是现实的屏幕刷新率却只有 30Hz。
滑动场景
让我们换一种场景再次举行测试,快速滑动视图,在 Instruments 中得到下图:
可以发现,DisplayLink 解锁帧率后,屏幕刷新率同样基本稳固在 120Hz,仅在丢帧时有降频。
- 需要注意的是笔者在 CADisplayLink 的回调中除了调用 os_signpost 上报 log 外无任何 UI 改动。
- 即便笔者展示的 TableView 极其简单,上图中仍旧可以观察到丢帧,无法在滑动中完美稳固 120Hz。这大概阐明 UIKit 的渲染性能在 120Hz 下会有某种程度上的原生瓶颈。
然后降低滑动屏幕的速度,得到了和慢速动画相似的结果,只管 DisplayLink 回调速度不减,但是 VSync 信号频率一直保持在较低的水平:
卡顿场景
上面两次测试都靠近理想情况,即整个 Render Loop 执行险些没有延伸与卡顿。但是现实中应用的运行总是有着各种各样的或大或小的卡顿问题。
为了验证更靠近现实情况下,DisplayLink 和 VSync 信号之间的关系,在一连滑动的情况下笔者人为参加了一个 20ms 的微小卡顿举行测试:
上图中可以看到,ProMotion 屏幕很好的处理了这次卡顿,由于三缓冲机制的存在,再 Render Loop 渲染 Surface 4 卡顿期间,通过改变 VSync 隔断,系统实验将缓冲区中的 Surface 283 与 Surface 250 延伸上屏,尽量缩短了用户看到静止画面的时长。
随后,主线程规复执行,可以看到 DisplayLink 的回调频率很快规复至卡顿前的高水平。而此时 VSync 信号由于前述卡顿减缓机制的存在频率其实有所降低。此时二者频率并不吻合。
这和之前播放慢速动画/慢速滑动的情况很相似,由于卡顿加上缓冲机制的存在导致短时间内系统将屏幕的刷新频率降低,但在 CPU 侧依然维持了 DisplayLink 的高速回调,满意了利用方对 preferredFrameRateRange 这一 API 的设置。
为了进一步分析了这种机制的本质,笔者接下来会实验逆向分析 iOS 15 中的系统库相关实现的改动。
逆向分析
DisplayLink 驱动方式的变革
在 CADisplayLink 回调方法上设置断点,分别在 iOS 14 和 15 ProMotion 装备上运行,可以得到:
- 在 iOS 14 上,CADisplayLink 是通过 Source 1 mach_port 直接接受 VSync 信号驱动的
- 在 iOS 15 ProMotion 装备上,CADisplayLink 不再由 VSync 信号驱动,而是由一个 UIKit 内部的 Source0 信号驱动
在 15 中,CADisplayLink 第一次创建并添加至 RunLoop 的时间,会注册一个 Source 1 信号,这和 14 中行为一致。
其 callout 回调地点对应符号为同样为 display_timer_callback,同样和 14 中的一致。
这也可以表明为什么 15 上 VSync 信号确实会唤醒一次 RunLoop,只是这次唤醒并不一定触发 DisplayLink 的回调,这就阐明 display_timer_callback 行为和 14 相比一定发生了某种变革。
display_timer_callback 逻辑的变革
利用 Hopper 分析 display_timer_callback 的实现,发现 15 和 14 的实现并无区别。利用 LLDB 举行 debug,渐渐分析,观察到后续调用函数为 CA: isplay: isplayLink::callback,其关键反汇编代码如下图所示:
观察反汇编代码可以发现,假如 CA::display_link_will_fire_handler 这个 block 返回了 NO,则这次 VSync 信号回调不会触发后续的 CA: isplayLink::dispatch_items 调用。
现实上在 LLDB 中也验证了这点:
注意上图中的 _CFRunLoopCurrentIsMain 和上图红框代码靠近,后续的 blraa 指令看起来很明显是调用了一个 block(上面的 ldr x9 [x8, #0x10] 就是把 invoke 指针从 block 结构体中取出的意思)。tbz 指令中 w0 寄存器为 block 执行的返回值,为 0(即 NO)时跳转至 0x1848dbc08,而 0x1848dbc08 刚好在 dispatch_items 的调用之后,跳过了该调用。
通过对上图中 blraa 指令 step in,我们发现这个 block 现实上是由 UIKitCore 注册的:
找到引用了该符号的 UIKit 的私有方法 __UIUpdateCycleSchedulerStart ,反汇编结果也验证了这点。
同时发现这个 block 的返回值固定为 0x0。
而同样的 symbol 在之前的 iOS 版本上并不存在,也就是说这个应该是 iOS 15 的变动。换安装了 iOS 15 的非 ProMotion 装备,重走上面的逆向流程发现,该装备的 CA::display_link_will_fire_handler 为 nil,未注册:
这里 cbz 执行了跳转,阐明 x0 为 nil,而 x0 是由 ldr x0, [x8, #0x1c8] 得到。
可以看到 x0 就是 CA::display_link_will_fire_handler。继续分析之前找到的私有符号 __UIUpdateCycleSchedulerStart 的相关实现,可以知道这是由于在非 ProMotion 装备上 _UIUpdateCycleEnabled 返回了 NO 导致的。
在返回 NO 的情况下 __UIUpdateCycleSchedulerStart 方法不会执行,CA::display_link_will_fire_handler 也就不会被注册。
_UIUpdateCycleEnabled 所带来的变革
继续研究 _UIUpdateCycleEnabled 相关的代码,笔者发现这个的改动并不是仅仅影响 DisplayLink 驱动方式那么简单。
当 _UIUpdateCycleEnabled 返回 YES 时,UIKit 会在 UIApplicationMain 中执行 _UIUpdateCycleSchedulerStart。分析该函数,发现 _UIUpdateCycleEnabled 启用时会调用 [CATransaction setDisableRunLoopObserverCommits:YES]。
Core Animation 是绝大部门 iOS 应用的渲染引擎,熟悉 iOS 渲染流程的同砚想必都知道它的执行也是由 MainRunLoop 驱动,大致为:
- MainRunLoop 由于用户操纵/Timer/GCD 等被唤醒,派发相应的变乱/回调
- 回调中应用修改 Layer Tree,触发 setNeedsLayout 或 setNeedsDisplay
- MainRunLoop 即将完成本次执行,在即将休眠前向 Observer 派发 BeforeWaiting 变乱
- BeforeWaiting 中触发 Core Animation 注册的 MainRunLoop Observer,触发事务提交 CA::Transaction::commit():
- 自顶向下触发各种 Layout/Display 等逻辑,更新结构/内容
- Core Animation 将更新后的 Layer Tree 打包发送给 Render Server
随后 MainRunLoop 进入休眠
Render Server 将打包好的 Layer Tree 解码,天生并提交对应的 draw calls
GPU 执行渲问鼎令,渲染出 FrameBuffer,待后续 VSync 信号来暂时上屏展示
上图中 +[CATransaction setDisableRunLoopObserverCommits:YES] 这个调用给了笔者提示,让我们验证一下 CA::Transaction::commit() 在 iOS 15 ProMotion 装备上的执行时机,会发现确实不再由 BeforeWaiting 变乱驱动了:
现实上同样的 Source 0 信号同时也驱动了 CADisplayLink 的回调:
关注这个 Source 0 的回调符号 runloopSourceCallback,会发现这个 Source0 是由 signalChanges 函数驱动:
而 signalChanges 又是由多个回调所驱动:
此中:
- runloopObserverCallback 为一个 BeforeWaiting 的 MainRunLoop observer 驱动。
- runloopTimerCallback 由 mk_timer 驱动,对应的 mach_port 不明,测试发现其回调频率在 1Hz 左右,但也会不断变革,推测是某种系统计时器。
- inputGroupSignaledCallback 由 mk_timer 驱动,对应的 mach_port 正是 VSync 信号。
- requestRegistrySignaledCallback 由 UIScrollView 在即将开始滑动时驱动。
通过上面的分析,笔者有来由以为在 iOS 15 上应用的渲染驱动机制出现了比力大的变革。此中之一便是 DisplayLink 的驱动源的改变。
结论
- iOS 15 上 Apple 改变了在 ProMotion 装备的渲染变乱循环的驱动方式,CoreAnimation 的事务提交不再由完全由 RunLoop 驱动,而是涉及了多个信号源
- 系统动态帧率选择的机制会综合考虑利用方设置的 API(如 preferredFrameRateRange)和现实展示的内容的变革频率。具体对 CADisplayLink 而言:
- 内容低速变革时,CADisplayLink 解锁高刷新率仅影响自身的回调频率,系统仍可能选择较低的屏幕刷新率来降低功耗
- 内容中高速变革时,CADisplayLink 解锁高刷新率可以让系统选择更高的刷新频率,甚至实现锁定 120Hz 的刷新
关于如何界定低速/中高速,笔者在下文中 CAAnimation 设置动态帧率 部门做了一些试验,可作为参考。
同时,默认设置的 CADisplayLink 回调频率最高为 60Hz,无法监控更高频率的刷新变乱。
- ProMotion 装备中,DisplayLink 不再由 VSync 信号直接驱动,而是在新引入的渲染变乱循环中执行。新版本 iOS 系统实现了某种更复杂的机制来尽可能满意利用者设置的偏好频率举行回调,但并不包管它与 VSync 信号的强关联性。这意味着默认的 CADisplayLink 的回调频率与现实帧率并不匹配,之前基于 CADisplayLink 举行帧率监控的方案在 ProMotion 装备上变得不再可行。
动态帧率的应用场景
监控动态帧率下的流通度体现
业界中一般接纳 CADisplayLink 对应用的流通度举行监控。由于 CADisplayLink 的行为在 iOS 15 上的变革,原先的监控方案无法评估 ProMotion 屏幕在超过 60Hz 时的体现。
根据上面的探索结论,现在笔者设想了三种针对 ProMotion 装备的兼容性修改方案:
方案一 [Pass]
对于任何装备都以 60Hz 为优化目标,只考虑刷新隔断长于 16.67ms 的情况。换句话说,在屏幕以 120Hz 刷新时,对于丢 1 帧的情况也以为不丢帧,由于此时两帧之间的隔断仍旧小于 16.67ms,理论上用户感知不大。
优点:
- 方案简单,仅需设置 preferredFramesPerSecond 为固定值 60 即可
- 兼容之前的指标。依然可以盘算 FPS 指标,对于刷新率高于 60Hz 的情况同一以为刷新率为 60Hz
缺点:
- 由于只能监控最高 60Hz 的情况,无法评估更高刷新率下一些微小丢帧对用户体验带来的影响,也无法评估对高刷屏的一些优化所带来的技术影响
- 在低刷新率时,MainRunLoop 依然会以 60Hz 运行,对功耗有一定影响
方案二 [Pass]
通过一些手段,可以替换驱动 display_timer_callback 的 Source 1 信号的回调,利用它来正确监听 VSync 信号,实现对动态帧率的正确监控。
优点:
- 理论上最精确的监控方案
- 对功耗的影响最小,回调频率只有在屏幕刷新率现实升高时才会随之提升
缺点:
- 利用了私有 API
- FPS 指标从此不再适用
- VSync 信号现在和渲染流程不完全匹配,虽然精确但不一定实用
方案三 [Pick]
通过在 CADisplayLink 回调中确认 duration 参数,盘算得到当前屏幕的实时刷新率,并修改 preferredFrameRateRange 来举行跟踪。
优点:
方案相对简单,只需在每次回调中更新 DisplayLink 对象的 preferredFrameRateRange 属性即可
缺点:
- 由于动态帧率的存在,FPS 指标可以反映实时屏幕刷新情况,但是聚合后的意义不大,消耗时需要区分特定机型/场景
- 观察到现在的最小回调频率为 60Hz,也就是说无法确认 ProMotion 屏幕在 48Hz、30Hz 甚至更低刷新率下的体现
- 在低刷新率时,MainRunLoop 依然会以 60Hz 运行,对功耗有一定影响
需要注意的是,CADisplayLink 的 preferredFrameRateRange 需要以雷同一下格式举行设置:
NSInteger currentFPS = (NSInteger)ceil(1.0 / displayLink.duration);
displayLink.preferredFrameRateRange = CAFrameRateRangeMake(10.0, currentFPS, 0.0);
CAFrameRateRange.minimum 传最小值 10.0,preferred 传 0.0,可以让该 CADisplayLink 只用于监控当前的系统帧率,而不影响帧率的动态选择。
相比前两个方案,方案三改动小,不利用私有 API,监控正确性也较高,缺点相对来说可以接受。
FPS 的替代指标
考虑到在 ProMotion 屏幕上 FPS 指标不再与应用运行是否流通直接相关,它的聚合值参考代价不大,有须要寻找一个新指标作为替换。
Apple 官方在 WWDC20 - 10077 Eliminate animation hitches with XCTest 中先容了 Hitch Time Ratio 这一概念,并偏重阐明白它比单纯的 FPS 更能适配差异刷新率的场景。
在 XCTest 框架中,苹果提供了 API XCTOSSignpostMetric 资助开发者在单测中即时地获取该指标,但相关 API 尽在单测中提供,线上无法利用。而 MetricKit 中的 MXAnimationMetric 只管可以在线上获取,但却不是实时的,无法满意大型 App 对差异场景的监控需求。
因此,遵照下面 Apple 对 Hitch Ratio 的界说:
Hitch time:
- Time in ms that a frame is late to display.
Hitch time ratio:
- Hitch time in ms per second for a given duration.
笔者实验实现了基于 CADisplayLink 的 (Scroll) Hitch Time Ratio 的盘算方案:
- 盘算上一帧的帧时间戳与上上一帧的目标帧时间戳得到上一帧的 Hitch Time
- 确定该帧是否是在滑动中渲染
- 累计得到团体的 Hitch Frame,与累积的帧隔断相比,得到 (Scroll) Hitch Time Ratio
关键场景提升帧率
在测试过程中笔者发现,系统 App 滑动时是稳固以最高刷新率 120Hz 运行的:
而第三方 App 即便设置了 CADisableMinimumFrameDurationOnPhone 为 true 也无法稳固以满帧率滑动(经过验证,这一点在 iOS 15.4 beta 系统上依然成立)。
通过利用 iOS 15 引入的新 API,我们可以在关键场景如滑动、转场、动画过程中主动解锁更高/限定更低的动态帧率,从而优化流通度大概优化功率,提升用户体验目标。
滑动中稳固 120Hz
首先,笔者希望非系统 App 也可以尽可能实现滑动中稳固 120Hz 刷新。
结合上述分析,这一点可以用 CADisplayLink 来实现。这里笔者提出两种可能方案仅供参考:
- 创建 CADisplayLink,设置其 preferredFramesPerSecond 为 120,然后将其添加到 UITrackingRunLoopMode 中。
CADisplayLink *dp = …
dp.preferredFramesPerSecond = 120;
// 大概
dp.preferredFrameRateRange = CAFrameRateRangeMake(120.0, 120.0, 0.0);
[dp addToRunLoop:[NSRunLoop mainRunLoop] forMode:UITrackingRunLoopMode];
在滑动中,该 CADisplayLink 被激活,系统锁定当前帧率为最高 120Hz(仅在内容中高速变革时生效)。制止滑动时则规复正常帧率。
- 添加 CADisplayLink 至 CommonModes 中,分别在开始/制止滑动时启用/停息 CADisplayLink,并修改对应的 preferredFramesPerSecond等属性,触发帧率变革。
CADisplayLink *dp = …
dp.paused = YES;
[dp addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
CFRunLoopAddObserver(CFRunLoopGetMain(),
CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopEntry | kCFRunLoopExit, YES, 0,
^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
if (activity == kCFRunLoopEntry) {
dp.paused = NO;
dp.preferredFramePerSecond = 120;
} else {
dp.paused = YES;
dp.preferredFramePerSecond = 0;
}
}), (__bridge CFStringRef)UITrackingRunLoopMode);
在实践中,由于也存在需要在非滑动状态下解锁帧率上限的情况,以是方案 2 的通用性会更好。
CAAnimation 设置动态帧率
现在苹果只提供了修改 CAAnimation 动画帧率的 API,设置 CAAnimation.preferredFrameRateRange 即可改变其对屏幕刷新率的影响。
- 对于用户感知明显的,如转场动画,可以设置为 120Hz。
- 对于感知不明显的,如旋转动画,可以降低其帧率,好比设置为 30Hz。
但是,和 DisplayLink 相同,过上述 API 的设置虽然会“影响”系统的动态帧率的选择,但这种影响并不是绝对的。在现实利用中,笔者发现屏幕选择的刷新率和 CAAnimation 在屏幕上变革的速度有关。
关于此点,以 iPhone 13 Pro 为例,笔者利用了一个简单的、偏好帧率为固定 120Hz 平移动画举行阐明:
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath “transform.translation.y”];
CGFloat speed = 170.0/330.0;
anim.toValue = @(100);
anim.fromValue = @(0);
anim.duration = 10.0;
anim.repeatCount = FLT_MAX;
anim.preferredFrameRateRange = CAFrameRateRangeMake(120, 120, 120);
此中 speed 变量为平移的速度,单位为 pt/s,试验发现:
- speed 取 (0, 160] 时,屏幕刷新率为 60Hz
- speed 取 [161, 320] 时,屏幕刷新率为 80Hz
- speed 取 [321, +∞) 时,屏幕刷新率为 120Hz
笔者仅在 iPhone 13 Pro 上测试了平移动画的场景,以上数据仅供参考。
末了,对于其他的常见的动画 API,例如 UIView.animateWithDuration、UIViewPropertyAnimator 等,则没有提供对应 API 举行修改。理论上也可以通过某些手段拿到这些上层 API 所创建的 CAAnimation 对象来实现修改。
手势/转场等其他场景解锁 120Hz
其他场景需要控制动态帧率的也可以通过手动修改 CADisplayLink 的 preferredFramePerSecond/preferredFrameRateRange 属性来实现,其实现和通过监听 RunLoop 来修改滑动帧率基本相同。
UIGestureRecognizer 常被用于实现的交互式动画。经过测试,发如今触发手势回调的同时启用一个解锁了频率的 CADisplayLink 也可以间接进步 UIGestureRecognizer 的回调频率,从而实现更高帧率的交互动画。
对于转场的场景,一个简单的方案是 swizzle UIViewController 的生命周期消息,在出现/消失的节点启用/停用 CADisplayLink 帧率的解锁,从而实现通用的页面转场动画帧率解锁方案。
Flutter 官方也筹划提供雷同 API 让应用侧可以针对差异的场景(滑动、动画 etc)动态切换屏幕刷新率:https://github.com/flutter/flutter/issues/90675
上线收益
基于上述思绪,笔者地点团队在国际化短视频业务落地了优化项目,经过实验验证:
- 大盘滑动帧率 P50 从 81.57 上升至 112.2
- 核心业务指标也有一定收益
结语
–
近年来,Apple 生态中软硬件的发展日新月异,有软件层的 dyld 的持续优化和 iOS 15 新引入的 Prewarm 机制,也有新的 ProMotion 屏幕,可以看到 Apple 一直致力于打造更丝滑流通的用户体验。
Apple 提供的系统级优化方案一般通用而无感知,但通用往往也意味着一定的局限性,可能预留了额外优化空间,应用开发者们可以进一步去研究如何更好地适配。
例如本文中,笔者通过研究新引入的 ProMotion 屏幕背后的机制,透过表象/深入汇编管中窥豹看到一部门本质,最终落地了监控 + 优化的方案,让大盘滑动帧率 P50 从 80 上升至 112 左右,取得了额外的业务收益。
末了,笔者以为,我们普通开发者作为 Apple 生态链中的一环,在享受系统级别优化自动带来的收益的同时,也应该主动去了解上述优化背后的底层原理。一方面,了解与学习 Apple 的成熟优化思绪可以提升我们作为工程师的眼界。另一方面,对系统底层原理的了解可以拓充我们的“弹药库”,对业务代价交付的全链路了解越广越深,越有可能抓住潜在的优化点,从而在性能优化工程师这条职业门路上走得更远更好。
参考资料
- WWDC20 - 10077 Eliminate animation hitches with XCTest
https://developer.apple.com/videos/play/wwdc2020/10077
- WWDC21 - 10147 Optimize for variable refresh rate displays
https://developer.apple.com/videos/play/wwdc2021/10147/
- Optimizing ProMotion Refresh Rates for iPhone 13 Pro and iPad Pro
https://developer.apple.com/documentation/quartzcore/optimizing_promotion_refresh_rates_for_iphone_13_pro_and_ipad_pro?language=objc
https://www.viewsonic.com/library/tech/explained/what-is-adaptive-sync/
- https://github.com/flutter/flutter/issues/90675
自我先容一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到如今。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长大概是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学结果低效又漫长,而且极易遇到天花板技术故步自封!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初志也很简单,就是希望可以或许资助到想自学提升又不知道该从何学起的朋侪,同时减轻大家的负担。
既有适合小白学习的零底子资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比力大,这里只是将部门目次截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、解说视频,并且会持续更新!
假如你觉得这些内容对你有资助,可以扫码获取!!(备注:Android)
末了
下面是有几位Android行业大佬对应上方技术点整理的一些进阶资料。希望可以或许资助到大家提升技术
高级UI,自界说View
UI这块知识是现今利用者最多的。当年火爆一时的Android入门培训,学会这小块知识就能随便找到不错的工作了。
不过很显然如今远远不敷了,拒绝无休止的CV,亲身去项目实战,读源码,研究原理吧!
《Android学习笔记总结+移动架构视频+大厂口试真题+项目实战源码》,点击传送门即可获取!
小。自己不成体系的自学结果低效又漫长,而且极易遇到天花板技术故步自封!**
因此收集整理了一份《2024年Android移动开发全套学习资料》,初志也很简单,就是希望可以或许资助到想自学提升又不知道该从何学起的朋侪,同时减轻大家的负担。
[外链图片转存中…(img-yA9eZ9o2-1712280090795)]
[外链图片转存中…(img-GK2VUzZy-1712280090796)]
[外链图片转存中…(img-IlHN2MwZ-1712280090796)]
[外链图片转存中…(img-IuYl8HDq-1712280090796)]
[外链图片转存中…(img-v5BVKx61-1712280090797)]
既有适合小白学习的零底子资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比力大,这里只是将部门目次截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、解说视频,并且会持续更新!
假如你觉得这些内容对你有资助,可以扫码获取!!(备注:Android)
末了
下面是有几位Android行业大佬对应上方技术点整理的一些进阶资料。希望可以或许资助到大家提升技术
[外链图片转存中…(img-jxycgc3A-1712280090797)]
高级UI,自界说View
UI这块知识是现今利用者最多的。当年火爆一时的Android入门培训,学会这小块知识就能随便找到不错的工作了。
不过很显然如今远远不敷了,拒绝无休止的CV,亲身去项目实战,读源码,研究原理吧!
[外链图片转存中…(img-ji105wAF-1712280090797)]
《Android学习笔记总结+移动架构视频+大厂口试真题+项目实战源码》,点击传送门即可获取!
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |