master
Omooo 4 years ago
parent 7164e09415
commit dbb282743a
  1. 4
      blogs/Android/口水话/Gradle.md
  2. 2
      blogs/Android/口水话/View 体系相关口水话.md
  3. 2
      blogs/Android/口水话/四大组件的启动流程口水话.md
  4. 2
      blogs/Android/口水话/插件化、热修复相关口水话.md
  5. 2
      blogs/Java/口水话/集合源码.md
  6. 2
      blogs/computer_network/口水话/HTTP、HTTPS、HTTP2.md

@ -28,9 +28,9 @@ Execution 阶段才真正进行任务的执行。Gradle 会按照 task graph 中
考虑到后面可能还会有一些类似的需求,于是我就写了一个插件。我觉得这是一个非常好的开端,之所以当初组长找到我做这件事是因为我之前在组内分享了 Gradle 的相关知识,包括 Gradle 的构建流程、Gradle 的核心概念 Task 以及利用 Transform API 结合 AspectJ、ASM 进行自动化埋点等。这对我来说呢是一个正向激励,后面我也越来越会做更多的分享。 考虑到后面可能还会有一些类似的需求,于是我就写了一个插件。我觉得这是一个非常好的开端,之所以当初组长找到我做这件事是因为我之前在组内分享了 Gradle 的相关知识,包括 Gradle 的构建流程、Gradle 的核心概念 Task 以及利用 Transform API 结合 AspectJ、ASM 进行自动化埋点等。这对我来说呢是一个正向激励,后面我也越来越会做更多的分享。
后面呢,我还在 Plugin 里面添加了自动 TinyPng 资源压缩,考虑到我们的 minApi 19,又做了全量的 png 转 webp 这一步的压缩是包含第三方库里面的图片的,使用的是 Google 开源的 cwebp 工具。通过 png 转 webp,减少了 5.7M,再来做包体积优化时,发现存在不少的重复资源,也就是遍历生成 MD5 值进行比较,减少了 133 kb;通过配置 resConfigs 只保留 zh、en 减少了 1.1M。 后面呢,我还在 Plugin 里面添加了自动 TinyPng 资源压缩,考虑到我们的 minApi 19,又做了全量的 png 转 webp 这一步的压缩是包含第三方库里面的图片的,使用的是 Google 开源的 cwebp 工具。通过 png 转 webp,减少了 5.7M,再来做包体积优化时,发现存在不少的重复资源,也就是遍历生成 MD5 值进行比较,减少了 133 kb;通过配置 resConfigs 只保留 zh、en 减少了 1.1M。
再之后,基于做了 MethodTracker,简单使用一个注解就可以查看方法耗时,还有基于 Choreographer 的 FPS 检测。这里当时是遇到一个问题的,我们知道插件里面的类,在外部模块是不能访问到的,然而我这里的注解以及 FPSDetector 都是需要在 app 模块使用的,这时候我就看了 JW 的 Hugo 的实现,它也是基于 AspectJ 做的方法耗时,因为它的注解也是可以在 app 模块使用的,看源码发现它不过是是把这个注解放到外部一个远程库的,然后在插件里面进行依赖的。Hugo 仅仅只有四个类,却有 7k 多的 Star,所以说有时候不能被表面吓着,以为会很麻烦其实很简单。 再之后,基于 ASM 做了 MethodTracker,简单使用一个注解就可以查看方法耗时,还有基于 Choreographer 的 FPS 检测。这里当时是遇到一个问题的,我们知道插件里面的类,在外部模块是不能访问到的,然而我这里的注解以及 FPSDetector 都是需要在 app 模块使用的,这时候我就看了 JW 的 Hugo 的实现,它也是基于 AspectJ 做的方法耗时,因为它的注解也是可以在 app 模块使用的,看源码发现它不过是是把这个注解放到外部一个远程库的,然后在插件里面进行依赖的。Hugo 仅仅只有四个类,却有 7k 多的 Star,所以说有时候不能被表面吓着,以为会很麻烦其实很简单。
在我写 Plugin 的之后,滴滴开源了 Booster,然后我就按照它的架构方式,使用 SPI 去自动注册 Task,这就不需要每新增一个 Task 都要去 apply 方法进行注册了。当时是用 Java 写的,也都切成了 Kotlin,并且也用的 kts 构建。 在我写 Plugin 的之后,滴滴开源了 Booster,然后我就按照它的架构方式,使用 SPI 去自动注册 Task,这就不需要每新增一个 Task 都要去 apply 方法进行注册了。当时是用 Java 写的,也都切成了 Kotlin,并且也用的 kts 构建。

