去大厂面试,结果没想到一个Handler还有中高级几种问法,我慌了(1)

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.sendXXXHandler.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 是同一个原理)是通过添加一个空的FragmentActivity 或者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开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

  • 28
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值