2022年2月记 零散的点

307 阅读7分钟
1. Surface Flinger, Window, Activity, View 的关系
  1. View -> Activity(内部包含Window) -> WindowManagerService -> Surface Flinger
  2. Surface Flinger
    • 专门负责UI内容的渲染.
    • 运行于运行时层,用于和HAL层通信.
    • Surface Flinger不在意渲染的实际内容,将其变成屏幕可以理解的格式,发给HAL
  3. Window
    • 在应用框架层,通过Java进行了封装
    • Window负责可视化内容的排版
    • Window将排版结果,通过WindowManager与WindowManagerService执行Binder通信,最终将排版结果交给Surface Flinger呈现
    • Surface Flinger为每个Window都映射了一份Surface.
  4. ViewGroup
    • 实际View的排版工作,是由ViewGroup完成,通过递归操作,完成了其内部所有控件的measure-layout-draw流程
    • ViewGroup将排版结果发给Window
  5. Activity
    • Activity可以看成是对Window的封装.对系统而言,Activity仍然是一个被管理的窗口.对开发者而言,只关注里面的模板方法即可.
    • Activity是为了实现 多窗口管理 及 视图管理 的需求.
    • Activity主要内容涉及 视图加载及优化, 生命周期, 重建及数据恢复, 窗口间跳转等.
2. 滑动冲突
  1. 滑动冲突产生的原因: 为了保持用户操作的连贯性,尽量避免页面的跳转.让用户在一个界面通过横向及纵向滑动浏览尽可能多的内容.
  2. 滑动冲突常见场景
  • 首页信息流: 纵向RecyclerView,Item是横向的BANNER
    • 解决滑动冲突:
      • RecyclerView本身已经解决了与嵌套布局的滑动冲突
      • 采用 RecyclerView + SnapHelper实现,双重机制避免滑动冲突
      • github.com/zhimaochen/…
  • 详情页: 可折叠顶部 + 粘性 TabLayout + 底部 ViewPager 嵌套 RecyclerView
    • 解决滑动冲突:
      • CoordinateLayout + AppBarLayout + CollapsingToolbarLayout 来 实现粘性浮动及避免滑动冲突
      • github.com/xuexiangjys…
  • 通用的滑动冲突解决方案: NestedScrollingParent2 和 NestedScrollingChild2
    • github.com/sangxiaonia…
    • NestedScrolling 是Andorid 5.0推出的一个嵌套滑动机制,主要是利用 NestedScrollingParent 和 NestedScrollingChild 让父View和子View在滚动时互相协调配合,极大的方便了我们对于嵌套滑动的处理。通过 NestedScrolling 我们可以很简单的实现类似知乎首页,QQ空间首页等非常漂亮的交互效果。
      但是有一个问题,对于fling的传递,NestedScrolling的处理并不友好,child只是简单粗暴的将fling结果抛给parent。对于fling,要么child处理,要么parent处理。当我们想要先由child处理一部分,剩余的再交个parent来处理的时候,就显得比较乏力了;
      在Andorid 8.0 ,推出了一个升级版本 NestedScrollingParent2 和 NestedScrollingChild2 ,友好的处理了fling的分配问题,可以实现非常丝滑柔顺的滑动效果

3. 卡顿检测的几种方法