@ -21,7 +21,7 @@ WMS 是所有 Window 窗口的管理者,它负责 Window 的添加和删除、
接下来就是正式绘制 View 了,从 performTraversals 开始,Measure、Layout、Draw 三步走。 接下来就是正式绘制 View 了,从 performTraversals 开始,Measure、Layout、Draw 三步走。
第一步是获取 DecorView 的宽高的 MeasureSpec 然后执行 performMeasure 流程。MeasureSpec 简单来说就是一个 int 值,高 2 位表示测量模式,低 30 位用来表示大小。策略模式有三种,EXACTLY、AT_MOST、UNSPECIFIED。EXACTLY 对应为 match_parent 和具体数值的情况,表示父容器已经确定 View 的大小;AT_MOST 对应 wrap_content,表示父容器规定 View 最大只能是 SpecSize;UNSPECIFIED 表示不限定测量模式,父容器不对 View 做任何限制,这种适用于系统内部。接着说,performMeasure 中会去调用 DecorView 的 measure 方法,这个是 View 里面的方法并且是 final 的,它里面会把参数透传给 onMeasure 方法,这个方法是可以重写的,也就是我们可以干预 View 的测量过程。在 onMeasure 中,会通过 getDefaultSize 获取到宽高的默认值,然后调用 setMeasureDimension 将获取的值进行设置。在 getDefaultSize 中,无论是 EXACTLY 还是 AT_MOST,都会返回 MeasureSpec 中的大小,这个 SpecSize 就是测量后的最终结果。至于 UNSPECIFIED 的情况,则会返回一个建议的最小值,这个值和子元素设置的最小值以及它的背景大小有关。从这个默认实现来看,如果我们自定义一个 View 不重写它的 onMeasure 方法,那么 warp_content 和 match_parent 一样。所以 DecorView 重写了 onMeasure 函数,它本身是一个 FrameLayout,所以最后也会调用到 FrameLayout 的 onMeasure 函数,作为一个 ViewGroup,都会遍历子 View 并调用子 View 的 measure 方法。这样便实现了层层递归调用到了每个子 View 的 onMeasure 方法进行测量。 第一步是获取 DecorView 的宽高的 MeasureSpec 然后执行 performMeasure 流程。MeasureSpec 简单来说就是一个 int 值,高 2 位表示测量模式,低 30 位用来表示大小。测量模式有三种,EXACTLY、AT_MOST、UNSPECIFIED。EXACTLY 对应为 match_parent 和具体数值的情况,表示父容器已经确定 View 的大小;AT_MOST 对应 wrap_content,表示父容器规定 View 最大只能是 SpecSize;UNSPECIFIED 表示不限定测量模式,父容器不对 View 做任何限制,这种适用于系统内部。接着说,performMeasure 中会去调用 DecorView 的 measure 方法,这个是 View 里面的方法并且是 final 的,它里面会把参数透传给 onMeasure 方法,这个方法是可以重写的,也就是我们可以干预 View 的测量过程。在 onMeasure 中,会通过 getDefaultSize 获取到宽高的默认值,然后调用 setMeasureDimension 将获取的值进行设置。在 getDefaultSize 中,无论是 EXACTLY 还是 AT_MOST,都会返回 MeasureSpec 中的大小,这个 SpecSize 就是测量后的最终结果。至于 UNSPECIFIED 的情况,则会返回一个建议的最小值,这个值和子元素设置的最小值以及它的背景大小有关。从这个默认实现来看,如果我们自定义一个 View 不重写它的 onMeasure 方法,那么 warp_content 和 match_parent 一样。所以 DecorView 重写了 onMeasure 函数,它本身是一个 FrameLayout,所以最后也会调用到 FrameLayout 的 onMeasure 函数,作为一个 ViewGroup,都会遍历子 View 并调用子 View 的 measure 方法。这样便实现了层层递归调用到了每个子 View 的 onMeasure 方法进行测量。
第二步是执行 performLayout 的流程,也就是调用到 DecorView 的 layout 方法,也就是 View 里面的方法,如果 View 大小发生变化,则会回调 onSizeChanged 方法,如果 View 状态发生变化,则会回调 onLayout 方法,这个方法在 View 中是空实现,因此需要看 DecorView 的父容器 FrameLayout 的 onLayout 方法,这个方法就是遍历子 View 调用其 layout 方法进行布局,子 View 的 layout 方法被调用的时候,它的 onLayout 方法又会被调用,这样就布局完了所有的 View。 第二步是执行 performLayout 的流程,也就是调用到 DecorView 的 layout 方法,也就是 View 里面的方法,如果 View 大小发生变化,则会回调 onSizeChanged 方法,如果 View 状态发生变化,则会回调 onLayout 方法,这个方法在 View 中是空实现,因此需要看 DecorView 的父容器 FrameLayout 的 onLayout 方法,这个方法就是遍历子 View 调用其 layout 方法进行布局,子 View 的 layout 方法被调用的时候,它的 onLayout 方法又会被调用,这样就布局完了所有的 View。

