自定义View(8)View的绘制流程

安卓UI的重点之一就是View的绘制流程,经常出现在面试题中。熟悉View的绘制流程,不仅能轻松通过View相关的面试,也可以让我们更加方便的使用自定义View以及官方View。此篇先以常见面试题为切入点,说明自定义View的重要性,然后又以getMeasuredHeight值的获取作为问题点,带着问题从源码角度分析View的绘制流程。

1. 面试题介绍

1.1 Android 基础与底层机制

1. 数据库的操作类型有哪些,如何导入外部数据库?
2. 是否使用过本地广播,和全局广播有什么差别?
3. 是否使用过IntentService,作用是什么,AIDL解决了什么问题?(小米)
4. Activity、Window、View三者的差别,Fragment的特点?(360)
5. 描述一次网络请求的流程(新浪)
6. Handler、Thread和HandlerThread的差别(小米)
7. 低版本SDK实现高版本API(小米)
8. launch mode 应用场景(百度、小米、乐视)
9. touch 事件流程传递(小米)
> 10. view 绘制流程(百度)
11. 什么情况导致内存泄露(美团)
12. ANR定位和修正
13. 什么情况导致OOM (乐视、美团)
14. Android Service 与Activity 之间通信的几种方式
15. Android 各个版本API的区别
16. 如何保证一个后台服务不被杀死,比较省电的方式是什么?(百度)
17. RequestLayout、onLayout、onDraw 、DrawChild 区别与联系(猎豹)
18. Invalidate() 和 postInvalidate() 的区别及使用(百度)
19. Android 动画框架实现原理

2. 不同位置获取 getMeasuredHeight 的值

public class MainActivity extends AppCompatActivity {private TextView mTextView;private String TAG = "view8";@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mTextView = findViewById(R.id.text_view);Log.e(TAG, "onCreate: " + "height1 = " + mTextView.getMeasuredHeight());mTextView.post(new Runnable() {@Overridepublic void run() {Log.e(TAG, "onCreate: " + "height2 = " + mTextView.getMeasuredHeight());}});}@Overrideprotected void onResume() {super.onResume();Log.e(TAG, "onCreate: " + "height3 = " + mTextView.getMeasuredHeight());}
}

在这里插入图片描述
从上面代码和运行结果可知,在Activity onCreate 和 onResume 的时候都无法获取到 getMeasuredHeight 值,而使用 mTextView.post(new Runnable())方式可以获取到值,为何如此呢?

3. View 的绘制流程

3.1 View的添加流程 (是如何被添加到屏幕窗口上)

3.1.1 创建顶层布局容器DecorView

