首页 > 其他分享 >IdleHandler的使用及原理

IdleHandler的使用及原理

时间:2023-12-06 09:44:22浏览次数:26  
标签:task IdleHandler MessageQueue queueIdle 使用 原理 执行 public

IdleHandler的使用及原理 - 简书 (jianshu.com)  

IdleHandler方式就是利用其特性,只有CPU空闲的时候才会执行相关任务,并且我们可以分批进行任务初始化,可以有效缓解界面的卡顿。

简单用法代码如下:

        Looper.myQueue().addIdleHandler(object: MessageQueue.IdleHandler {
            override fun queueIdle(): Boolean {
                //执行任务
                return false;
            }
        })

可以将上述代码添加到Activity onCreate中,在queueIdle()方法中实现延迟执行任务,在主线程空闲,也就是activity创建完成之后,它会执行queueIdle()方法中的代码。

如何设置是否重复执行

queueIdle()返回true表示可以反复执行该方法,即执行后还可以再次执行;返回false表示执行完该方法后会移除该IdleHandler,即只执行一次。

IdleHandler源码解析:

IdleHandler属于MessageQueue内部接口,只有一个queueIdle()方法声明。通过方法addIdleHandler将我们的idleHandler添加到集合中。

public final class MessageQueue {

    private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
  
 
     /**
     * Add a new {@link IdleHandler} to this message queue.  This may be
     * removed automatically for you by returning false from
     * {@link IdleHandler#queueIdle IdleHandler.queueIdle()} when it is
     * invoked, or explicitly removing it with {@link #removeIdleHandler}.
     *
     * <p>This method is safe to call from any thread.
     *
     * @param handler The IdleHandler to be added.
     */
    public void addIdleHandler(@NonNull IdleHandler handler) {
        if (handler == null) {
            throw new NullPointerException("Can't add a null IdleHandler");
        }
        synchronized (this) {
            mIdleHandlers.add(handler);
        }
    }
 
 
    /**
     * Callback interface for discovering when a thread is going to block
     * waiting for more messages.
     */
    public static interface IdleHandler {
        boolean queueIdle();
    }
}
IdleHandler的queueIdle方法何时执行

在MessageQueue取消息的next方法中,IdleHandler相关代码如下:

    @UnsupportedAppUsage
    Message next() {
        ...
        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                ...
                if (msg != null) {
                 ...

                // If first time idle, then get the number of idlers to run.
                // Idle handles only run if the queue is empty or if the first message
                // in the queue (possibly a barrier) is due to be handled in the future.
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                   //如果消息队列为空或者消息执行时间还未到,则获取IdleHandler队列的大小,下面需要用到
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run.  Loop and wait some more.
                   //无需要执行的  idle handler,则继续阻塞
                    mBlocked = true;
                    continue;
                }

                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                //将IdleHandler列表转为数组
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }

            // Run the idle handlers.
            // We only ever reach this code block during the first iteration.
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null; // release the reference to the handler

                boolean keep = false;
                try {
                    keep = idler.queueIdle();   //开始顺序执行所有IdleHandler的queueIdle方法
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }

                if (!keep) {   //如果发现有queueIdle()方法返回false,则线程安全地删除这个idlehandler不再执行queueIdle
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }
            ...
        }
    }
多任务延迟初始化实战:

我们根据queueIdle返回true时可以执行多次的特点,可以实现一个任务列表,然后从这个任务列表中取任务执行。

public class TaskDispatcher {

    private Queue<Runnable> delayTasks = new LinkedList<>();

    private MessageQueue.IdleHandler idleHandler = () -> {
        if (delayTasks.size() > 0) {
            Runnable task = delayTasks.poll();
            if (task != null) {
                task.run();
            }
        }
        return !delayTasks.isEmpty();  //只要task任务不为空,就继续执行初始化
    };

    public TaskDispatcher addTask(Runnable task) {
        delayTasks.add(task);
        return this;
    }

    public void start() {
        Looper.myQueue().addIdleHandler(idleHandler);
    }
}

创建一个ARouter初始化和Webview初始的task

public class WebviewInitTask implements Runnable {

    @Override
    public void run() {
        Log.i("minfo", "初始化Okhttp");
    }
}

public class WebviewInitTask implements Runnable {

    @Override
    public void run() {
        Log.i("minfo", "初始化Webview");
    }
}

界面显示后进行调用:

override fun onCreate(savedInstanceState: Bundle?) {
        setTheme(R.style.AppTheme)
        super.onCreate(savedInstanceState)
        
            TaskDispatcher()
            .addTask(ARouterInitTask())
            .addTask(WebviewInitTask())
            .start()
    }

打印执行结果

 
关于IdleHandler问题

Q:IdleHandler 有什么用?

IdleHandler 是 Handler 提供的一种在消息队列空闲时,执行任务的时机;
当 MessageQueue 当前没有立即需要处理的消息时,会执行 IdleHandler;

Q:MessageQueue 提供了 add/remove IdleHandler 的方法,是否需要成对使用?