@ -19,7 +19,7 @@ Launcher 组件是如何获取这些信息的呢?其实是在系统启动时
在 Instrumentation 的 execStartActivity 中,通过 ActivityManagerNative 的 getDefault 获取 AMS 的一个代理对象,实际上就是调用 ServiceManager 的 getService,获取的是一个 IActivityManager 接口,这也是一个 Hook 点。然后就调到了它的实现类 ActivityManagerProxy 中的 startActivity 方法,在这个方法中,就会往 AMS tranact 一个 START_ACTIVITY_TRANSACTION 的请求。 在 Instrumentation 的 execStartActivity 中,通过 ActivityManagerNative 的 getDefault 获取 AMS 的一个代理对象,实际上就是调用 ServiceManager 的 getService,获取的是一个 IActivityManager 接口,这也是一个 Hook 点。然后就调到了它的实现类 ActivityManagerProxy 中的 startActivity 方法,在这个方法中,就会往 AMS tranact 一个 START_ACTIVITY_TRANSACTION 的请求。
在 AMS 收到这个请求时,就会让 ActivityStack 去处理。它首先根据上面传递过来的 ApplicationThread 去通知 Launcher 所在的应用程序进程去暂停 Launcher 组件,也就是回调到 ActivityThread 的内部类 ApplicationThread 的 schedulePauseActivity,往主线程发送一个 PAUSE_ACTIVITY 消息,mH 进行处理执行 handlePauseActivity。这个方法先调用 Activity 的 onPause 函数,然后执行 QueuedWork 里面的一些数据写入操作,SharedPreferences 的 apply 就会丢在这执行,如果数据量毕竟大的话可能会造成 ANR 的。最后就是通知 AMS Launcher 组件已经暂停完成了。 在 AMS 收到这个请求时,就会让 ActivityStack 去处理。它首先根据上面传递过来的 ApplicationThread 去通知 Launcher 所在的应用程序进程去暂停 Launcher 组件,也就是回调到 ActivityThread 的内部类 ApplicationThread 的 schedulePauseActivity,往主线程发送一个 PAUSE_ACTIVITY 消息,mH 进行处理执行 handlePauseActivity。这个方法先调用 Activity 的 onPause 函数,然后执行 QueuedWork 里面的一些数据写入操作,SharedPreferences 的 apply 就会丢在这执行,如果数据量比较大的话可能会造成 ANR 的。最后就是通知 AMS Launcher 组件已经暂停完成了。
再回到 AMS 中,就开始真正的去启动 MainActivity 了,首先检查这个 Activity 对应的 ActivityRecord 对象所在的应用程序进程是否已经存在,如果存在就直接执行 realStartActivityLocked,如果不存在就先 startProcessLocked 去创建一个应用程序进程。创建应用程序进程即调用 Porcess start 函数,并指定该进程的入口函数为 ActivityThread,这一步其实是请求 Zygote fork 出一个应用程序进程。在 ActivityThread 的 main 函数中,创建消息循环,并且调用 attachApplication 把当前的 ApplicationThread 对象传递给 AMS,AMS 有了它就可以去通知应用程序去执行 MainActivity 的 onCreate 方法了。同上述执行 Launcher 的 onPause 函数一样,也是发一个消息给主线程,调用 handleLauncherActivity。 再回到 AMS 中,就开始真正的去启动 MainActivity 了,首先检查这个 Activity 对应的 ActivityRecord 对象所在的应用程序进程是否已经存在,如果存在就直接执行 realStartActivityLocked,如果不存在就先 startProcessLocked 去创建一个应用程序进程。创建应用程序进程即调用 Porcess start 函数,并指定该进程的入口函数为 ActivityThread,这一步其实是请求 Zygote fork 出一个应用程序进程。在 ActivityThread 的 main 函数中,创建消息循环,并且调用 attachApplication 把当前的 ApplicationThread 对象传递给 AMS,AMS 有了它就可以去通知应用程序去执行 MainActivity 的 onCreate 方法了。同上述执行 Launcher 的 onPause 函数一样,也是发一个消息给主线程,调用 handleLauncherActivity。

@ -9,7 +9,7 @@
#### 热修复 #### 热修复
热修复,我所了解的一种实现方式就是类加载方案,即 dex 插桩,这种思路在插件化中也会用到。除此之外,还有底层替换方案,即修改 替换 ArtMethod。采用类加载方案的主要是以腾讯系为主,包括微信的 Tinker、饿了么的 Amigo;采用底层替换方案主要是阿里系的 AndFix 等。 热修复,我所了解的一种实现方式就是类加载方案,即 dex 插桩,这种思路在插件化中也会用到。除此之外,还有底层替换方案,即修改替换 ArtMethod。采用类加载方案的主要是以腾讯系为主,包括微信的 Tinker、饿了么的 Amigo;采用底层替换方案主要是阿里系的 AndFix 等。
#### 插件化 #### 插件化

