Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >去大厂面试,结果没想到一个Handler还有中高级几种问法,我慌了...

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

原创
作者头像
Android技术干货分享
修改于 2020-11-19 06:30:04
修改于 2020-11-19 06:30:04
72000
代码可运行
举报
文章被收录于专栏:Android技术分享Android技术分享
运行总次数:0
代码可运行

Handler深层次问题解答

ThreadLocal

ThreadLocal为每个线程都提供了变量的副本,使得每个线程在某一时间访问到的并非同一个对象,这样就隔离了多个线程对数据的数据共享。

如果让你设计一个ThreadLocal,ThreadLocal 的目标是让不同的线程有不同的变量 V,那最直接的方法就是创建一个 Map,它的 Key 是线程,Value 是每个线程拥有的变量 V,ThreadLocal 内部持有这样的一个 Map 就可以了。你可能会设计成这样

实际上Java的实现是下面这样,Java 的实现里面也有一个 Map,叫做 ThreadLocalMap,不过持有 ThreadLocalMap 的不是 ThreadLocal,而是 Thread。Thread 这个类内部有一个私有属性 threadLocals,其类型就是 ThreadLocalMap,ThreadLocalMap 的 Key 是 ThreadLocal。

精简之后的代码如下

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class Thread {
  //内部持有ThreadLocalMap
  ThreadLocal.ThreadLocalMap 
    threadLocals;
}
class ThreadLocal<T>{
  public T get() {
    //首先获取线程持有的
    //ThreadLocalMap
    ThreadLocalMap map =
      Thread.currentThread()
        .threadLocals;
    //在ThreadLocalMap中
    //查找变量
    Entry e = 
      map.getEntry(this);
    return e.value;  
  }
  static class ThreadLocalMap{
    //内部是数组而不是Map
    Entry[] table;
    //根据ThreadLocal查找Entry
    Entry getEntry(ThreadLocal key){
      //省略查找逻辑
    }
    //Entry定义
    static class Entry extends
    WeakReference<ThreadLocal>{
      Object value;
    }
  }
}

在Java的实现方案中,ThreadLocal仅仅只是一个代理工具类,内部并不持有任何线程相关的数据,所有和线程相关的数据都存储在Thread里面,这样的设计从数据的亲缘性上来讲,ThreadLocalMap属于Thread也更加合理。所以ThreadLocal的get方法,其实就是拿到每个线程独有的ThreadLocalMap

还有一个原因,就是不容易产生内存泄漏,如果用我们的设计方案,ThreadLocal持有的Map会持有Thread对象的引用,这就意味着只要ThreadLocal对象存在,那么Map中的Thread对象就永远不会被回收。ThreadLocal的生命周期往往都比线程要长,所以这种设计方案很容易导致内存泄漏。

而Java的实现中Thread持有ThreadLocalMap,而且ThreadLocalMap里对ThreadLocal的引用还是弱引用,所以只要Thread对象可以被回收,那么ThreadLocalMap就能被回收。Java的实现方案虽然看上去复杂一些,但是更安全

ThreadLocal与内存泄漏

但是一切并不总是那么完美,如果在线程池中使用ThreadLocal可能会导致内存泄漏,原因是线程池中线程的存活时间太长,往往和程序都是同生共死的,这就意味着Thread持有的ThreadLocalMap一直都不会被回收,再加上ThreadLocalMap中的Entry对ThreadLocal是弱引用,所以只要ThreadLocal结束了自己的生命周期是可以被回收掉的。但是Entry中的Value却是被Entry强引用的,所以即便Value的生命周期结束了,Value也是无法被回收的,从而导致内存泄漏

所以我们可以通过try{}finally{}方案来手动释放资源

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
ExecutorService es;
ThreadLocal tl;
es.execute(()->{
  //ThreadLocal增加变量
  tl.set(obj);
  try {
    // 省略业务逻辑代码
  }finally {
    //手动清理ThreadLocal 
    tl.remove();
  }
});

