Android常见优化方式-避开高峰期

现在,随着私家车越来越多,城市路网交通压力也越来也大,交通阻塞成为家常便饭。为了缓解交通压力,一些城市采取了交通限行方式,比如北京禁止外地牌照汽车在工作日期间早晚高峰期进入五环,等到车流高峰期过去之后才允许上路。

如果把Android应用程序比作是一座城市的话,把CPU和内存等资源看作是城市的交通道路,把组成应用程序的各个功能模块,比如UI、后台服务、线程等看作是汽车,它们运行的时候看作是汽车上路行驶。那么我们编程实践时,是不是也可以借鉴这种机制,实行错峰运行呢?

应用程序不会一直在很忙的运转,总会有相对空闲的时候,如果应用程序中的一些业务逻辑不是很重要,对运行的时机不很敏感,那么就可以等到应用程序相对空闲的时候再运行。这样,这些业务就不会和正常业务一起争用宝贵的CPU资源了,例如缓存数据的过期检查和清除,它就是对时间不敏感的操作,对缓存的过期检查早一点执行和晚一点执行对系统没有很大的影响,那么我们可以把它放在APP空闲的时候执行了。

那么,如何知道Android应用程序处于空闲期呢?说实话,要准确地判断应用是否处于空闲期很难,不过,我们可以转换一下思路,我们都知道Android应用中UI用户体验是最重要的,开发Android应用的一个最重要的原则就是要优先保证UI的响应,否则容易出现ANR。显然,只要和UI任务有关的全部操作都运行完了,应用对CPU资源就不再那么敏感了,此时我们就可以执行一些不重要的任务了,也就是说,就像错峰出行一样,只要错开UI使用CPU的高峰期就行了。

我们知道Android的UI操作是按照事件驱动机制设计的,Android系统为UI线程分配了一个消息队列MessageQueue,用于与UI有关的事件循环。如果在某一时刻,发现UI线程消息队列已经空了,就意味着所有的UI事件都已经处理完了,此时UI运行的高峰期已过,就可以近似地认为Android应用进入空闲期了。

Android已经考虑到这点了,提供了一个消息队列处于空闲期时的回调接口。在消息队列MessageQueue类中,定义了一个IdleHandler接口,它是用来表示当线程处理完所有的消息,准备进入等待状态时的回调接口,可以根据这个来判断应用的核心业务运行状态是否进入了空闲状态。下面是它的定义:

    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();
    }

Android框架中有一个使用它的应用场景。当从一个Activity启动另一个Activity时,Ams要先调用当前Activity的onPause(),然后再启动目标Activity,目标Activity启动后,要把原Activity完全覆盖掉,按照Activity的生命周期管理,原Activity要调用onStop(),但它是在什么时候调用的呢,就是在IdleHandler中调用的。为什么这样做呢,因为新Activity的显示非常重要,要保证它的优先处理,旧Activity要销毁了,已经不显示UI界面了,那就等到UI线程处于空闲期再处理。

在APP运行时,如果某个场景的后台任务操作过多,以至于与UI线程激烈争用CPU资源了,不妨考虑此方案,以优化应用性能。

例如,我们知道Android应用运行启动之后,一般要进行全局初始化、资源加载、UI显示等一系列的操作,显然此时会出现CPU使用一个高峰期,如果这些任务不加区分,一股脑的全部运行起来,势必影响到UI的显示。因此,可以对此进行流程优化,全力把保障UI线程的运行,把初始化、资源加载分为两部分,一部分是UI界面显示必需的,另一部分和初始化界面不相关的,在应用启动阶段只运行和UI相关的部分,其余部分等到高峰期过去之后再运行。

当UI线程处理完MessageQueue中的所有message之后,进入了idle状态,此时UI的展现已经尘埃落定,正在等待用户的点击事件。虽然应用的其它普通任务可能也在运行,但是相对来说,没有UI功能那么重要,此时开始进行一些后台初始化的工作是非常恰当的,不过这些工作应该是启动线程异步运行的,以免占用UI线程。下面是演示代码,运行在Appliction的onCreate()。

Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
        @Override
        public boolean queueIdle() {
            initInBackground(); //后台运行的初始化任务在UI处于空闲时运行
            return false;
        }
    });

这里所说的UI空闲时间是相对的,不是绝对的。尽管UI线程的消息队列已经为空了,也进入了IdleHandler处理,在之后的事情是无法预料的,如果紧接着又收到了一个Message,那么UI应该马上处理才对。因此IdleHandler得处理要尽可能的快,如果不能尽快运行完毕,一定要在线程中异步执行,以免阻塞UI线程的对用户操作事件的正常响应。

进一步,在Android应用开发中,可以根据具体业务逻辑的特点,合理地安排它们在不同时机运行,可以有效地控制它们使用资源。我们不妨把这些业务逻辑看作是任务,根据任务在应用中的重要程度,可以把任务分为三种类型,针对它们做不同的运行策略:

  • 重要任务

是与UI操作相关的任务,是Android应用的核心业务,要优先保证UI的响应。要保证UI线程不被过多的占用,当UI要处理Touch事件时,一定要保证它能尽快地得到处理,尽量不要让别的任务和它抢占系统资源,更不能阻塞。和UI相关的耗时操作,可以使用异步方式,比如,当要从数据库获取数据时,就启动一个AsynchTask线程进行处理,得到数据后再通知UI线程。

  • 普通任务

是在后台运行的任务,与UI不相干的,如持久化数据、更新数据库等,由于相对没有UI重要,但不能不处理,只要不过多地影响到UI线程就行,可以择机处理。比如把它放入一个队列中进行排队,依次进行处理,这样就避免在集中一段时间内出现CPU使用的高峰期,在Android中可以使用HandlerThread进行排队,这样,既保证了普通任务的运行,又不过多的和主要任务抢占资源。IntentService就是按照这个机制来实现的。

  • 垃圾任务

是最不重要的任务,什么时候执行都可以,如清理缓存,可以使用空闲时间,这类任务可以等到UI线程空闲时处理。

猜你喜欢

转载自blog.csdn.net/moter/article/details/80288182