「性能优化1.3」延迟加载方案

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/lwj_zeal/article/details/88654365

「性能优化1.0」启动分类及启动时间的测量
「性能优化1.1」计算方法的执行时间
「性能优化1.2」异步优化
「性能优化1.3」延迟加载方案

一、延时加载

1.1、为什么要延迟加载?

我们在 MainActivity 中优先应该展示视图给用户,而一些其它的数据可以将其延迟再去初始化,例j如我们一般会在进入 MainActivity 时去检测一下当前是否是新用户来确定是否要显示引导图,或者读取当前未读的消息等,这些操作要求的及时性并不是那么高,这样就不会影响视图的展示。

1.2、延迟加载的方案是什么?

  • 常规实现

在 MainActivity#OnCreate 执行一个 postDelayed(Runnable r, long delayMillis)

  • 更优方案

使用 IdleHandler

1.3、常规方案

postDelayed(Runnable r, long delayMillis)这种方案的伪代码如下:

//MainActivity.java
public void onCreate(...) {
    
    ...
    mainHandler.postDelay(new Runnable(){
        public void run() {
            //具体要做的延迟加载
            //例如读取未读消息
            showTipPopWindow();
            //用户当前登录状态等
            checkUnReadMsg();            
        }
    },500);
    ...
}

从伪代码中可以看出,这种方式确实可以做到数据的延迟加载,但是其缺点是很明显的:

时间不好把控,不能确定 delay 多少时间。如果时间设置过短,那么此时 UI 还没渲染完毕,这势必会阻塞到 UI 的渲染,如果过长,那么又导致延迟时间变长了。

1.4、更优方案

1.4.1、IdleHandler 处理延迟加载

源码位置:MessageQueue.IdleHandler

先列出来 IdleHandler 的源码。

/**
 * Callback interface for discovering when a thread is going to block
 * waiting for more messages.
 */
public static interface IdleHandler {
    /**
     * Called when the message queue has run out of messages and will now
     * wait for more.  Return true to keep your idle handler active, false
     * to have it removed.  This may be called if there are still messages
     * pending in the queue, but they are all scheduled to be dispatched
     * after the current time.
     */
    boolean queueIdle();
}

我们先来描述一下关于 Handler ,Looper,MessageQueue,Message 这几个东西的作用。

每一个 Looper 都会绑定一个线程,,而且 Looper 会不断的轮训对应的 MessageQueue 去获取需要处理的 Message,当前消息队列没有更多消息可以处理的话(也就是当前是空闲状态),那么系统就会告诉 IdleHandler,我们只需要在 queueIdle() 方法中处理我们做的任务。

扫描二维码关注公众号,回复: 5840602 查看本文章

当我们处理好我们的一个任务之后,queueIdle() 返回 true 表示我们要继续使用这个 IdleHanlder,下次 MessageQueue 空闲时,还是会继续回调 queueIdle() 方法,我们继续在这里处理我们未完成的任务即可,如果返回 false ,那么系统就会移除这个 IdleHandler

1.4.2、核心思想是什么?

分批进行延迟加载,每一次 Handler 空闲时就加载一个任务

配合这个示例图和下面的代码来理解如何使用 IdleHanlder 实现延迟初始化。

延迟初始化.png

  • 延迟加载的两个任务
//显示引导框UI
public void showTipPopWindow(){
    //一顿操作异步操作,然后更新 UI 视图
}
//加载消息红点UI
public void checkUnReadMsg(){
    //一顿操作异步操作,然后更新 UI 视图
}
  • 将任务的执行放到 Runnable 中,保存到一个集合列表中 tasks
final List<Runnable> tasks = new ArrayList<>();
tasks.add(new Runnable() {
    @Override
    public void run() {
        showTipPopWindow();
    }
});
tasks.add(new Runnable() {
    @Override
    public void run() {
        checkUnReadMsg();
    }
});
  • 定义一个 IdleHandler
MessageQueue.IdleHandler idleHandler = new MessageQueue.IdleHandler() {
    @Override
    public boolean queueIdle() {
        Log.d(TAG, "queueIdle");
        if (!tasks.isEmpty()) {
            //取出一个任务
            Runnable task = tasks.get(0);
            //执行这个任务
            task.run();
            //执行完毕,移除这个人
            tasks.remove(task);
        }
        //如果任务列表为空,就不要这个 IdleHandler 了,
        return !tasks.isEmpty();
    }
};
//往主线程的 MessageQueue 中添加我们自定义的 IdleHandler
Looper.myQueue().addIdleHandler(idleHandler);
  • 在 MainActitity 中第一个 View 绘制时调用

我们上一节中已经了解了如何获取应用的启动时间,我们是通过打点的方式来计算这个时间差值,开始打点时间为 Application#attachBaseContext,而结束打点的位置就是第一个 View的 onPreDrawListener 回调时。具体不清楚的读者可以回到上一小节看一下具体过程。

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    TextView textView = findViewById(R.id.textview);
    textView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
        @Override
        public boolean onPreDraw() {
                MessageQueue.IdleHandler idleHandler = new   MessageQueue.IdleHandler() {
                    @Override
                    public boolean queueIdle() {
                        Log.d(TAG, "queueIdle");
                            if (!tasks.isEmpty()) {
                                //取出一个任务
                                    Runnable task = tasks.get(0);
                                //执行这个任务
                                task.run();
                                //执行完毕,移除这个人
                                tasks.remove(task);
                            }
                            //如果任务列表为空,就不要这个 IdleHandler 了,
                        return !tasks.isEmpty();
                     }
            };
//往主线程的 MessageQueue 中添加我们自定义的 IdleHandler
Looper.myQueue().addIdleHandler(idleHandler);
            return true;
        }
    });
}

好了,经过上面几步的操作,我们就已经将需要延迟加载的任务放到 IdleHanlder 中去处理,这种方案解决了上面通过 Handler.postDelay()不能确定具体需要 delay 的时间问题,并且因为任务的及时性要求不是很高,那么就可以等主线程比较空闲时再来执行,一举两得。

二、总结

本节中,我们了解为什么要进行任务的异步加载,以及实现了两种异步加载方案,并对比了两种方案的优劣性,最后我们得到一个更加优的方案就是利用系统提供的 IdleHandler 来处理我们的延迟任务。在本节中最重要的是需要理解好延迟加载的核心思想就是在主线程``空闲时每次只加载一个任务

记录于 2019年3月19日

猜你喜欢

转载自blog.csdn.net/lwj_zeal/article/details/88654365