以上ThreadLocal内容主要参考自这里

epoll机制

epoll机制在Handler中的应用,在主线程的 MessageQueue 没有消息时,便阻塞在 loop 的 queue.next() 中的 nativePollOnce() 方法里,最终调用到epoll_wait()进行阻塞等待。此时主线程会释放 CPU 资源进入休眠状态,直到下个消息到达或者有事务发生,通过往 pipe 管道写端写入数据来唤醒主线程工作。这里采用的 epoll 机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。 所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。

这里有一篇深度好文聊聊IO多路复用之select,poll,epoll详解,这边拿文章中的最后两段话:

  1. 表面上看epoll的性能最好,但是在连接数少并且连接都十分活跃的情况下,select和poll的性能可能比epoll好,毕竟epoll的通知机制需要很多函数回调。
  2. select低效是因为每次它都需要轮询。但低效也是相对的,视情况而定,也可通过良好的设计改善。

之所以选择Handler底层选择epoll机制,我感觉是epoll在效率上更高。在select/poll中,进程只有在调用一定的方法后,内核才对所有监视的文件描述符进行扫描,而epoll事先通过epoll_ctl()来注册一个文件描述符,一旦基于某个文件描述符就绪时,内核会采用类似callback的回调机制,迅速激活这个文件描述符,当进程调用epoll_wait()时便得到通知。(此处去掉了遍历文件描述符,而是通过监听回调的的机制。这正是epoll的魅力所在。)

Handler的同步屏障机制

如果有一个紧急的Message需要优先处理,该怎么做?这其实涉及到架构方面的设计了,通用场景和特殊场景的设计。你可能会想到sendMessageAtFrontOfQueue()这个方法,实际也远远不只是如此,Handler中加入了同步屏障这种机制,来实现[异步消息优先]执行的功能

postSyncBarrier()发送同步屏障,removeSyncBarrier()移除同步屏障

同步屏障的作用可以理解成拦截同步消息的执行,主线程的 Looper 会一直循环调用 MessageQueue 的 next() 来取出队头的 Message 执行,当 Message 执行完后再去取下一个。当 next() 方法在取 Message 时发现队头是一个同步屏障的消息时,就会去遍历整个队列,只寻找设置了异步标志的消息,如果有找到异步消息,那么就取出这个异步消息来执行,否则就让 next() 方法陷入阻塞状态。如果 next() 方法陷入阻塞状态,那么主线程此时就是处于空闲状态的,也就是没在干任何事。所以,如果队头是一个同步屏障的消息的话,那么在它后面的所有同步消息就都被拦截住了,直到这个同步屏障消息被移除出队列,否则主线程就一直不会去处理同步屏幕后面的同步消息。

而所有消息默认都是同步消息,只有手动设置了异步标志,这个消息才会是异步消息。另外,同步屏障消息只能由内部来发送,这个接口并没有公开给我们使用。

Choreographer 里所有跟 message 有关的代码,你会发现,都手动设置了异步消息的标志,所以这些操作是不受到同步屏障影响的。这样做的原因可能就是为了尽可能保证上层 app 在接收到屏幕刷新信号时,可以在第一时间执行遍历绘制 View 树的工作。

Choreographer 过程中的动作也都是异步消息,这样可以确保 Choreographer 的顺利运转,也确保了第一时间执行 doTraversal(doTraversal → performTraversals 就是执行 view 的 layout、measure、draw),这个过程中如果有其他同步消息,也无法得到处理,都要等到 doTraversal 之后。

因为主线程中如果有太多消息要执行,而这些消息又是根据时间戳进行排序,如果不加一个同步屏障的话,那么遍历绘制 View 树的工作就可能被迫延迟执行,因为它也需要排队,那么就有可能出现当一帧都快结束的时候才开始计算屏幕数据,那即使这次的计算少于 16.6ms,也同样会造成丢帧现象。