@ -6,7 +6,7 @@
起初想看集合源码是因为,一次偶然的机会,一位同事跟我说 ArrayList 的初始容量为空,第一次 add 时才会扩容至 10。我当时就觉得我的知识体系有点落后了,就决定去看一遍集合源码。在看的过程中呢,可以用惊喜+收获满满来形容了。惊喜是指对于 Stack、HashSet、LinkedHashMap 等等的实现方式感觉很震惊,看完这些集合源码有种那种你感觉很难但是其实很简单的感觉,不仅如此,熟悉集合源码对实际开发也是有帮助的,主要体现在 ArrayList remove 时可能抛出的 ConcurrentModificationException、Collections 使用不当造成的 UnsupportedOperationException、ArrayMap 的缓存机制等等。 起初想看集合源码是因为,一次偶然的机会,一位同事跟我说 ArrayList 的初始容量为空,第一次 add 时才会扩容至 10。我当时就觉得我的知识体系有点落后了,就决定去看一遍集合源码。在看的过程中呢,可以用惊喜+收获满满来形容了。惊喜是指对于 Stack、HashSet、LinkedHashMap 等等的实现方式感觉很震惊,看完这些集合源码有种那种你感觉很难但是其实很简单的感觉,不仅如此,熟悉集合源码对实际开发也是有帮助的,主要体现在 ArrayList remove 时可能抛出的 ConcurrentModificationException、Collections 使用不当造成的 UnsupportedOperationException、ArrayMap 的缓存机制等等。
在看集合源码时,Debug 源码有时并不直接,可以使用 OpenJDK 开源的 JOI 工具查看对象的内存布局,这对分析集合扩容、HashMap 树化非常有用。 在看集合源码时,Debug 源码有时并不直接,可以使用 OpenJDK 开源的 JOL 工具查看对象的内存布局,这对分析集合扩容、HashMap 树化非常有用。
#### 目录 #### 目录

@ -46,5 +46,5 @@ TLS1.3 在 2018 年正式发布,它移除了很多已证明不安全的套件
HTTP2 有三个核心概念,Strem 流、Message 消息、Frame 帧。在一个 TCP 连接上可以有多个流,也就是并发多请求,实现多路复用;以前浏览器是只能对一个域名开启 6 个并发链接;同时流还可以设置优先级,让服务器优先处理;一条流上可以有多个 Message 消息,每个 Message 都可以携带多个 Frame 帧,帧类型包括 HEADER 帧和 DATA 帧,也就对应于 HTTP/1.1 中的 header 和 data。在 HTTP/1.1 中,服务端是没法主动向客户端推送信息的,客户端只能以轮询的方式去请求,在 HTTP2 中有了服务器推送,也就是服务端可以提前将资源推送至浏览器缓存。 HTTP2 有三个核心概念,Strem 流、Message 消息、Frame 帧。在一个 TCP 连接上可以有多个流,也就是并发多请求,实现多路复用;以前浏览器是只能对一个域名开启 6 个并发链接;同时流还可以设置优先级,让服务器优先处理;一条流上可以有多个 Message 消息,每个 Message 都可以携带多个 Frame 帧,帧类型包括 HEADER 帧和 DATA 帧,也就对应于 HTTP/1.1 中的 header 和 data。在 HTTP/1.1 中,服务端是没法主动向客户端推送信息的,客户端只能以轮询的方式去请求,在 HTTP2 中有了服务器推送,也就是服务端可以提前将资源推送至浏览器缓存。
HTTP2 只在应用层解决了队头阻塞问题,并未在 TCP 层解决队头阻塞问题。TCP 的报文传输时无序,接收时组装。如果队头包没有到达,即使后序数据包已经接到了也是没办法交给上层应用程序处理的,只能等待重发。而在 HTTP3 采用了 UDP,UDP 先天没有队列的概念,自然就解决了队头阻塞的问题,但是它仍然保留了 TCP 的可靠性,这也需要 QUIC 协议自己去实现重传机制、拥塞控制等机制。 HTTP2 只在应用层解决了队头阻塞问题,并未在 TCP 层解决队头阻塞问题。TCP 的报文传输时无序,接收时组装。如果队头包没有到达,即使后序数据包已经接到了也是没办法交给上层应用程序处理的,只能等待重发。而在 HTTP3 采用了 UDP,UDP 先天没有队列的概念,自然就解决了队头阻塞的问题,但是它仍然保留了 TCP 的可靠性,这也需要 QUIC 协议自己去实现重传机制、拥塞控制等机制。

Loading…
Cancel
Save