WindowManager维护着所有的Activity的DecorView和ViewRootImpl。在前面我们讲过,WindowManagerGlobal的addView方法中中初始化了ViewRootImpl,然后调用它的setView方法,将DecorView作为参数传递了进去。所以我们看看ViewRootImpl做了什么
//ViewRootImpl.java
//view是DecorView
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
···
// Schedule the first layout -before- adding to the window
// manager, to make sure we do the relayout before receiving
// any other events from the system.
requestLayout(); //发起布局请求
···
view.assignParent(this); //将当前ViewRootImpl对象this作为参数调用了DecorView的 assignParent
···
}
}
}
在setView()方法里调用了DecorView的assignParent
//View.java
/*
-
Caller is responsible for calling requestLayout if necessary.
-
(This allows addViewInLayout to not request a new layout.)
*/
@UnsupportedAppUsage
void assignParent(ViewParent parent) {
if (mParent == null) {
mParent = parent;
} else if (parent == null) {
mParent = null;
} else {
throw new RuntimeException(“view " + this + " being added, but”
- " it already has a parent");
}
}
参数是ViewParent,而ViewRootImpl是实现了ViewParent接口的,所以在这里就将DecorView和ViewRootImpl绑定起来了。每个Activity的根布局都是DecorView,而DecorView的parent又是ViewRootImpl,所以在子View里执行invalidate()之类的工作,循环找parent,最后都会找到ViewRootImpl里来。所以实际上View的刷新都是由ViewRootImpl来控制的。
即使是界面上一个小小的 View 发起了重绘请求时,都要层层走到 ViewRootImpl,由它来发起重绘请求,然后再由它来开始遍历 View 树,一直遍历到这个需要重绘的 View 再调用它的 onDraw()
方法进行绘制。
View.invalidate()
请求重绘的操作最后调用到的是ViewRootImpl.scheduleTraversals()
,而ViewRootImpl.setView()
方法中调用了requestLayout方法
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
最终也调用到了scheduleTraversals()方法,其实这个方法是屏幕刷新的关键。
其实打开一个 Activity,当它的 onCreate—onResume 生命周期都走完后,才将它的 DecoView 与新建的一个 ViewRootImpl 对象绑定起来,同时开始安排一次遍历 View 任务也就是绘制 View 树的操作等待执行,然后将 DecoView 的 parent 设置成 ViewRootImpl 对象。所以我们在onCreate~onResume中获取不到View宽高,界面的绘制也是在onResume之后才开始执行的。
ViewRootImpl.scheduleTraversals()
的一系列分析以及屏幕刷新机制可以参考这篇文章,这里的内容也是大部分参考它的,同步屏障相关的分析内容也在里面。
Choreographer主要作用是协调动画,输入和绘制的时间,它从显示子系统接收定时脉冲(例如垂直同步),然后安排渲染下一个frame的一部分工作。
可通过Choreographer.getInstance().postFrameCallback()来监听帧率情况;
public class FPSFrameCallback implements Choreographer.FrameCallback {
private static final String TAG = “FPS_TEST”;
private long mLastFrameTimeNanos;
private long mFrameIntervalNanos;
public FPSFrameCallback(long lastFrameTimeNanos) {
mLastFrameTimeNanos = lastFrameTimeNanos;
//每一帧渲染时间 多少纳秒
mFrameIntervalNanos = (long) (1000000000 / 60.0);
}
@Override
public void doFrame(long frameTimeNanos) { //Vsync信号到来的时间frameTimeNanos
//初始化时间
if (mLastFrameTimeNanos == 0) {
//上一帧的渲染时间
mLastFrameTimeNanos = frameTimeNanos;
}
final long jitterNanos = frameTimeNanos - mLastFrameTimeNanos;
if (jitterNanos >= mFrameIntervalNanos) {
final long skippedFrames = jitterNanos / mFrameIntervalNanos;
if (skippedFrames > 5) {
Log.d(TAG, "Skipped " + skippedFrames + " frames! "
- “The application may be doing too much work on its main thread.”);
}
}
mLastFrameTimeNanos = frameTimeNanos;
//注册下一帧回调
Choreographer.getInstance().postFrameCallback(this);
}
}
调用方式在Application中注册
Choreographer.getInstance().postFrameCallback(FPSFrameCallback(System.nanoTime()))
丢帧的原因:造成丢帧大体上有两类原因,一是遍历绘制 View 树计算屏幕数据的时间超过了 16.6ms;二是,主线程一直在处理其他耗时的消息,导致遍历绘制 View 树的工作迟迟不能开始,从而超过了 16.6 ms 底层切换下一帧画面的时机。
Handler锁相关问题
既然可以存在多个Handler往MessageQueue中添加数据(发送消息时各个Handler可能处于不同线程),那它内部是如何确保线程安全的?
Handler.sendXXX
,Handler.postXXX
最终会会调到MessageQueue的enqueueMessage方法
源码如下:
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException(“Message must have a target.”);
}
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
//加锁保证安全
synchronized (this) {
···
}
}
其内部通过synchronized关键字保证线程安全。同时messagequeue.next()内部也会通过synchronized加锁,确保取的时候线程安全,同时插入也会加锁。这个问题其实不难,只是看你有没有了解源码。
Handler中的同步方法
如何让handler.post消息执行之后然后再继续往下执行,同步方法runWithScissors
public final boolean runWithScissors(@NonNull Runnable r, long timeout) {
if (r == null) {
throw new IllegalArgumentException(“runnable must not be null”);
}
if (timeout < 0) {
throw new IllegalArgumentException(“timeout must be non-negative”);
}
if (Looper.myLooper() == mLooper) {
r.run();
return true;
}
BlockingRunnable br = new BlockingRunnable®;
return br.postAndWait(this, timeout);
}
Handler在系统以及第三方框架的一些应用
HandlerThread
HandlerThread继承于Thread,顾名思义,实际上是Handler和Thread的一个封装,已经为我们封装的很好很安全了,内部也通过synchronized来保证线程安全,比如getLooper方法
public Looper getLooper() {
if (!isAlive()) {
return null;
}
// If the thread has been started, wait until the looper has been created.
synchronized (this) {
while (isAlive() && mLooper == null) {
try {
wait();
} catch (InterruptedException e) {
}
}
}
return mLooper;
}
在线程的run方法里,所以当线程启动之后才能创建Looper并赋值给mLooper,这里的阻塞就是为了等待Looper的创建成功。同时该方法是用Public修饰的,说明该方法是提供外部调用的,Looper创建成功提供给外部使用。
IntentService
简单看一下源码就能看到Handler的应用,Handler的handMessage最终会回调到onHandleIntent方法。
public abstract class IntentService extends Service {
private volatile Looper mServiceLooper;
@UnsupportedAppUsage
private volatile ServiceHandler mServiceHandler;
如何打造一个不崩溃的程序
打造一个不崩溃的程序,可以参考这篇文章
Glide中的应用
Glide 相信大应该非常熟悉了,我们都知道Glide生命周期的控制(如果不了解,可以看下Glide相关文章的分析,跟LiveData 是同一个原理)是通过添加一个空的Fragment到Activity 或者Fragment中,然后通过FragmentMannager管理Fragment的生命周期,从而达到生命周期的控制。下面是节选了Glide一段添加Fragment的代码:
private RequestManagerFragment getRequestManagerFragment(
@NonNull final android.app.FragmentManager fm,
@Nullable android.app.Fragment parentHint,
boolean isParentVisible) {
//1.通过FragmentManager获取 RequestManagerFragment,如果已添加到FragmentManager则返回实例,否则为空
RequestManagerFragment current = (RequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG);
if (current == null) {
//2.如果fm里面没有则从map缓存里取
current = pendingRequestManagerFragments.get(fm);
if (current == null) {
//3.第1和2步都没有,说明确实没创建,则走创建的流程
current = new RequestManagerFragment();
current.setParentFragmentHint(parentHint);
if (isParentVisible) {
current.getGlideLifecycle().onStart();
}
//4.将新创建的fragment 保存到map容器
pendingRequestManagerFragments.put(fm, current);
//5.发送添加fragment事务事件
fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss();
//6.发送remove 本地缓存事件
handler.obtainMessage(ID_REMOVE_FRAGMENT_MANAGER, fm).sendToTarget();
}
}
return current;
}
//跟上面那个方法唯一的区别是 这个是Fragment 的FragmentManager,上面的是Acitivity 的FragmentManager
private SupportRequestManagerFragment getSupportRequestManagerFragment(
@NonNull final FragmentManager fm, @Nullable Fragment parentHint, boolean isParentVisible) {
SupportRequestManagerFragment current =
(SupportRequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG);
if (current == null) {
current = pendingSupportRequestManagerFragments.get(fm);
if (current == null) {
current = new SupportRequestManagerFragment();
current.setParentFragmentHint(parentHint);
if (isParentVisible) {
current.getGlideLifecycle().onStart();
}
pendingSupportRequestManagerFragments.put(fm, current);
fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss();
handler.obtainMessage(ID_REMOVE_SUPPORT_FRAGMENT_MANAGER, fm).sendToTarget();
}
}
return current;
}
@Override
public boolean handleMessage(Message message) {
boolean handled = true;
Object removed = null;
Object key = null;
switch (message.what) {
case ID_REMOVE_FRAGMENT_MANAGER:
//7.移除缓存
android.app.FragmentManager fm = (android.app.FragmentManager) message.obj;
key = fm;
removed = pendingRequestManagerFragments.remove(fm);
break;
//省略代码…
}
//省略代码…
return handled;
}
看了上面的代码,大家可能会有疑惑。
-
Fragment添加到FragmentManager为什么还要保存到map容器里(第4步)?
-
判断Fragment是否已添加为啥还要从map容器判断(第2步),FragmentManager 去find 不就可以做到了吗?
其实答案很简单,学了Handler原理之后我们知道:就是在第5步执行完之后并没有将Fragment添加到FragmentManager(事件排队中),而是发送添加Fragment的事件。接下来我们看代码
//FragmentManagerImpl.java
void scheduleCommit() {
synchronized (this) {
boolean postponeReady =
mPostponedTransactions != null && !mPostponedTransactions.isEmpty();
boolean pendingReady = mPendingActions != null && mPendingActions.size() == 1;
if (postponeReady || pendingReady) {
mHost.getHandler().removeCallbacks(mExecCommit);
mHost.getHandler().post(mExecCommit);
updateOnBackPressedCallbackEnabled();
}
}
}
添加Fragment 最终会走到FragmentManagerImpl的 scheduleCommit
方法,我们可以看到他是通过Handler 发送事件。
这也就解释了为什么第5步执行完之后Fragment为什么没有立即添加到FragmentManager,所以需要Map缓存Fragment来标记是否有Fragment添加。再接着有了第6步发送移除Map缓存的消息,因为Handler处理消息是有序的。
总结
本文其实对源码的分析并没有非常仔细,却从整个系统各个方面的运用进行的不同的扩展,以及一些第三方框架中的使用,希望本文对你有帮助,喜欢的点个赞吧~
最后为了帮助大家深刻理解Handler相关知识点的原理以及面试相关知识,这里还为大家整理了**Android开发相关源码精编解析**:
深入解析 Handler 源码解析
-
发送消息
-
消息入队
-
消息循环
-
消息遍历
-
消息的处理
-
同步屏障机制
-
阻塞唤醒机制
最后
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。
因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门
如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。**
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。
因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-mVCa9Rx7-1715862355122)]
[外链图片转存中…(img-yHFnhi9Z-1715862355123)]
[外链图片转存中…(img-wVwxLJOu-1715862355124)]
[外链图片转存中…(img-xbhtFxfR-1715862355126)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门
如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!