深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上鸿蒙开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
}
getDelegate
返回的是 AppCompatDelegateImpl
// AppCompatDelegateImpl
@Override
public void setContentView(int resId) {
// 1. 实例化 DecorView
ensureSubDecor();
// 2. R.id.content 就是承载我们自定义 View 的容器
ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
// 3. 将我们自定义 View 添加到 R.id.content 中
LayoutInflater.from(mContext).inflate(resId, contentParent);
mAppCompatWindowCallback.getWrapped().onContentChanged();
}
逻辑比较清晰,先实例化一个 DecorView,然后将我们自定义 View 添加到 DecorView 中,那这个 DecorView 是什么呢
DecorView
DecorView 是一个继承自 FrameLayout 的 View,里面一般包含一个标题栏和一个内容区,其中内容区的 id 是 android.R.id.content,这里附一张 示意图
我们这里可以简单理解为:DecorView 就是整个 Activity 的 Root View,用来容纳我们设置的自定义 View 布局;
所以执行完 setContentView
之后,一个以 DecorView 为 Root 的 View 树就创建好了,但是清注意,这个时候仅仅是构建好一个 View 树,View 还没有真正开始进入绘制流程,此时 View 不可见,也拿不到宽高。
我们可以通过 View.post {} 来拿到宽高,这是为什么呢?感兴趣可移步到 Android 消息机制
requstLayout()
回顾之前分析 handleResumeActivity
源码,我们知道,这里面先调用了 onResume()
,然后最终调用到 ViewRootImpl.setView
,在 ViewRootImpl.setView
里面调用了 requestLayout
开启 View 绘制流程。
首先看看这个 ViewRootImpl
是什么
ViewRootImpl
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks,
AttachedSurfaceControl { }
首先从定义上看,ViewRootImpl 不是 View,而是一个实现了 ViewParent 接口的对象。它的构造方法中有两个成员需要重点关注下
public ViewRootImpl(@UiContext Context context, Display display, IWindowSession session,
boolean useSfChoreographer) {
// 保存当前线程,对于这里分析的启动 Activity 的场景,这里是主线程
mThread = Thread.currentThread();
// Choreographer 是屏幕刷新机制的关键,后续会单独讲
mChoreographer = useSfChoreographer
? Choreographer.getSfInstance() : Choreographer.getInstance();
}
这里对 ViewRootImpl 的介绍先不再深入,我们留个印象即可,我们重点看下 requestLayout
做了什么事情
// ViewRootImpl
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
“Only the original thread that created a view hierarchy can touch its views.”);
}
}
这里主要做了两件事情:检查线程、调用 scheduleTraversals()
checkThread
mThread
就是实例化 ViewRootImpl
的时候的线程,我们前面提到 AMS 通过 Binder 调用最终向 ActivityThread 主线程发消息来执行 Activity 生命周期,所以 mThread 就是主线程;requestLayout
调用的地方也是主线程触发的,所以这里检测一定是通过的。不过如果我们在代码中手动调用 View.requestLayout
,最终也会走到这里,此时的当前线程就是调用线程,所以这也解释了为什么我们不能在非主线程更新 UI。
一定不能在非主线程更新 UI 吗?这里留个疑问,后面会解答。
scheduleTraversals
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
// 1. 发送一个同步屏障
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// 2. 向 Choreographer post 一个 callback
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
final class TraversalRunnable implements Runnable {
@Override
public void run() {
// 3. 执行 doTraversal
doTraversal();
}
}
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
// 4. 移除同步屏障
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing(“ViewAncestor”);
}
// 5. onMeasure、onLayout、onDraw
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
- 关于消息同步屏障,详细介绍可以移步到 Android 消息机制 ,这里简单理解一下其目的就是为了保证渲染任务得到高优执行。
- Choreographer 是屏幕刷新机制的关键实现,详细介绍可以移步到 屏幕刷新机制 ,这里也简单了解一下:往 Choreographer 里面 postCallback 目的是为了在系统下一帧渲染时间到来的时候执行 Runnable 中的逻辑,对应上面也就是执行
performTraversals()
下面重点介绍下 performTraversals()
这个方法内部实现特别复杂,代码很长,这里就不贴源码了,最关键的三行代码就是 performMeasure()
、performLayout()
、performDraw()
,而这三个方法内部对应的就是我们熟知的 onMeasure()
、onLayout()
、onDraw()
了
关于 measure
layout
draw
的流程,这里放一张图,一图胜千言
总结
- Launcher 进程通过 Binder 向 system_server 进程的 AMS 发送启动 Activity 请求
- AMS 判断如果应用进程不存在,通过 socket 向 zygote 进程发送 fork 应用进程命令
- 应用进程启动之后,调用
ActivityThread.main()
方法启动消息轮循,建立 Binder 通信 - AMS 通过 Binder 调度 Activity
onCreate
、onResume
等生命周期 onCreate
中通过setContentView
传入的自定义布局构建以 DecorView 为 Root 的 View 树onResume
后通过Choreographer
屏幕刷新机制,开启 View 的绘制流程,执行onMeasure()
onLayout()
onDraw()
答疑解惑
什么是 fork 进程?
fork()
系统调用在父进程和子进程中的行为确实有些特殊。当一个进程调用 fork()
时,它会创建一个新的子进程,子进程是父进程的副本,包括代码、数据、堆栈等。在 fork()
之后,父进程和子进程将并发执行相同的代码。这意味着 fork()
系统调用在父进程和子进程中都会执行。
fork()
的返回值规则是为了让父进程和子进程能够区分自己的角色。在父进程中,fork()
返回新创建子进程的进程 ID(PID);在子进程中,fork()
返回 0。由于父进程和子进程并发执行相同的代码,它们可以根据 fork()
的返回值来判断自己是父进程还是子进程,并执行不同的代码路径。
以下是一个简单的示例来说明这个概念:
#include <stdio.h>
#include <unistd.h>
int main() {
pid_t pid = fork();
if (pid < 0) {
// fork failed
perror(“fork”);
return 1;
} else if (pid == 0) {
// In child process
printf(“I am the child process, my PID is %d\n”, getpid());
} else {
// In parent process
printf(“I am the parent process, my PID is %d and my child’s PID is %d\n”, getpid(), pid);
}
return 0;
}
在这个示例中,我们调用 fork()
创建一个子进程。由于父进程和子进程并发执行相同的代码,它们都会检查 fork()
的返回值。在子进程中,fork()
返回 0,因此它会执行 if (pid == 0)
分支;在父进程中,fork()
返回子进程的 PID,因此它会执行 else
分支。这样,父进程和子进程可以根据 fork()
的返回值来区分自己的角色,并执行相应的操作。
为什么 Android 系统要通过 socket 的方式让 AMS 向 zygote 发送 fork 应用进程的请求?
简单高效:Socket 通信相较于其他 IPC 机制(如 Binder、共享内存等)来说,实现起来更加简单直接。AMS 只需向 Zygote 发送一个创建新进程的请求,而无需进行复杂的数据传输和共享。因此,使用 Socket 通信可以降低实现复杂度,同时保持较高的通信效率
为什么 Android 要用 zygote 进程来 fork 应用进程,不可以直接创建新进程吗?
在 Android 系统中,Zygote 进程充当了应用程序进程的孵化器(incubator)。Zygote 进程在系统启动时创建,它预加载了许多常用的类和资源,为应用程序进程提供了一个初始化好的运行环境。当需要创建新的应用程序进程时,系统会通过 Zygote 进程来 fork 出新的进程。这种设计的主要原因是为了提高应用程序启动速度和资源共享。
以下是使用 Zygote 进程的一些优势:
- 提高应用程序启动速度:Zygote 进程在系统启动时预加载了许多常用的类和资源,这些类和资源在内存中只有一份,可以被所有应用程序进程共享。当通过 Zygote 进程 fork 出新的应用程序进程时,新进程可以直接使用这些已加载的类和资源,无需再次加载。这样可以大大减少应用程序启动时的类加载和资源初始化时间,提高启动速度。
- 资源共享:由于 Zygote 进程预加载的类和资源在内存中只有一份,它们可以被所有应用程序进程共享。这样可以减少系统的内存占用,提高资源利用率。
- 简化应用程序启动流程:通过 Zygote 进程来创建应用程序进程,可以简化启动流程,减少启动过程中的错误和异常。Zygote 进程为应用程序提供了一个统一的、经过良好测试的运行环境,有助于提高应用程序的稳定性和兼容性。
当然,理论上 Android 系统也可以直接创建新进程,但这样做会失去上述的优势,导致应用程序启动速度变慢、资源共享效率降低以及启动流程变得复杂。因此,Android 系统采用了 Zygote 进程来 fork 应用程序进程,以提高性能和稳定性。
ApplicationThread 和 ActivityThread 各自创建时机和作用?
ApplicationThread 是 ActivityThread 的内部私有类,是一个 Binder 类,它继承自 IApplicationThread.Stub,实现了 IApplicationThread 接口。ApplicationThread 主要负责处理来自 AMS(Activity Manager Service)的请求。在应用程序进程启动时,ActivityThread 会创建一个 ApplicationThread 实例,并将其注册到 AMS。随后,AMS 可以通过 ApplicationThread 调用应用程序的各种方法,如启动 Activity、发送广播、处理服务请求等
ActivityThread 是在 zygote 进程 fork 出应用进程之后,调用 ActivityThread.main() 方法内部创建的,ActivityThread 内部开启了应用程序的主线程,负责处理与应用程序生命周期、UI 事件和系统服务相关的任务;而 ApplicationThread 是一个 Binder 类,负责处理来自 AMS 的请求。这两个类共同协作,实现了应用程序与系统服务之间的通信和协同工作
getWidth() 与 getMeasuredWidth() 区别?
应用场景
- getMeasuredWidth() / getMeasuredHeight()是在Measure过程中赋值的,所以需在Measure过程后获取的值才有意义
- 同理,getWidth() / getHeight()在Layout过程中赋值,所以在Layout过程后获取的值才有意义
所以,二者的应用场景是:
- getMeasuredWidth() / getMeasuredHeight():在onLayout()中获取View的宽/高
- getWidth() / getHeight():在除onLayout()外的地方获取View的宽/高
不相等情况
- 问:上面提到,一般情况下,二者获取的宽 / 高是相等的。那么,“非一般” 情况是什么?(即二者不相等)
- 答:人为设置:通过重写View的 layout()强行设置
@Override
public void layout( int l , int t, int r , int b) {
// 改变传入的顶点位置参数
super.layout(l,t,r+100,b+100);
}
虽然这样的人为设置无实际意义,但证明了:View的最终宽 / 高 与 测量宽 / 高是可以不一样
requestLayout()、invalidate()、postInvalidate() 区别?
invaliddate() 和 postInvalidate()
这个两个方法的区别比较简单:postInvalidate() 就是在子线程调用时,把操作 post 到主线程调用,最终还是走的 invalidate()
public void postInvalidate() {
postInvalidateDelayed(0);
}
public void postInvalidateDelayed(long delayMilliseconds) {
// We try only with the AttachInfo because there’s no point in invalidating
// if we are not attached to our window
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
attachInfo.mViewRootImpl.dispatchInvalidateDelayed(this, delayMilliseconds);
}
}
public void dispatchInvalidateDelayed(View view, long delayMilliseconds) {
Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view);
mHandler.sendMessageDelayed(msg, delayMilliseconds);
}
private void handleMessageImpl(Message msg) {
switch (msg.what) {
case MSG_INVALIDATE:
((View) msg.obj).invalidate();
break;
// …
}
reqeustLayout() 和 invalidate()
两个方法的执行结果:
//调用requestLayout()方法,打印如下
MyView onMeasure:
MyView onMeasure:
MyView onLayout:
MyView onDraw:
//调用invalidate()方法,打印如下
MyView onDraw:
- requestLayout 和 invalidate 最终都会调用到 ViewRootImpl 的 scheduleTraversals() 方法
- scheduleTraversals 最终会走到 performTraversals(),方法内部会根据传参和状态的不同判断是否执行 onMeasure、onLayout()、onDraw()
总结
requestLayout()
和invalidate()
都是Android中用于更新视图的方法,它们的区别如下:
requestLayout()
:当视图的布局参数(如宽度、高度、位置等)发生变化时,需要调用requestLayout()
方法来重新测量(measure)、布局(layout)和绘制(draw)视图。requestLayout()
方法会触发视图树的一次完整的测量、布局和绘制流程,包括父视图和子视图。由于requestLayout()
涉及到整个视图树的更新,因此性能开销较大。通常情况下,当视图的尺寸或位置发生变化时,需要调用requestLayout()
方法。invalidate()
:当视图的内容(如颜色、文本等)发生变化时,需要调用invalidate()
方法来重新绘制(draw)视图。invalidate()
方法只会触发视图的绘制流程,而不会触发测量和布局。相比requestLayout()
,invalidate()
方法的性能开销较小。通常情况下,当视图的外观发生变化时,需要调用invalidate()
方法。requestLayout()
用于更新视图的布局参数,会触发整个视图树的测量、布局和绘制流程,性能开销较大。invalidate()
用于更新视图的内容,只会触发视图的绘制流程,性能开销较小。
在实际开发过程中,需要根据视图的变化情况选择合适的方法来更新视图。如果只是视图内容的变化,应优先使用invalidate()
方法;如果是视图布局参数的变化,需要使用requestLayout()
方法。
View 的更新必须在主线程吗?
任何线程都可以更新自己创建的 UI,只需要满足以下两种情况
- 在 ViewRootImpl 创建之前,可以在子线程更新 UI,比如在 Activity onCreate 的时候
- 在 ViewRootImpl 创建之后,只需要保证创建 ViewRootImpl 的线程和更新 UI 的线程是同一个就可以;比如我们在子线程调用
ViewManager#addView
我们同上说子线程不可以更新 UI,这个异常是在 ViewRootImpl 的 checkThread() 方法中抛出的:
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
“Only the original thread that created a view hierarchy can touch its views.”);
}
}
Android 学习笔录
Android 性能优化篇:https://qr18.cn/FVlo89
Android Framework底层原理篇:https://qr18.cn/AQpN4J
Android 车载篇:https://qr18.cn/F05ZCM
Android 逆向安全学习笔记:https://qr18.cn/CQ5TcL
Android 音视频篇:https://qr18.cn/Ei3VPD
Jetpack全家桶篇(内含Compose):https://qr18.cn/A0gajp
OkHttp 源码解析笔记:https://qr18.cn/Cw0pBD
Kotlin 篇:https://qr18.cn/CdjtAF
Gradle 篇:https://qr18.cn/DzrmMB
Flutter 篇:https://qr18.cn/DIvKma
Android 八大知识体:https://qr18.cn/CyxarU
Android 核心笔记:https://qr21.cn/CaZQLo
Android 往年面试题锦:https://qr18.cn/CKV8OZ
2023年最新Android 面试题集:https://qr18.cn/CgxrRy
Android 车载开发岗位面试习题:https://qr18.cn/FTlyCJ
音视频面试题锦:https://qr18.cn/AcV6Ap
深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上鸿蒙开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!**
[外链图片转存中…(img-YcxWxqXs-1715510377638)]
[外链图片转存中…(img-HTwsK4CA-1715510377639)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上鸿蒙开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新