不是必须;
IdleHandler.queueIdle() 的返回值,可以移除加入 MessageQueue 的 IdleHandler;
Q:当 mIdleHanders 一直不为空时,为什么不会进入死循环?

只有在 pendingIdleHandlerCount 为 -1 时,才会尝试执行 mIdleHander;
pendingIdlehanderCount 在 next() 中初始时为 -1,执行一遍后被置为 0,所以不会重复执行;
Q:是否可以将一些不重要的启动服务,搬移到 IdleHandler 中去处理?

不建议;
IdleHandler 的处理时机不可控,如果 MessageQueue 一直有待处理的消息,那么 IdleHander 的执行时机会很靠后;
Q:IdleHandler 的 queueIdle() 运行在那个线程?

陷进问题,queueIdle() 运行的线程,只和当前 MessageQueue 的 Looper 所在的线程有关;
子线程一样可以构造 Looper,并添加 IdleHandler;

参考:

   

标签:task,IdleHandler,MessageQueue,queueIdle,使用,原理,执行,public
From: https://www.cnblogs.com/wanglongjiang/p/17878825.html

相关文章

  • NET Core 3.1 MVC 在html中引用js的方法使用时不生效异常
    在html的select元素添加了onchange事件,changeContent方法也在当前html下。<selectid="changeLanguage"class="form-controlinput-lg"asp-for="language"asp-items="Model.supportedLanguages"onchange="changeContent()">&l......
  • 使用技巧 | 红米 Redmi Note 12 Turbo优化记录(去广告等)
    原文链接:https://engapi.com/article/7569原文也是我写的。我的红米Redminote8pro6+128已有些卡顿,遂在K70推出之际下单了RedmiNote12Turbo16+1T当备机。以下记录红米Note12Turbo去广告和优化过程,小米/红米系列都可参考。1.去广告系统设置参考这里:小米/红米手机如何......
  • 使用阿里云服务器部署.net 6 mvc 程序(使用域名)
    1.创建Web程序打开vs2022,选择ASP.NETCoreWeb应用(模型-视图-控制器)创建项目。修改Program.cspublicstaticvoidMain(string[]args){varbuilder=WebApplication.CreateBuilder(args);//Addservicestothecontainer.builder.Services.AddControllers......
  • 使用ThinkPHP框架根据Excel内容批量处理图片名称详解记录
    ThinkPHP依赖以下环境Nginx+PHP,建议提前装好Composer,PHP、Composer需要设置好系统环境变量。1.通过Composer安装Laravel框架composercreate-projecttopthink/thinkthinkphp6启动服务测试cdthinkphp6phpthinkrun然后就可以在浏览器中访问http://localhost:8000如果不能显示......
  • 使用分布式事务 Seata 的 TCC 模式
    Seata的TCC模式需要通过人工编码来实现数据的回滚恢复,有点麻烦,但是性能最高。TCC是3个方法的首字母缩写,即Try方法、Confirm方法、Cancel方法。Try方法进行资源的检查和冻结,Confirm方法是当所有事务都成功后调用的方法,Cancel方法是当整体事务中某个分支事务失败时调用......
  • 浏览器跨 Tab 窗口通信原理
    目录浏览器跨Tab窗口通信原理BroadcastChannelSharedWorkerAPIlocalStorage/sessionStorage跨Tab窗口通信应用场景浏览器跨Tab窗口通信原理所谓多窗口下进行互相通信,是指在浏览器中,不同窗口(包括不同标签页、不同浏览器窗口甚至不同浏览器实例)之间进行数据传输和通信的......
  • 使用new关键字,是用来调用这个对象,并给了一个新名字和内存
    new关键字是用于创建对象的关键字。它会分配内存并初始化对象。当我们使用new关键字创建对象时,会自动调用该对象的构造方法。构造方法可以用于初始化类的属性,并为对象分配内存。例如,以下代码定义了一个Person类:publicclassPerson{   privateStringname;   private......
  • 使用 Guava Retry 优雅的实现重试机制
    王有志,一个分享硬核Java技术的互金摸鱼侠加入Java人的提桶跑路群:共同富裕的Java人大家好,我是王有志。今天我会通过一个真实的项目改造案例和大家聊一聊如何优雅的实现Java中常用的的重试机制。业务背景在我们的系统中当客户完成支付后,保单管理系统会通过MQ推送出一条包......
  • Day20 Java流程控制02:scanner进阶使用
    Java流程控制02:scanner进阶使用1.判断是否是整数/小数:packagecom.baixiaofan.scanner;importjava.util.Scanner;publicclassDemo04{publicstaticvoidmain(String[]args){Scannerscanner=newScanner(System.in);inti=0;fl......
  • java_JDBC连接池C3P0的使用
    1、数据库连接池基本介绍1.预先再缓冲池中放入一定数量的连接,当需要建立连接时,只需要从“缓冲池”中取出一个,使用完毕后放回。2.数据库连接池负责分配、管理和释放数据库连接,它允许多个程序重复的使用现有的数据库连接,而不是重新建立一个。3.当应用程序向连接池请求的数量超过最大......