//View8/app/src/main/java/com/example/view8/MainActivity.java
// 这里主要是以默认继承的 AppCompatActivity 源码分析,如果是继承 Activity,
// 则直接进到PhoneWindow 的 setContentView,但基本流程都差不多
public class MainActivity extends AppCompatActivity {  protected void onCreate(Bundle savedInstanceState) {setContentView(R.layout.activity_main);   // onCreate中主要就是操作了这一行
-------->
//.gradle/caches/modules-2/files-2.1/androidx.appcompat/appcompat/1.6.1/ace9a78b961165396147e8691faa18c1b0e48e20/appcompat-1.6.1-sources.jar!/androidx/appcompat/app/AppCompatActivity.javapublic void setContentView(@LayoutRes int layoutResID) {getDelegate().setContentView(layoutResID);
-------->
//.gradle/caches/modules-2/files-2.1/androidx.appcompat/appcompat/1.6.1/ace9a78b961165396147e8691faa18c1b0e48e20/appcompat-1.6.1-sources.jar!/androidx/appcompat/app/AppCompatDelegate.javapublic abstract void setContentView(View v);
-------->
//.gradle/caches/modules-2/files-2.1/androidx.appcompat/appcompat/1.6.1/ace9a78b961165396147e8691faa18c1b0e48e20/appcompat-1.6.1-sources.jar!/androidx/appcompat/app/AppCompatDelegateImpl.javapublic void setContentView(View v) {ensureSubDecor();private void ensureSubDecor() {if (!mSubDecorInstalled) {mSubDecor = createSubDecor();private ViewGroup createSubDecor() {mWindow.getDecorView();  // 这里的mWindow就是PhoneWindow
-------->
//Android/Sdk/sources/android-33/com/android/internal/policy/PhoneWindow.javapublic final @NonNull View getDecorView() {if (mDecor == null || mForceDecorInstall) {installDecor();private void installDecor() {if (mDecor == null) {mDecor = generateDecor(-1);protected DecorView generateDecor(int featureId) {return new DecorView(context, featureId, this, getAttributes());   // 在这里new DecorView

3.1.2 在顶层布局中加载基础布局ViewGroup

//Android/Sdk/sources/android-33/com/android/internal/policy/PhoneWindow.javaprivate void installDecor() {if (mContentParent == null) {mContentParent = generateLayout(mDecor);protected ViewGroup generateLayout(DecorView decor) {int layoutResource;// 通过不同的条件(主题),对 layoutResource 进行初始化,然后传入 onResourcesLoaded// 假设 layoutResource 走了这个,如果走了其他的,布局中也会有FrameLayout,只是上面的东西不一样layoutResource = R.layout.screen_simple; mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);// ID_ANDROID_CONTENT = com.android.internal.R.id.content,也就是 layoutResource 中的 FrameLayout 布局ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);  return contentParent;  // 将 FrameLayout 布局 返回
-------->
//Android/Sdk/sources/android-33/com/android/internal/policy/DecorView.java void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {final View root = inflater.inflate(layoutResource, null);if (mDecorCaptionView != null) {mDecorCaptionView.addView(root,new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));} else {// 解析layoutResource,执行了addView,添加到 mDecor 里addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
-------->
<!-- Android/Sdk/platforms/android-33/data/res/layout/screen_simple.xml -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:fitsSystemWindows="true"android:orientation="vertical"><ViewStub android:id="@+id/action_mode_bar_stub"android:inflatedId="@+id/action_mode_bar"android:layout="@layout/action_mode_bar"android:layout_width="match_parent"android:layout_height="wrap_content"android:theme="?attr/actionBarTheme" /><FrameLayoutandroid:id="@android:id/content"android:layout_width="match_parent"android:layout_height="match_parent"android:foregroundInsidePadding="false"android:foregroundGravity="fill_horizontal|top"android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>

3.1.3 将ContentView添加到基础布局中的FrameLayout中

// 如果 MainActivity extends Activity 
//Android/Sdk/sources/android-33/com/android/internal/policy/PhoneWindow.javapublic void setContentView(int layoutResID) {// 将我们创建的 R.layout.activity_main 布局,放到 mContentParent 即系统创建的 FrameLayout 布局中mLayoutInflater.inflate(layoutResID, mContentParent); // 如果 MainActivity extends AppCompatActivity 
//.gradle/caches/modules-2/files-2.1/androidx.appcompat/appcompat/1.6.1/ace9a78b961165396147e8691faa18c1b0e48e20/appcompat-1.6.1-sources.jar!/androidx/appcompat/app/AppCompatDelegateImpl.javapublic void setContentView(int resId) {// 将我们创建的 R.layout.activity_main 布局,放到 mContentParent 即系统创建的 FrameLayout 布局中LayoutInflater.from(mContext).inflate(resId, contentParent);

PhoneWindow 是 Window 的唯一实现类,以上操作,只是把我们的布局 R.layout.activity_main加载到 DecorView中。此时还是什么都不会显示的。
在这里插入图片描述

3.2 View的绘制流程

上面已经走了一遍View的添加流程,即创建DecorView,然后将我们的布局R.layout.activity_main添加进去,但是还没有走View的测量,所以还是拿不到getMeasuredHeight值的。上节我们已经看了onCreate方法,即ActivityThread#performLaunchActivity,这次就不看了,直接从ActivityThread#handleResumeActivity开始。

//Android/Sdk/sources/android-33/android/app/ActivityThread.javapublic void handleRelaunchActivity(ActivityClientRecord tmp,PendingTransactionActions pendingActions) {handleRelaunchActivityInner(r, configChanges, tmp.pendingResults, tmp.pendingIntents,pendingActions, tmp.startsNotResumed, tmp.overrideConfig, "handleRelaunchActivity");private void handleRelaunchActivityInner(ActivityClientRecord r, int configChanges,List<ResultInfo> pendingResults, List<ReferrerIntent> pendingIntents,PendingTransactionActions pendingActions, boolean startsNotResumed,Configuration overrideConfig, String reason) {handleLaunchActivity(r, pendingActions, customIntent);public Activity handleLaunchActivity(ActivityClientRecord r,PendingTransactionActions pendingActions, Intent customIntent) {final Activity a = performLaunchActivity(r, customIntent);public void handleResumeActivity(ActivityClientRecord r, boolean finalStateRequest,boolean isForward, String reason) {// 这里的performResumeActivity就会回到 Activity 的 Resume 方法if (!performResumeActivity(r, finalStateRequest, reason)) {return;

此时,onCreate方法和onResume方法都已经调用了,但是因为没有调用onMeasure方法,所以还是拿不到getMeasuredHeight的值。

