1.Android ViewRootImpl
2.找到卡顿来源,源码BlockCanary源码精简分析
3.Android UIç»å¶ä¹Viewç»å¶çå·¥ä½åç
4.子线程更新UI全解
5.Android 14 HWUI 源码研究 View Canvas RenderThread ViewRootImpl skia
6.View 绘制流程源码分析
Android ViewRootImpl
æ¬æ主è¦åæ两个é®é¢ï¼
1ã为ä»ä¹View çç»å¶æµç¨æ¯ä» ViewRootImpl çperformTraversals()æ¹æ³å¼å§çï¼
2ãView çinvalidateæ¹æ³æ¯æä¹è§¦åå°ViewRootImpl çperformTraversals()æ¹æ³çã
å¨é 读æ¬æåï¼æ好å äºè§£windowçæ·»å è¿ç¨ï¼Androidæ¶æ¯å¤çæºå¶ å View çç»å¶æµç¨ãæ¨èå é 读以ä¸æç« ï¼
Android WindowåWindowManager
Android-æ¶æ¯æºå¶
Android View çç»å¶æµç¨
android æºç 注éçæææ¯ï¼ViewRootImplæ¯è§å¾å±æ¬¡ç»æç顶é¨ï¼å®ç° View å WindowManager ä¹é´æéçåè®®ãæ¯ WindowManager Global çå é¨å®ç°ä¸éè¦çç»æé¨åã
View çç»å¶æµç¨æ¯ä» ViewRootImpl çperformTraversals()æ¹æ³å¼å§çï¼é£å°åºæ¯åªéè°ç¨äºperformTraversals()æ¹æ³å¢ï¼ä¸é¢æ们åæä¸ä¸ï¼
1.ç§æå±æ§çperformTraversals()æ¹æ³è¯å®æ¯å¨å é¨è°ç¨èµ·æ¥çï¼ç»è¿æç´¢æ¾å°æ¯doTraversal()æ¹æ³è°ç¨äºã
2.æ¥çæ¾å°äºï¼è°ç¨äºdoTraversal() çTraversalRunnable ç±»
3.å é¨åªæä¸ä¸ªå°æ¹å®ä¾åäºTraversalRunnable çå®ä¾mTraversalRunnable ï¼æ¥å°å°ä¸¤ä¸ªæ¹æ³å é½è°ç¨äºmTraversalRunnable ï¼ææ¾ scheduleTraversals æ¯ä¸»å¨è§¦åè¿ä¸ª Runnable ãè¿å°±è¡¨æè°ç¨äºscheduleTraversals ()å½æ°çå°æ¹é½ä¸»å¨è§¦åäºviewçå·æ°ã
4.æ¥çæ们çä¸ä¸ mChoreographer.postCallback åäºä»ä¹
å¯ä»¥çå°ï¼æååèµ°è¿äºscheduleVsyncLocked()æ¹æ³å ã
5.mDisplayEventReceiver çç±» æ¯ FrameDisplayEventReceiverï¼ç»§æ¿èª
DisplayEventReceiver ã
æåèµ°å°è¿é就没äºï¼é£ä¹è¿ä¸ªæ¹æ³æ¯åäºä»ä¹å¢ï¼è¿ä¸ªæ¹æ³ç注éæ¯è¿ä¸ªææï¼å®æå¨ä¸ä¸ä¸ªæ¾ç¤ºå¸§å¼å§æ¶ä¼ éå个åç´åæ¥èå²ãææå°±æ¯ï¼è°ç¨äºè¿ä¸ªæ¹æ³å¯ä»¥æ¶å°ç³»ç»ä¼ éè¿æ¥çåç´åæ¥èå²ä¿¡å·ãAndroidç³»ç»æ¯émså°±ä¼åéä¸ä¸ªVSYNCä¿¡å·ï¼VSYNCï¼vertical synchronization åç´åæ¥ï¼å¸§åæ¥ï¼ï¼è§¦å对UIè¿è¡æ¸²æãè¿ä¸ªåç´åæ¥ä¿¡å¯¹äºåºç¨æ¥è¯´äºï¼åªæäºè®¢é äºçå¬ï¼æè½æ¶å°ãèä¸æ¯è®¢é ä¸æ¬¡ï¼æ¶å°ä¸æ¬¡ã
6.æ¢ç¶æ¯å¨è¿ä¸ªç±»éé¢è®¢é åç´åæ¥ä¿¡å·çï¼é£åè°ä¹åºè¯¥å¨è¿éãäºæ¯æ¾å°äºä»¥ä¸æ¹æ³ã
native code è°ç¨å° onVsyncï¼è¿ä¸ªæ¹æ³ç注é解éå¦ä¸ï¼å½æ¥æ¶å°åç´åæ¥èå²æ¶è°ç¨ãæ¥æ¶è åºè¯¥æ¸²æä¸ä¸ªå¸§ï¼ç¶åè°ç¨ { @link scheduleVsync} æ¥å®æä¸ä¸ä¸ªåç´åæ¥èå²ã
è¿ä¸ªæ¹æ³çå ·ä½å®ç°å¨åé¢åæå°çFrameDisplayEventReceiver ç±»éé¢ã
è¿éå¯ä»¥çå°ï¼å ¶å®mHandlerå°±æ¯å½å主线ç¨çhandlerï¼å½æ¥æ¶å°onVsyncä¿¡å·çæ¶åï¼å°èªå·±å°è£ å°Messageä¸ï¼çå°Looperå¤çï¼æåLooperå¤çæ¶æ¯çæ¶åå°±ä¼è°ç¨runæ¹æ³ï¼è¿éæ¯Handlerçæºå¶ï¼ä¸å解éã
7.æåï¼å¦ä¸å¾è°ç¨æ示ï¼æç»ä»mCallbackQueuesååä¹åæ·»å çä»»å¡åæ§è¡runæ¹æ³ï¼ä¹å°±æ¯TraservalRunnableçrunæ¹æ³ã
æ»ç»ä¸é¢çåæï¼è°ç¨æµç¨å¦ä¸å¾æ示å¦ä¸ï¼
ä¸é¢æ们åæå°åªè¦è°ç¨äºViewRootImpl çscheduleTraversals ()æ¹æ³ï¼æç»å°±è½è°ç¨äºViewRootImpl çperformTraversals()æ¥å¼å§ç»å¶ãé£è¯å®æ¯æ们常è°ç¨çviewå·æ°çæ¥å£ï¼ç»è¿ä¸ç³»åçæ¹æ³è°ç¨ï¼æç»è°ç¨äºViewRootImpl çscheduleTraversals ()æ¹æ³ãä¸é¢æ们åæä¸ä¸å¸¸ç¨çView ç invalidate()æ¥å£æ¯æä¹è°ç¨å°äºViewRootImpl çscheduleTraversals ()æ¹æ³ã
å¯ä»¥çåºï¼invalidateæå¤ä¸ªéè½½æ¹æ³ï¼ä½æç»é½ä¼è°ç¨invalidateInternalæ¹æ³ï¼å¨è¿ä¸ªæ¹æ³å é¨ï¼è¿è¡äºä¸ç³»åçå¤æï¼å¤æViewæ¯å¦éè¦éç»ï¼æ¥ç为该View设置æ è®°ä½ï¼ç¶åæéè¦éç»çåºåä¼ éç»ç¶å®¹å¨ï¼å³è°ç¨ç¶å®¹å¨çinvalidateChildæ¹æ³ã
æ¥çæ们çViewGroup#invalidateChildï¼
ç±äºä¸æåä¸è°ç¨ç¶å®¹å¨çæ¹æ³ï¼å°æåä¼è°ç¨å°ViewRootImplçinvalidateChildInParentæ¹æ³ï¼æ们æ¥ççå®çæºç ï¼ViewRootImpl#invalidateChildInParent:
æåè°ç¨äºscheduleTraversalsæ¹æ³ï¼è§¦åViewçå·¥ä½æµç¨ãè³æ¤ï¼æ们已ç»å®æ´å°åæäºä¸æ¬¡è°ç¨View ç invalidate()æ¹æ³å°è§¦åViewRootImpl çscheduleTraversals()æ¹æ³ã
找到卡顿来源,源码BlockCanary源码精简分析
通过屏幕渲染机制我们了解到,源码Android的源码屏幕渲染是通过vsync实现的。软件层将数据计算好后,源码放入缓冲区,源码出c源码硬件层从缓冲区读取数据绘制到屏幕上,源码渲染周期是源码ms,这让我们看到不断变化的源码画面。如果计算时间超过ms,源码就会出现卡顿现象,源码这通常发生在软件层,源码而不是源码硬件层。卡顿发生的源码原因在于软件层的计算时间需要小于ms,而计算的源码执行地点则在Handler中,具体来说是在UI的Handler中。Android进程间的交互通过Binder实现,线程间通信通过Handler。
软件层在收到硬件层的vsync信号后,会在Java层向UI的Handler中投递一个消息,进行view数据的计算。这涉及到测量、布局和绘制,通常在`ViewRootImpl`的`performTraversals()`函数中实现。因此,view数据计算在UI的爱心弹幕源码Handler中执行,如果有其他操作在此执行且耗时过长,则可能导致卡顿,我们需要找到并优化这些操作。
要找到卡顿的原因,可以通过在消息处理前后记录时间,计算时间差,将这个差值与预设的卡顿阈值比较。如果大于阈值,表示发生了卡顿,此时可以dump主线程堆栈并显示给开发者。实现这一功能的关键在于在Looper中设置日志打印类。通过`Looper.loop()`函数中的日志打印,我们可以插入自定义的Printer,并在消息执行前后计算时间差。另一种方法是在日志中添加前缀和后缀,根据这些标志判断时间点。
BlockCanary是一个用于检测Android应用卡顿的工具,通过源码分析,我们可以了解到它的实现逻辑。要使用BlockCanary,首先需要定义一个继承`BlockCanaryContext`的类,并重写其中的关键方法。在应用的`onCreate()`方法中调用BlockCanary的安装方法即可。当卡顿发生时,BlockCanary会通知开发者,偷游戏源码并在日志中显示卡顿信息。
BlockCanary的核心逻辑包括安装、事件监控、堆栈和CPU信息的采集等。在事件发生时,会创建LooperMonitor,同时启动堆栈采样和CPU采样。当消息将要执行时,开始记录开始时间,执行完毕后停止记录,并计算执行时间。如果时间差超过预设阈值,表示发生了卡顿,并通过回调传递卡顿信息给开发者。
堆栈和CPU信息的获取通过`AbstractSampler`类实现,它通过`post`一个`Runnable`来触发采样过程,循环调用`doSample()`函数。StackSampler和CpuSampler分别负责堆栈和CPU信息的采集,核心逻辑包括获取当前线程的堆栈信息和CPU速率,并将其保存。获取堆栈信息时,通过在`StackSampler`类中查找指定时间范围内的堆栈信息;获取CPU信息时,从`CpuSampler`类中解析`/proc/stat`和`/proc/mpid/stat`文件的CPU数据,并保存。
总结而言,最新unigui源码BlockCanary通过在消息处理前后记录时间差,检测卡顿情况,并通过堆栈和CPU信息提供详细的卡顿分析,帮助开发者定位和优化性能问题。
Android UIç»å¶ä¹Viewç»å¶çå·¥ä½åç
è¿æ¯AndroidUIç»å¶æµç¨åæç第äºç¯æç« ï¼ä¸»è¦åæçé¢ä¸Viewæ¯å¦ä½ç»å¶å°çé¢ä¸çå ·ä½è¿ç¨ãViewRoot 对åºäº ViewRootImpl ç±»ï¼å®æ¯è¿æ¥ WindowManager å DecorView ç纽带ï¼Viewçä¸å¤§æµç¨åæ¯éè¿ ViewRoot æ¥å®æçãå¨ ActivityThread ä¸ï¼å½ Activity 对象被å建å®æ¯åï¼ä¼å° DecorView æ·»å å° Window ä¸,åæ¶ä¼å建 ViewRootImpl 对象ï¼å¹¶å° ViewRootImpl 对象å DecorView 建ç«å ³èã
measure è¿ç¨å³å®äº View ç宽/é«ï¼ Measure å®æ以åï¼å¯ä»¥éè¿ getMeasuredWidth å getMeasuredHeight æ¹æ³æ¥è·å View æµéåç宽/é«ï¼å¨å ä¹ææçæ åµä¸ï¼å®çåäºViewçæç»ç宽/é«ï¼ä½æ¯ç¹æ®æ åµé¤å¤ã Layout è¿ç¨å³å®äº View çå个顶ç¹çåæ åå®é ç宽/é«ï¼å®æ以åï¼å¯ä»¥éè¿ getTopãgetBottomãgetLeft å getRight æ¥æ¿å°Viewçå个顶ç¹çä½ç½®ï¼å¯ä»¥éè¿ getWidth å getHeight æ¹æ³æ¿å°Viewçæç»å®½/é«ã Draw è¿ç¨å³å®äº View çæ¾ç¤ºï¼åªæ draw æ¹æ³å®æå View çå 容æè½åç°å¨å±å¹ä¸ã
DecorView ä½ä¸ºé¡¶çº§ View ï¼ä¸è¬æ åµä¸ï¼å®å é¨ä¼å å«ä¸ä¸ªç«ç´æ¹åç LinearLayout ï¼å¨è¿ä¸ª LinearLayout éé¢æä¸ä¸ä¸¤ä¸ªé¨åï¼ä¸é¢æ¯æ é¢æ ï¼ä¸é¢æ¯å 容æ ãå¨Activityä¸ï¼æ们éè¿ setContentView æ设置çå¸å±æä»¶å ¶å®å°±æ¯è¢«å å°å 容æ ä¸çï¼èå 容æ id为 content ãå¯ä»¥éè¿ä¸é¢æ¹æ³å¾å° content:ViewGroup content = findViewById(R.android.id.content) ãéè¿ content.getChildAt(0) å¯ä»¥å¾å°è®¾ç½®ç view ã DecorView å ¶å®æ¯ä¸ä¸ª FrameLayout , View å±çäºä»¶é½å ç»è¿ DecorView ï¼ç¶åæä¼ éç»æ们ç View ã
MeasureSpec 代表ä¸ä¸ªä½çintå¼ï¼é«2ä½ä»£è¡¨ SpecMode ,ä½ä½ä»£è¡¨ SpecSize , SpecMode æ¯ææµé模å¼ï¼è SpecSize æ¯æå¨æç§æµé模å¼ä¸çè§æ ¼å¤§å°ã
SpecMode æä¸ç±»ï¼å¦ä¸æ示ï¼
UNSPECIFIED
EXACTLY
AT_MOST
LayoutParamséè¦åç¶å®¹å¨ä¸èµ·æè½å³å®ViewçMeasureSpecï¼ä»èè¿ä¸æ¥å³å®Viewç宽/é«ã
对äºé¡¶çº§Viewï¼å³DecorViewåæ®éViewæ¥è¯´ï¼MeasureSpecç转æ¢è¿ç¨ç¥æä¸åã对äºDecorViewï¼å ¶MeasureSpecç±çªå£ç尺寸åå ¶èªèº«çLayoutParamså ±åç¡®å®ï¼
对äºæ®éViewï¼å ¶MeasureSpecç±ç¶å®¹å¨çMeasureSpecåèªèº«çLayoutparamså ±åå³å®ï¼
MeasureSpecä¸æ¦ç¡®å®ï¼onMeasureå°±å¯ä»¥ç¡®å®Viewçæµé宽/é«ã
å°ç»ä¸ä¸
å½å View ç宽é«éç¨ wrap_content æ¶ï¼ä¸ç®¡ç¶å®¹å¨ç模å¼æ¯ç²¾ç¡®æ¨¡å¼è¿æ¯æ大模å¼ï¼å View ç模å¼æ»æ¯æ大模å¼+ç¶å®¹å¨çå©ä½ç©ºé´ã
View çå·¥ä½æµç¨ä¸»è¦æ¯æ measure ã layout ã draw ä¸å¤§æµç¨ï¼å³æµéãå¸å±ãç»å¶ãå ¶ä¸ measure ç¡®å® View çæµé宽/é«ï¼ layout ç¡®å® view çæç»å®½/é«åå个顶ç¹çä½ç½®ï¼è draw åå° View ç»å¶å¨å±å¹ä¸ã
measure è¿ç¨è¦åæ åµï¼å¦æåªæ¯ä¸ä¸ªåå§ç view ï¼åéè¿ measure æ¹æ³å°±å®æäºå ¶æµéè¿ç¨ï¼å¦ææ¯ä¸ä¸ª ViewGroup ï¼é¤äºå®æèªå·±çæµéè¿ç¨å¤ï¼è¿ä¼éåè°ç¨ææåå ç´ ç measure æ¹æ³ï¼å个åå ç´ åéå½å»æ§è¡è¿ä¸ªæµç¨ã
å¦ææ¯ä¸ä¸ªåå§ç Viewï¼é£ä¹éè¿ measure æ¹æ³å°±å®æäºæµéè¿ç¨ï¼å¨ measure æ¹æ³ä¸ä¼å»è°ç¨ View ç onMeasure æ¹æ³ï¼View ç±»éé¢å®ä¹äº onMeasure æ¹æ³çé»è®¤å®ç°:
å çä¸ä¸ getSuggestedMinimumWidth å getSuggestedMinimumHeight æ¹æ³çæºç ï¼
å¯ä»¥çå°ï¼ getMinimumWidth æ¹æ³è·åçæ¯ Drawable çåå§å®½åº¦ãå¦æåå¨åå§å®½åº¦ï¼å³æ»¡è¶³ intrinsicWidth > 0ï¼ï¼é£ä¹ç´æ¥è¿ååå§å®½åº¦å³å¯ï¼å¦æä¸åå¨åå§å®½åº¦ï¼å³ä¸æ»¡è¶³ intrinsicWidth > 0ï¼ï¼é£ä¹å°±è¿å 0ã
æ¥ççæéè¦ç getDefaultSize æ¹æ³ï¼
å¦æ specMode 为 MeasureSpec.UNSPECIFIED å³æªæå®æ¨¡å¼ï¼é£ä¹è¿åç±æ¹æ³åæ°ä¼ éè¿æ¥ç尺寸ä½ä¸º View çæµé宽度åé«åº¦ï¼
å¦æ specMode ä¸æ¯ MeasureSpec.UNSPECIFIED å³æ¯æ大模å¼æè 精确模å¼ï¼é£ä¹è¿åä» measureSpec ä¸ååºç specSize ä½ä¸º View æµéåç宽度åé«åº¦ã
çä¸ä¸åæçè¡¨æ ¼ï¼
å½ specMode 为 EXACTLY æè AT_MOST æ¶ï¼View çå¸å±åæ°ä¸º wrap_content æè match_parent æ¶ï¼ç» View ç specSize é½æ¯ parentSize ãè¿ä¼æ¯å»ºè®®çæå°å®½é«è¦å¤§ãè¿æ¯ä¸ç¬¦åæ们çé¢æçãå 为æä»¬ç» View 设置 wrap_content æ¯å¸æViewç大å°å好å¯ä»¥å 裹å®çå 容ã
å æ¤ï¼
å¦ææ¯ä¸ä¸ª ViewGroupï¼é¤äºå®æèªå·±ç measure è¿ç¨ä»¥å¤ï¼è¿ä¼éåå»è°ç¨ææåå ç´ ç measure æ¹æ³ï¼å个åå ç´ åéå½å»æ§è¡ measure è¿ç¨ã
ViewGroup 并没æéå View ç onMeasure æ¹æ³ï¼ä½æ¯å®æä¾äº measureChildrenãmeasureChildãmeasureChildWithMargins è¿å 个æ¹æ³ä¸é¨ç¨äºæµéåå ç´ ã
å¦ææ¯ View çè¯ï¼é£ä¹å¨å®ç layout æ¹æ³ä¸å°±ç¡®å®äºèªèº«çä½ç½®ï¼å ·ä½æ¥è¯´æ¯éè¿ setFrame æ¹æ³æ¥è®¾å® View çå个顶ç¹çä½ç½®ï¼å³åå§å mLeft ï¼ mRight ï¼ mTop ï¼ mBottom è¿å个å¼ï¼ï¼ layout è¿ç¨å°±ç»æäºã
å¦ææ¯ ViewGroup çè¯ï¼é£ä¹å¨å®ç layout æ¹æ³ä¸åªæ¯ç¡®å®äº ViewGroup èªèº«çä½ç½®ï¼è¦ç¡®å®åå ç´ çä½ç½®ï¼å°±éè¦éå onLayout æ¹æ³ï¼å¨ onLayout æ¹æ³ä¸ï¼ä¼è°ç¨åå ç´ ç layout æ¹æ³ï¼åå ç´ å¨å®ç layout æ¹æ³ä¸ç¡®å®èªå·±çä½ç½®ï¼è¿æ ·ä¸å±ä¸å±å°ä¼ éä¸å»å®ææ´ä¸ª View æ ç layout è¿ç¨ã
layout æ¹æ³çä½ç¨æ¯ç¡®å® View æ¬èº«çä½ç½®ï¼å³è®¾å® View çå个顶ç¹çä½ç½®ï¼è¿æ ·å°±ç¡®å®äº View å¨ç¶å®¹å¨ä¸çä½ç½®ï¼
onLayout æ¹æ³çä½ç¨æ¯ç¶å®¹å¨ç¡®å®åå ç´ çä½ç½®ï¼è¿ä¸ªæ¹æ³å¨ View ä¸æ¯ç©ºå®ç°ï¼å 为 View 没æåå ç´ äºï¼å¨ ViewGroup ä¸åè¿è¡æ½è±¡åï¼å®çåç±»å¿ é¡»å®ç°è¿ä¸ªæ¹æ³ã
1.ç»å¶èæ¯ï¼ background.draw(canvas); ï¼ï¼
2.ç»å¶èªå·±ï¼ onDraw ï¼ï¼
3.ç»å¶ childrenï¼ dispatchDraw(canvas) ï¼ï¼
4.ç»å¶è£ é¥°ï¼ onDrawScrollBars ï¼ã
dispatchDraw æ¹æ³çè°ç¨æ¯å¨ onDraw æ¹æ³ä¹åï¼ä¹å°±æ¯è¯´ï¼æ»æ¯å ç»å¶èªå·±åç»å¶å View ã
å¯¹äº View ç±»æ¥è¯´ï¼ dispatchDraw æ¹æ³æ¯ç©ºå®ç°çï¼å¯¹äº ViewGroup ç±»æ¥è¯´ï¼ dispatchDraw æ¹æ³æ¯æå ·ä½å®ç°çã
éè¿ dispatchDraw æ¥ä¼ éçã dispatchDraw ä¼éåè°ç¨åå ç´ ç draw æ¹æ³ï¼å¦æ¤ draw äºä»¶å°±ä¸å±ä¸å±ä¼ éäºä¸å»ãdispatchDraw å¨ View ç±»ä¸æ¯ç©ºå®ç°çï¼å¨ ViewGroup ç±»ä¸æ¯çæ£å®ç°çã
å¦æä¸ä¸ª View ä¸éè¦ç»å¶ä»»ä½å 容ï¼é£ä¹å°±è®¾ç½®è¿ä¸ªæ 记为 trueï¼ç³»ç»ä¼è¿è¡è¿ä¸æ¥çä¼åã
å½å建çèªå®ä¹æ§ä»¶ç»§æ¿äº ViewGroup 并ä¸ä¸å ·å¤ç»å¶åè½æ¶ï¼å°±å¯ä»¥å¼å¯è¿ä¸ªæ è®°ï¼ä¾¿äºç³»ç»è¿è¡åç»çä¼åï¼å½æç¡®ç¥éä¸ä¸ª ViewGroup éè¦éè¿ onDraw ç»å¶å 容æ¶ï¼éè¦å ³éè¿ä¸ªæ è®°ã
åèï¼ãAndroidå¼åèºæ¯æ¢ç´¢ã
子线程更新UI全解
子线程更新 UI 的问题在 Android 开发中至关重要。通常,尝试在子线程更新 UI 会导致应用崩溃,这是因为 Android 设计了一条铁律:禁止子线程直接操作 UI。原因在于,屏幕刷新率至少是每 ms 一次,要求 UI 更新快速响应以避免卡顿。如果在子线程中操作 UI,可能导致线程不安全,进而产生不可预知的 UI 结果。
深入理解这一限制,我们从源码层面分析。Android 版本的框架中,错误通常从 View#setBackgroundColor() 开始,层层传递至 ViewRootImpl#checkThread(),这里会检查线程是否与预期一致,如果不一致则抛出异常。这个检查机制确保 UI 更新在主线程中进行,避免了多线程对 UI 的并发修改。
源码追踪显示,从 imageView.setBackgroundColor() 开始,let源码教程调用链会到 View#requestLayout(),接着递归到 Activity 的顶层 View,通过 setContentView() 和 PhoneWindow 的设置,最终到达 DecorView,这是整个 View 树的根节点。DecorView 作为 ViewParent 的概念在此时显得重要,因为它虽然没有直接父 View,但仍有 ViewParent,即 ViewRootImpl。
ViewRootImpl 的 requestLayout() 方法再次执行线程检查,确保它是在主线程初始化的。如果试图在子线程更新 View,除非满足特定条件,如在 Activity 的 onResume() 之前更新,或者在非硬件加速且更新的是特定 View 的某些方法时,否则会触发异常。
总结来说,尽管存在绕过检查的可能,但遵循 Android 设计原则,最好避免在子线程更新 UI。因为这种做法可能带来不可预测的行为,尤其是在定制化系统中。了解了这些底层机制后,开发者应确保 UI 更新始终在主线程进行,以保证应用的稳定性和用户体验。
Android HWUI 源码研究 View Canvas RenderThread ViewRootImpl skia
HUWUI是Android系统中负责应用可视化元素绘制的核心组件,其架构主要在C++层实现,从Java层接收View绘制信息,通过唯一的渲染线程使用skia技术完成渲染任务。整体上,从应用程序到UI线程,再到渲染线程,形成了清晰的层级关系。
HUWUI的构建主要包括三个核心类,它们分别是:RecordingCanvas、Canvas、RenderNode、RenderProxy、RenderThread、CanvasContext、IRenderPipeline。在Java层,主要涉及两类Canvas,RecordingCanvas用于记录绘制指令,Canvas则是直接用于渲染。RecordingCanvas在构造时创建,而Canvas在调用时创建。这两个类在C++层分别对应SkiaRecordingCanvas和SkiaCanvas,后者直接引用SkCanvas。
在全局循环中,UI线程与渲染线程之间的协同操作至关重要。具体流程包括:新创建Activity后,附着到对应的PhoneWindow,然后调用PhoneWindow的setContentView方法,将View添加到DecorView作为子节点。接着,DecorView与ViewRootImpl对接,完成View的更新与渲染。整个过程包含了measure、layout和draw等复杂子流程。
渲染线程创建与核心对象紧密关联,主要包括RenderProxy、RenderThread和DrawFrameTask。RenderProxy负责Java层信息的衔接,RenderThread作为进程唯一的渲染线程,持有DrawFrameTask和CanvasContext,完成一帧的绘制任务。指令记录流程的核心在于使用C++层的RecordingCanvas将View属性和绘制信息记录到DisplayList中,进而完成指令的渲染。
Surface、ANativeWindow、EGLSurface的创建流程在ViewRootImpl的performTraversals函数中初始化。ReliableSurface的封装和EGL与Skia环境的创建主要在RenderThread的requireGlContext函数中实现。从源码分析,这一过程通常在三个地方调用。
View树与RenderNode树之间的协作关系明确,一个Application进程对应多个Activity,每个Activity与一个PhoneWindow绑定,PhoneWindow持有DecorView,DecorView对应一个ViewRootImpl,而ViewRootImpl与ThreadedRender模块对接。ThreadedRender与C++层的RenderProxy一一对应,RenderProxy持有关键对象,如RenderThread、CanvasContext、DrawFrameTask等。RenderThread是单例模式,进程唯一,负责一帧绘制的逻辑。
在RenderPipeline模块中,关键操作包括makeCurrent、draw和swapBuffers。Native Canvas在这一过程中扮演了桥梁角色,接收Java API调用,而RecordingCanvas完成Op记录,最终DisplayListData存储这些Op。
skia的核心资源主要在三个使用场景中发挥作用,具体细节需深入分析,这些资源对于实现高效、稳定的渲染效果至关重要。
View 绘制流程源码分析
在View的绘制流程中,ViewRootImpl的setView主流程涉及的关键步骤包括设置PFLAG_FORCE_LAYOUT和PFLAG_INVALIDATED。这一步骤在执行时,触发了View的重绘逻辑。
接下来,当View收到需要重绘的信号后,会执行invalidate方法。这个方法首先计算出需要重绘的dirty区域,然后从下向上,最终调用到ViewRootImpl的scheduleTraversals方法。这个过程中,脏区域的范围逐步扩大,直至整个View需要进行重绘。
在View的绘制流程中,PFLAG_FORCE_LAYOUT和PFLAG_INVALIDATED的使用至关重要。它们的设置触发了视图的重绘和布局过程,保证了UI在用户操作或其他事件触发时能够及时响应和更新。通过这种方式,系统确保了用户界面的实时性和交互性。
具体来说,当View收到布局或尺寸变化的信号时,会调用requestLayout方法,同时设置PFLAG_FORCE_LAYOUT标志。这个标志告诉系统,当前布局需要强制执行,即使布局尚未完成,也应立即进行更新。同时,invalidate方法的调用,会触发PFLAG_INVALIDATED标志的设置,表明视图需要重绘。
在ViewRootImpl中,scheduleTraversals方法是负责组织和执行视图层级中所有视图的重绘和布局的。它会根据脏区域和布局标志的设置,合理安排视图的更新顺序,确保系统的性能和用户体验。
总结整个流程,View的绘制和布局机制通过一系列的标志(如PFLAG_FORCE_LAYOUT和PFLAG_INVALIDATED)和方法(如requestLayout和invalidate)来协调和控制。这些机制使得系统能够高效地响应用户操作,实现流畅的UI交互。通过深入理解这些源码细节,开发者能够更好地优化UI性能,提高用户体验。