那么,有了同步屏障消息的控制就能保证每次一接收到屏幕刷新信号就第一时间处理遍历绘制 View 树的工作么?

只能说,同步屏障是尽可能去做到,但并不能保证一定可以第一时间处理。因为,同步屏障是在 scheduleTraversals() 被调用时才发送到消息队列里的,也就是说,只有当某个 View 发起了刷新请求时,在这个时刻后面的同步消息才会被拦截掉。如果在 scheduleTraversals() 之前就发送到消息队列里的工作仍然会按顺序依次被取出来执行。

下面是部分详细的分析:

WindowManager维护着所有的Activity的DecorView和ViewRootImpl。在前面我们讲过,WindowManagerGlobal的addView方法中中初始化了ViewRootImpl,然后调用它的setView方法,将DecorView作为参数传递了进去。所以我们看看ViewRootImpl做了什么

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//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方法

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@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()来监听帧率情况;

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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中注册

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Choreographer.getInstance().postFrameCallback(FPSFrameCallback(System.nanoTime()))

丢帧的原因:造成丢帧大体上有两类原因,一是遍历绘制 View 树计算屏幕数据的时间超过了 16.6ms;二是,主线程一直在处理其他耗时的消息,导致遍历绘制 View 树的工作迟迟不能开始,从而超过了 16.6 ms 底层切换下一帧画面的时机。

Handler锁相关问题

既然可以存在多个Handler往MessageQueue中添加数据(发送消息时各个Handler可能处于不同线程),那它内部是如何确保线程安全的?

Handler.sendXXXHandler.postXXX最终会会调到MessageQueue的enqueueMessage方法

源码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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(r);
    return br.postAndWait(this, timeout);
}

Handler在系统以及第三方框架的一些应用

HandlerThread

HandlerThread继承于Thread,顾名思义,实际上是Handler和Thread的一个封装,已经为我们封装的很好很安全了,内部也通过synchronized来保证线程安全,比如getLooper方法

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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方法。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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的代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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的事件。接下来我们看代码

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//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 最终会走到FragmentManagerImplscheduleCommit方法,我们可以看到他是通过Handler 发送事件。

这也就解释了为什么第5步执行完之后Fragment为什么没有立即添加到FragmentManager,所以需要Map缓存Fragment来标记是否有Fragment添加。再接着有了第6步发送移除Map缓存的消息,因为Handler处理消息是有序的。

总结

本文其实对源码的分析并没有非常仔细,却从整个系统各个方面的运用进行的不同的扩展,以及一些第三方框架中的使用,希望本文对你有帮助,喜欢的点个赞吧~

最后为了帮助大家深刻理解Handler相关知识点的原理以及面试相关知识,这里还为大家整理了Android开发相关源码精编解析

深入解析 Handler 源码解析

  • 发送消息
  • 消息入队
  • 消息循环
  • 消息遍历
  • 消息的处理
  • 同步屏障机制
  • 阻塞唤醒机制

还有Handler相关面试题解析帮助熟练掌握Handler知识:

最后为了帮助大家深刻理解Android相关知识点的原理以及面试相关知识,这里放上相关的我搜集整理的24套腾讯、字节跳动、阿里、百度2019-2020BAT 面试真题解析,我把大厂面试中常被问到的技术点整理成了视频和PDF(实际上比预期多花了不少精力),包知识脉络 + 诸多细节。

还有 高级架构技术进阶脑图 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。

我是之后按着这份资料复习面试题,后来再去面试就过了,帮助还是很大的~