    public void handleResumeActivity(ActivityClientRecord r, boolean finalStateRequest,boolean isForward, String reason) {// 这里的performResumeActivity就会回到 Activity 的 Resume 方法if (!performResumeActivity(r, finalStateRequest, reason)) {return;if (r.window == null && !a.mFinished && willBeVisible) {ViewManager wm = a.getWindowManager();  // 这个 ViewManager 是一个接口,我们需要找这个的实现if (a.mVisibleFromClient) {if (!a.mWindowAdded) {wm.addView(decor, l);  // 这里的 wm 就是 ViewManager 的 getWindowManager
-------->
//Android/Sdk/sources/android-33/android/app/Activity.javapublic WindowManager getWindowManager() {return mWindowManager;  // 接着找这个 mWindowManager 在哪里实现}final void attach(Context context, ActivityThread aThread, ...) {mWindowManager = mWindow.getWindowManager();   // 这里是mWindow,我们知道Window的唯一实现类就是PhoneWindow
-------->
//Android/Sdk/sources/android-33/com/android/internal/policy/PhoneWindow.javafinal ViewManager wm = getWindowManager();  // 接着进入 getWindowManager
-------->
//Android/Sdk/sources/android-33/android/view/Window.javapublic WindowManager getWindowManager() {return mWindowManager;    // 发现这里又返回了 mWindowManager,我们继续找这个的实现public void setWindowManager(WindowManager wm, IBinder appToken, String appName,boolean hardwareAccelerated) {// 接着需要进入 WindowManagerImpl ,所以上面的wm 返回的就是 WindowManagerImpl ,接着在这个类里面找 addViewmWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);  
-------->
//Android/Sdk/sources/android-33/android/view/WindowManagerImpl.javapublic void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {applyTokens(params);mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,mContext.getUserId());  // 发现这里又调用了 mGlobal.addView}
-------->
//Android/Sdk/sources/android-33/android/view/WindowManagerGlobal.javapublic void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow, int userId) {ViewRootImpl root;  // 申明root// 通过 ViewRootImpl 的构造方法,将root实例化if (windowlessSession == null) {root = new ViewRootImpl(view.getContext(), display);} else {root = new ViewRootImpl(view.getContext(), display,windowlessSession);}view.setLayoutParams(wparams);mViews.add(view);mRoots.add(root);mParams.add(wparams);try {root.setView(view, wparams, panelParentView, userId);  // 将这几个参数关联起来
-------->
//Android/Sdk/sources/android-33/android/view/ViewRootImpl.javapublic void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,int userId) {synchronized (this) {if (mView == null) {requestLayout();

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/47863.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

LLM大模型实战项目--基于Stable Diffusion的电商平台虚拟试衣

本文详细讲解LLM大模型实战项目&#xff0c;基于Stable Diffusion的电商平台虚拟试衣 一、项目介绍 二、阿里PAI平台介绍 三、阿里云注册及开通PAI 四、PAI_DSW环境搭建 五、SDLORA模型微调 一、项目介绍 AI虚拟试衣是一种创新的技术&#xff0c;利用人工智能和计算机视觉技…

【2024最新华为OD-C/D卷试题汇总】[支持在线评测] 卢小姐的生日礼物(200分) - 三语言AC题解(Python/Java/Cpp)

🍭 大家好这里是清隆学长 ,一枚热爱算法的程序员 ✨ 本系列打算持续跟新华为OD-C/D卷的三语言AC题解 💻 ACM银牌🥈| 多次AK大厂笔试 | 编程一对一辅导 👏 感谢大家的订阅➕ 和 喜欢💗 最新华为OD机试D卷目录,全、新、准,题目覆盖率达 95% 以上,支持题目在线评测…

为什么现在的小家电换了Type-C接口后需要PD诱骗芯片

在当今科技飞速发展的时代&#xff0c;小家电产品正经历着前所未有的变革。随着消费者对于便捷性、高效性和安全性的要求不断提高&#xff0c;小家电产品的接口设计也逐渐向Type-C接口靠拢。然而&#xff0c;这一转变并非简单的接口替换&#xff0c;它背后隐藏着对PD诱骗芯片的…

【PG】PostgreSQL高可用之repmgr事件通知

目录 描述 结合脚本 占位符 repmgr命令 生成的事件&#xff1a; repmgrd 生成的事件&#xff08;流复制模式&#xff09;&#xff1a; 描述 每次repmgr或repmgrd执行重大事件时&#xff0c;都会将该事件的记录连同时间戳、失败或成功的标识以及进一步的详细信息&#xff08…

docker安装好了,但是启动失败

新项目要用docker部署,但是docker安装完后,启动失败,服务器用的是国产化的(之前的服务器非国产化,之前也没任何问题),国产化的使用起来问题一大堆,还是bclinux 安装好后重启一直显示 使用journalctl -xe也没任何报错 使用systemctl status docker查看docker状态是灰…

VScode:前端项目中yarn包的安装和使用

一、首先打开PowerShell-管理员身份运行ISE 输入命令&#xff1a; set-ExecutionPolicy RemoteSigned 选择“全是”&#xff0c;表示允许在本地计算机上运行由本地用户创建的脚本&#xff0c;没有报错就行了 二、接着打开VScode集成终端&#xff0c;安装yarn插件 输入 npm ins…

ELK日志分析系统部署文档

一、ELK说明 ELK是Elasticsearch&#xff08;ES&#xff09; Logstash Kibana 这三个开源工具组成&#xff0c;官方网站: The Elastic Search AI Platform — Drive real-time insights | Elastic 简单的ELK架构 ES: 是一个分布式、高扩展、高实时的搜索与数据分析引擎。它…

前端pc和小程序接入快递100(跳转方式和api方式)====实时查询接口

文章目录 跳转方式微信小程序&#xff08;我以uniapp为例&#xff09;pc api接入说明关于签名计算成功示例 跳转方式 没有任何开发成本&#xff0c;直接一键接入 可以直接看官方文档 https://www.kuaidi100.com/openapi/api_wxmp.shtml 微信小程序&#xff08;我以uniapp为例…

Python求均值,方差,标准差

参考链接&#xff1a;变异系数&#xff08;Coefficient of Variation,COV&#xff09;和协方差&#xff08;Covariance, Cov&#xff09;-CSDN博客 参考链接&#xff1a;pandas中std和numpy的np.std区别_numpy pandas std-CSDN博客 在计算蛋白质谱数据中的每个蛋白对应的变异…

C++内存管理(区别C语言)深度对比

欢迎来到我的Blog&#xff0c;点击关注哦&#x1f495; 前言 前面已经介绍了类和对象&#xff0c;对C面向对象编程已经有了全面认识&#xff0c;接下来要学习对语言学习比较重要的是对内存的管理。 一、内存的分区 代码区&#xff1a;存放程序的机器指令&#xff0c;通常是可…

从操作系统层面认识Linux

描述进程-PCB Linux操作系统下的PCB是: task_struct https://www.cnblogs.com/tongyan2/p/5544887.htmlhttps://www.cnblogs.com/tongyan2/p/5544887.html校招必背操作系统面试题-什么是 PCB&#xff08;进程控制块&#xff09; &#xff1f;_哔哩哔哩_bilibili校招必背操作系…

WSL-Ubuntu20.04环境使用YOLOv8 TensorRT推理加速

在阅读本章内容之前,需要把部署环境以及训练环境都安装好。 1.TensorRTX下载 这里使用Wang-xinyu大佬维护的TensorRTX库来对YOLOv8进行推理加速的演示,顺便也验证一下前面环境配置的成果。 github地址:GitHub - wang-xinyu/tensorrtx,下载后放到wsl的路径下,我这里放在/h…

transformer论文讲解

1.标题 作者 Transformer 开创了继 MLP 、CNN和 RN 之后的第四大类模型。200页综述&#xff08;来自评论区&#xff1a; https://arxiv.org/pdf/2108.07258.pdf &#xff09;建议将Transformer作为基础模型。 标题&#xff1a;XXX is all you need. 头条标题。 Attention i…

Docker部署内网穿透服务

前提 首先&#xff01;市面上的可下载的内网穿透是不是都非常的不好用&#xff0c;本地开发测试用起来都不方便。免费版本的各有限制。从无条件免费到后面维护的越来越复杂。无脑人&#xff08;我&#xff09;只需要下面这个。 一个是随机域名不定期会更换&#xff0c;一个是隧…

【数据结构】链表(LinkedList)详解

文章目录 [toc] 前言1. 链表的介绍1.1 链表的定义1.2 链表的结构种类 2. 单向链表的模拟实现2.1 创建链表2.2 打印链表2.3 求链表长度 3. 单向链表常见方法的模拟实现3.1 头插法3.2 尾插法3.3 指定位置插入3.4 查找值 key 的节点是否在链表中3.5 删除值为 key 的节点3.6 删除所…

栈(用C语言实现)

1. 栈 1.1 概念与结构 栈&#xff1a;⼀种特殊的线性表&#xff0c;其只允许在固定的⼀端进行插入和删除元素操作。进行数据插入和删除操作的⼀端称为栈顶&#xff0c;另⼀端称为栈底。栈中的数据元素遵守后进先出 LIFO&#xff08;Last In First Out&#xff09;的原则。 压…

Richteck立锜科技电源管理芯片简介及器件选择指南

一、电源管理简介 电源管理组件的选择和应用本身的电源输入和输出条件是高度关联的。 输入电源是交流或直流&#xff1f;需求的输出电压比输入电压高或是低&#xff1f;负载电流多大&#xff1f;系统是否对噪讯非常敏感&#xff1f;也许系统需要的是恒流而不是稳压 (例如 LED…

【产品那些事】固件安全-关于OTA升级包分析

文章目录 前言什么是OTA?升级包(固件)的类型和架构案例tp-link路由器升级包怎么解包分析?binwalk安装及使用ubi_reader安装及使用unsquashfs安装及使用某车企OTA升级包通用Android OTA解包相关分区第二层解包前言 什么是OTA? OTA(Over-the-Air)是一种通过无线通信网络(…

adb查看网卡信息,并修改网卡mac地址

这种方法修改mac后&#xff0c;关机后会失效! 文章结尾有永久修改mac地址的方法! 1. 查看网卡的信息&#xff0c;以及mac地址&#xff0c;ip地址&#xff0c;子网掩码等 //查看所有网卡信息adb shell ifconfig//MAC地址&#xff1a; HWaddr 5e:2c:e9:58:3e:4f //IP地址&a…

小试牛刀-Telebot区块链游戏机器人

目录 1.编写目的 2.实现功能 2.1 Wallet功能 2.2 游戏功能 2.3 提出功能 2.4 辅助功能 3.功能实现详解 3.1 wallet功能 3.2 游戏功能 3.3 提出功能 3.4 辅助功能 4.测试视频 Welcome to Code Blocks blog 本篇文章主要介绍了 [Telebot区块链游戏机器人] ❤博主…