www.infoq.cn/article/wei…
juejin.cn/post/700618…
juejin.cn/post/697356…
medium.com/androiddeve… developer.android.google.cn/studio/prof…

  1. Choreographer 类,用于同 Vsync 机制配合,实现统一调度界面绘图。 系统每隔 16.6ms 发出 VSYNC 信号,来通知界面进行重绘、渲染,理想情况下每一帧的周期为 16.6ms,代表一帧的刷新频率。开发者可以通过 Choreographer 的 postFrameCallback 设置自己的 callback,你设置的 callcack 会在下一个 frame 被渲染时触发.
    fun monitorF1(view: android.view.View) {
        Log.d(TAG, "monitorF1. start.")
        var lastTime: Long = -1
        var cost: Long = -1
        Choreographer.getInstance().postFrameCallback(object : Choreographer.FrameCallback {
            override fun doFrame(frameTimeNanos: Long) {
                if (lastTime < 0) {
                    lastTime = frameTimeNanos
                } else {
                    cost = (frameTimeNanos - lastTime) / 1000000
                    lastTime = frameTimeNanos
                    Log.d(TAG, "monitorF1. cost:$cost")
                    if(cost > 16){
                        //说明当前帧的刷新 和 上一帧耗时过大,要打印线程
                        //开启子线程获取堆栈信息
                        //dumpStackTraceInNewThread()
                    }
                }
                Choreographer.getInstance().postFrameCallback(this)
            }
        })
    }
    
    * D/LagMonitor: monitorF1. cost:16
    * D/LagMonitor: monitorF1. cost:15
    * D/LagMonitor: monitorF1. cost:16
    
  2. 通过监听主线程对应的Looper实例的loop逻辑.在loog中会执行死循环:
    //获取Looper实例
    final Looper me = myLooper();
    //开启死循环
    for(;;){
        //1: 获取Message实例
        Message msg = queue.next();
        //2: 获取Looper实例关联的Printer实例
        final Printer logging = me.mLogging;
        //3: Message处理前打印
        if (logging != null) {
            logging.println(">>>>> Dispatching to " + msg.target + " " +
                    msg.callback + ": " + msg.what);
        }
        //4: Message处理
        msg.target.dispatchMessage(msg);
        //5: Message处理后打印
        if (logging != null) {
            logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
        }
    }
    
    //可以设置Looper实例的Printer
    public void setMessageLogging(@Nullable Printer printer) {
        mLogging = printer;
    }
    
    • Looper实例执行setMessageLoggine可以设置loop死循环中,打印日志的Printer实例
    • 通过传入的自定义Printer实例,可以在println中获取得到Message及Message执行完毕的时间,进而判断当下Message处理时间是否过长.
    • 发现指定Message处理时间过长,此时再去获取堆栈信息也晚了,已经执行完了.应该开启单独线程,定时获取主线程堆栈信息保存为 时间:堆栈信息 的映射.
    • 发现指定Message处理时间过长,则将对应时间段的堆栈打印出来.
      • 为避免一直保持堆栈信息,可以设置指定时间后将对应信息删除.
  3. 字节码插桩实现在方法头尾添加方法耗时统计
    • 通过Gradle Plugin+ASM,编译期在每个方法开始和结束位置分别插入一行代码,统计方法耗时
    插桩前
    fun method(){
       run()
    }
    
    插桩后
    fun method(){
       input(1)
       run()
       output(1)
    }
    
    • 字节码插桩时可以添加对调用线程的判断,比如非主线程调用可以不处理
    • 目前微信的Matrix 使用的卡顿监控方案就是字节码插桩
      • 避免方法数暴增:在方法的入口和出口应该插入相同的函数,在编译时提前给代码中每个方法分配一个独立的 ID 作为参数。
      • 过滤简单的函数:过滤一些类似直接 return、i++ 这样的简单函数,并且支持黑名单配置。对一些调用非常频繁的函数,需要添加到黑名单中来降低整个方案对性能的损耗。
  4. JankStats Library
    • 使用Google提供的JankStates,可以在代码中实时监听指定Window的卡顿情况
    • JankStates好处是集成简单,且可用在State中自由添加不同方向的信息
    • 通过FrameData中的isJank属性,由系统自动判断是否发生了卡顿.可以直接将之前保存的不同方向的可疑点的值全打印出来.
4. synchronize , 重量级锁 , 轻量级锁 , 偏向锁 , 公平锁 , 非公平锁 , 悲观锁 , 乐观锁