以上内容均放在了开源项目:github 中已收录,里面包含不同方向的自学Android路线、面试题集合/面经、及系列技术文章等,资源持续更新中...

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Handler的初级、中级、高级问法,你都掌握了吗?
Handler是Android中的消息处理机制,是一种线程间通信的解决方案,同时你也可以理解为它天然的为我们在主线程创建一个队列,队列中的消息顺序就是我们设置的延迟的时间,如果你想在Android中实现一个队列的功能,不妨第一时间考虑一下它。本文分为三部分:
没关系再继续努力
2021/12/30
1.2K0
“终于懂了” 系列:Android屏幕刷新机制—VSync、Choreographer 全面理解!
在Android中,当我们谈到 布局优化、卡顿优化 时,通常都知道 需要减少布局层级、减少主线程耗时操作,这样可以减少丢帧。如果丢帧比较严重,那么界面可能会有明显的卡顿感。我们知道 通常手机刷新是每秒60次,即每隔16.6ms刷新一次。 问题来了:
胡飞洋
2020/08/25
10.3K1
“终于懂了” 系列:Android屏幕刷新机制—VSync、Choreographer 全面理解!
从 Android 开发到读懂源码 第08期:Android应用层视图渲染机制
Android应用层是不涉及 SurfaceFlinger,FrameBuffer 之类的底层框架,常用刷新视图都是在 View 的 draw 相关方法中进行标准绘制 api 操作,然后通过 View.invalidate 或者 View.requestLayout 通知系统进行视图显示的刷新。在此不讨论 draw 相关的 api , draw 的所有绘制方法都是直接jni调用对应 skia 的绘制,具体的自己查看 skia 引擎相关的资料。
数据库交流
2022/04/25
6610
从 Android 开发到读懂源码 第08期:Android应用层视图渲染机制
面试必考体系庞大的Handler你真的都了解吗?Handler二十七问带你打破砂锅问到底!
既然它如此重要,不知对面的你了解它多深呢?今天就和大家一起打破砂锅问到底,看看Handler这口砂锅的底到底在哪里。
Android技术干货分享
2021/03/24
5690
面试必考体系庞大的Handler你真的都了解吗?Handler二十七问带你打破砂锅问到底!
面试官:View.post() 为什么能够获取到 View 的宽高?
说一些题外话,Android 面试进阶指南 其实是我在小专栏维护的一个付费专栏,且已经有部分付费用户。本文是第九篇文章了,为了维护付费用户的权益,没有办法把所有文章都同步到公众号。如果你对这个专栏感兴趣,不妨点击文末 阅读原文 了解专栏详情。
路遥TM
2021/08/31
1.4K0
面试官:如何监测应用的 FPS ?
即使你不知道 FPS,但你一定听说过这么一句话,在 Android 中,每一帧的绘制时间不要超过 16.67ms。那么,这个 16.67ms 是怎么来的呢?就是由 FPS 决定的。
音视频开发进阶
2020/11/10
1.5K0
面试官:如何监测应用的 FPS ?
从 Android 开发到读懂源码 第03期:View.post 源码解析
这个方法在日常开发中是经常用到的,例如在子线程中我们需要更新 UI,可以通过 post 一个 runnable ,在 run 方法中去绘制 UI ,或者我们需要在 Activity 的 onCreate 中获取一个 View 的宽高时,也会通过 post 一个 runnable 并在 run 方法中获取这个 View 的 width 和 height 信息。本文基于 Android 9.0 的源码进行分析。
数据库交流
2022/04/25
3130
从 Android 开发到读懂源码 第03期:View.post 源码解析
《Android面试题思考与解答》2021年3月刊
要声明的一点是:面试题的目的不是为了让大家背题,而是从不同维度帮助大家复习,取长补短。
码上积木
2021/04/16
1.5K0
​一帧图像的Android之旅 :应用的首个绘制请求
Android 框架提供了各种用 2D 和 3D 图形渲染的 API 与制造商的图形驱动程序实现方法交互,在Android平台上应用开发者可通过三种方式将图像绘制到屏幕上:Canvas、OpenGLES、Vulkan 无论使用什么方式进行内容的生产,这个离用户最近的图形系统都扮演者一个非常重要的角色,在此系统一系列关键组件的协同帮助下,最终按照我们的预期将画面展示给用户。
音视频开发进阶
2020/07/29
2.1K1
揭秘:Android屏幕中你不知道的刷新机制
之前在整理知识的时候,看到android屏幕刷新机制这一块,以前一直只是知道,Android每16.6ms会去刷新一次屏幕,也就是我们常说的60fpx,那么问题也来了:
Android技术干货分享
2019/05/19
1.6K0
Android 面试之必问Android基础
Activity的启动模式有四种:Standard、SingleTop、SingleTask和SingleInstance。
Rouse
2021/05/28
7950
Android 面试之必问Android基础
面试?看完这篇就够了-深入分析从点击应用图标到应用界面展示
原文链接:https://juejin.cn/post/7291935944089681946
GeeJoe
2023/10/24
3430
面试?看完这篇就够了-深入分析从点击应用图标到应用界面展示
面试官:如何监测应用的 FPS ?
即使你不知道 FPS,但你一定听说过这么一句话,在 Android 中,每一帧的绘制时间不要超过 16.67ms。那么,这个 16.67ms 是怎么来的呢?就是由 FPS 决定的。
路遥TM
2021/08/31
1.6K0
Choreographer全解析
早呀各位。今天继续屏幕刷新机制的知识讲解,上文说到vsync的处理,每一帧UI的绘制前期处理都在Choreographer中实现,那么今天就来看看这个神奇的舞蹈编舞师是怎么将UI变化反应到屏幕上的。
码上积木
2021/01/11
4790
Choreographer全解析
Handler另类难点三问
之前有一章节介绍了Handler的常见面试题,今天就来说说另类的,可能你没关注的其他问题,一起看看吧。
码上积木
2020/11/24
4270
【面试专题】2021年字节、阿里、网易等 Handler 面试题集合,Android高级开发必备!
如果你是面试高级Android开发,Handler绝对是必问问题,没有之一。本文结合了2021年4-5月份字节、阿里、网易等公司的面试经历,整理了面试过程中被问及的Handler相关的知识点。会涉及到很多细节知识,大家可以作为面试参考了解一下。
Android技术干货分享
2021/06/11
1.8K1
【面试专题】2021年字节、阿里、网易等 Handler 面试题集合,Android高级开发必备!
面试必备:ThreadLocal+Looper+Handler
文章目录 一、Handler使用与概述 1.1使用步骤 1.2Handler的使用背景 二、Android消息机制分析 2.1 ThreadLocal 2.2 messageQueue 2.3 Looper 2.4 Handler 三、主线程的消息机制 Handler是消息机制的上层接口,开发中基本只用和Handler交互即可。Handler可以将一个任务切换到Handler指定的线程中执行。如在用Handler在子线程更新UI。 Android消息机制主要就是Handler的运行机制。Handler的运行
胡飞洋
2020/07/23
7270
Android VSYNC (Choreographer)与UI刷新原理分析.md
从UI控件内容更改到被重新绘制到屏幕上,这中间到底经历了什么?另外,连续两次setTextView到底会触发几次UI重绘呢?为什么Android APP的帧率最高是60FPS呢,这就是本文要讨论的内容。
看书的小蜗牛
2020/02/13
1.8K0
带你了解源码中的 ThreadLocal提问源码分析小彩蛋应用场景
这次想来讲讲 ThreadLocal 这个很神奇的东西,最开始接触到这个是看了主席的《开发艺术探索》,后来是在研究 ViewRootImpl 中又碰到一次,而且还发现 Android 中一个小彩蛋,就越发觉得这个东西很有趣,那么便借助主席的这次作业来好好梳理下吧。
请叫我大苏
2018/08/02
4200
面试官带你学Android——面试中Handler 这些必备知识点你都知道吗?
在Android面试中,关于 Handler 的问题是必备的,但是这些关于 Handler 的知识点你都知道吗?
Android技术干货分享
2020/10/12
7190
面试官带你学Android——面试中Handler 这些必备知识点你都知道吗?
推荐阅读
相关推荐
Handler的初级、中级、高级问法,你都掌握了吗?
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验