juejin.cn/post/684490…
www.cnblogs.com/butterfly10…
juejin.cn/post/684490…
mp.weixin.qq.com/s?__biz=Mzk…
Synchronized 升级到重量级锁之后就下不来了?你错了!
juejin.cn/post/684490…
juejin.cn/post/684490…

  1. 锁有4种状态
  • 无锁状态, 偏向锁状态, 轻量级锁状态, 重量级锁状态
  • 随着线程竞争激烈,锁逐渐升级
  • 锁可以升级,但不可降级
  1. 重量级锁
  • 在JDK1.6之前内置锁的实现方式.简单说,重量级锁是采用互斥量来控制对互斥资源的访问.
  • 在JDK1.6之前, synchronized实现的内置锁都是重量级锁.因为synchronized需要使用JVM的monitorenter和monitorexit, 本质上是依赖操作系统的 Mutex Lock 互斥量 来实现的,我们一般称之为重量级锁.
  • 使用Mutex Lock需要将当前线程挂起并从用户态切换到内核态来执行,耗时较长,切换代价非常高,尤其是同步代码块频繁执行的情况.
  1. 轻量级锁
  • 轻量级锁是相对于重量级锁而言,本意是在没有多线程竞争情况下,减少重量级锁使用的OS的互斥量带来的性能损耗.
  • 轻量级锁提升性能的依据是绝大多数情况下,同步代码块执行过程中不存在线程竞争,可以使用CAS操作避免互斥量的开销
  • 轻量级锁在加锁过程会执行CAS操作
    • CAS操作成功则加锁成功
    • CAS操作失败有2种可能
      • 当前线程已经获取锁,直接执行同步代码块即可
      • 锁对象当前被其他线程锁定.当前线程会执行一定次数的自旋,尝试获取锁
  • 自旋: 执行一段无意义的类似循环逻辑,执行完毕重新执行CAS获取锁.一定次数自旋内仍未获取锁,则该轻量级锁就升级为重量级锁.当前线程会真正挂起进入阻塞状态.
  1. 偏向锁
  • 轻量锁是在无多线程竞争情况下,通过CAS操作替换,避免OS操作Mutext Lock互斥量.
  • 偏向锁是在无多线程竞争情况下,同一个线程仅执行一次CAS操作,将后续冗余的CAS操作也避免.
  • 偏向锁的获取及释放及升级
    • 偏向锁第一次被线程A获取,A会执行1次CAS操作,将锁对象对象头中的记录的Thread ID保存为A的ID
    • A获取偏向锁成功后,执行同步代码块并退出,其持有的偏向锁并不释放
    • A下次再次执行该代码块可直接进入,无需再次CAS获取锁.因为之前就没有释放.
    • 若后续B要执行同步代码块,会执行CAS尝试获取锁
      • 若A此时已退出同步代码块,则B的CAS操作成功.对应Thread ID更新为B的ID
      • 若A此时未退出同步代码块,则B的CAS操作失败.该偏向锁升级为轻量级锁,B执行自旋尝试获取该轻量级锁(转换为轻量级锁的逻辑)
  1. 锁的升级: 偏向锁 -> 轻量级锁 -> 重量级锁
  2. 锁的一些优化
    • 6.1. 适应性自旋
      • JDK1.6之后引入了适应性自旋
      • 如果某个轻量级锁,之前线程A自旋X次获得了锁,且A正在运行中,则JVM认为当下B也可以通过自旋获得锁,允许B自旋X甚至更多次尝试获取锁.
      • 如果某个自旋锁很少通过自旋被获取,则后续获取该轻量级锁,会忽略自旋,直接将其升级为重量级锁,避免自旋空转消耗CPU.
    • 6.2. 锁消除
      • 编译期执行时,对一些检测到不可能存在多线程竞争的锁,执行移除,执行时是无锁的.
    • 6.3. 锁粗化
      • JVM检测到一连串零碎的操作都是对同一个对象加锁,则会将加锁的范围粗化到整个操作序列的外部.避免锁的重复获取/释放.
  3. 在JDK1.6之后,不必过于担心synchronize影响性能. 偏向锁 -> 轻量级锁 -> 重量级锁 .不是高并发情况下基本会以偏向锁的形态运行.性能很好
  4. 乐观锁 和 悲观锁 juejin.cn/post/684490… mp.weixin.qq.com/s/gSPgFtKZW… juejin.cn/post/706254… juejin.cn/post/684490… juejin.cn/post/684490… juejin.cn/post/684490… AtomicStampedReference: CAS+版本号机制. 避免ABA问题.
5. TCP和UDP