android点击刷新界面,Android页面卡顿,是时候做刷新控制了

Android开发中,我们有时候会遇到变更频繁、特别复杂的页面。比如拿微信首页的聊天会话列表来说,每一条消息的到来,都会影响到聊天顺序,至少需要触发一次页面刷新。假如我的群非常多,隔一段时间不用微信的话,可能进入App后一直在收到消息,页面一直在刷新,那么刷新过于频繁的情况下,就会出现卡顿。微信现在当然不会这么明显,那如果是你来开发,会有微信这样的效果吗?

实际开发中,我就遇到过很多次这样的问题。各个模块、各种业务场景导致的数据变化,都有可能来触发同一个页面的刷新,我们一句简单的notifyDataChange调用就能实现需求,但久而久之,页面卡顿越来越明显。

至于卡顿的原理,大家可以自行去了解。现在我们需要做的,就是如何限制对UI的刷新?因为频繁的刷新,对于用户来说,眼睛是感知不到的,反而会导致掉帧和卡顿。每次刷新,我们也可以认为是对UI的一次请求。

大型App的多业务模块可能对服务端触发同一个请求,浪费流量甚至影响服务端性能,能否做到某些高并发情况下减轻对服务端的压力,同时又不影响客户端数据的最终一致性?我们完全可以对同一类请求进行限制,提高CS间请求有效交互率。

在一些常见的CS模式开发过程中,客户端(Client)和服务端(Server)之间依赖特定协议的请求进行数据同步和交互。经常会发生如下场景:因客户端的逻辑不健壮或漏洞,可能在特殊情况下爆发对服务端的频繁请求,从而导致服务端性能急剧下降甚至是宕机。

从客户端角度看,不同业务场景下可能触发的都是同一个请求,必要性非常低。客户端很多对服务端的请求用于同步数据,目的仅在于尽可能保证两端数据一致性。

那么,我们该采用什么样的策略来减少或者限制同类请求呢?

搞个定时器,每个请求延迟一分钟再触发,在当前请求触发之前的相同请求直接丢弃,不就OK了吗?

不错,既然认为是同类请求,那么丢弃其中一部分似乎合情合理。但这个丢弃方式是否太过粗糙?可能出现类似如下场景的问题。

假设某个买菜App中,用户再订单列表页点击确认收货后,进入订单详情后却一直显示“未确认”,等到一分钟后,才变成“已确认”。

对于上一个请求尚未响应时,同时由触发的同一请求,可以进行请求合并,从而降低对服务器并发。

不错,对同时并发的请求如果能够进行合并,的确会降低对服务器的并发,但是实际效果可能并不明显。

假设一个请求的响应时间需要100 ms,那么对于同一客户端,如果发送同一请求的平均间隔大于100 ms,那么这样的合并就意义不大了,但是如果我们服务端认为100 ms发一次本身就已经很频繁了,该怎么降低呢?假设服务端只能承受200 ms一次的访问呢?

总结以上两种想法,我们需要寻找一种方案,既能实现闲时及时请求,又能实现忙时大批量减少请求。至于刚提到的请求合并,我们后续文章再探讨一下。

在Android开发时,我们经常接触到Handler,作为一个常驻线程的句柄,可以通过post方法对其所属线程提交Runnable任务,期望它“立刻”执行,通过postDelayed方法,可以延迟一段时间执行。因此我们可以封装实现一个限制执行请求/任务的LimitRequester。

package com.ya.helper.util;

import android.os.Handler;

import java.util.concurrent.atomic.AtomicBoolean;

/**

* 包装Handler,实现请求控制

* 机制:前一次请求尚未执行完成,则仅保留最新一次请求

* 注意:同一LimitRequester对象提交的所有Runnable视为同一类可合并请求

*

* @author miss

*/

public class LimitRequester {

private static final String TAG = "LimitRequester";

private Handler mHandler;

private AutoTimeRunnable aRun;

private int mTime;

/**

* 创建限制刷新的Handler

* 上个请求尚未执行完,则仅保留最新一次请求

*

* @param handler 实际使用的线程句柄 Handler

*/

public LimitRequester(Handler handler) {

mHandler = handler;

aRun = new AutoTimeRunnable();

}

/**

* 创建间隔刷新的Handler

* 两次请求提交间隔不能超过interval,否则丢弃仅执行最新一次

*

* @param handler

* @param interval 毫秒间隔

*/

public LimitRequester(Handler handler, int interval) {

if (interval <= 0) {

throw new IllegalArgumentException("LimitRequester interval must > 0");

}

mHandler = handler;

mTime = interval;

aRun = new AutoTimeRunnable();

}

/**

* 提交请求,默认为同一类别

* 若Handler处于非空闲状态,则仅执行最新一次提交

*

* @param runnable

*/

public void postLimit(Runnable runnable) {

if (null == runnable) {

return;

}

aRun.post(runnable);

}

private class AutoTimeRunnable implements Runnable {

private Runnable toRun;

//标识是否空闲,默认是空闲状态

private final AtomicBoolean isIdle = new AtomicBoolean(true);

private long lastExecuteTime = 0L;

public void post(Runnable toRun) {

//若空闲,则执行,不空闲,则仅保存最新一次Runnable

this.toRun = toRun;

if (isIdle.compareAndSet(true, false)) {

execute();

}

}

/**

* 执行最新保存的Runnable

* 执行完成后,若发现有新请求,则执行

* 若没有新请求,则置为空闲状态

*/

@Override

public void run() {

Runnable r = toRun;

r.run();

if (r != toRun) {

execute();

} else {

isIdle.set(true);

}

}

/**

* 具体执行逻辑

* 若无间隔,则立刻执行

* 若有间隔,两次执行间隔超过要求间隔,立刻执行

* 两次执行间隔不及要求间隔,则延迟间隔时间后执行

*/

private void execute() {

if (mTime > 0) {

long now = System.currentTimeMillis();

long interval = now - lastExecuteTime;

if (interval >= mTime) {

lastExecuteTime = now;

mHandler.post(this);

} else {

lastExecuteTime = now + mTime;

mHandler.postDelayed(this, mTime);

}

} else {

mHandler.post(this);

}

}

}

}

需要限制请求访问的地方,即使用LimitRequester,传入执行线程句柄,通过postLimit进行提交即可。

需要注意的地方是:

1、同一个LimitRequester所处理的请求必须是可进行合并的Runnable,Runnable是不是同一个对象引用无所谓

2、不同请求的限制处理,需要构造不同的LimitRequester对象,实际可以是同一线程执行

我们通过一段测试代码进行验证:

假设我们要执行1000次打印,每200ms打印一次,如果使用LimitRequester,限制1s执行一次,则最终输出结果应该是200次打印。

new Thread(new Runnable() {

@Override

public void run() {

LimitRequester requester = new LimitRequester(new Handler(Looper.getMainLooper()), 1000);

final int[] count = {1};

for (int i = 0; i < 1000; i++) {

try {

Thread.sleep(200);

} catch (InterruptedException e) {

e.printStackTrace();

}

requester.postLimit(new Runnable() {

@Override

public void run() {

Log.i(TAG, "执行第" + count[0]++ + "次");

}

});

}

}

}).start();

输出结果为:

.....

2020-05-23 15:58:55.292 7123-7123/com.ya.helper.androidlab I/TEST: 执行第195次

2020-05-23 15:58:56.302 7123-7123/com.ya.helper.androidlab I/TEST: 执行第196次

2020-05-23 15:58:57.315 7123-7123/com.ya.helper.androidlab I/TEST: 执行第197次

2020-05-23 15:58:58.326 7123-7123/com.ya.helper.androidlab I/TEST: 执行第198次

2020-05-23 15:58:59.334 7123-7123/com.ya.helper.androidlab I/TEST: 执行第199次

2020-05-23 15:59:00.339 7123-7123/com.ya.helper.androidlab I/TEST: 执行第200次

2020-05-23 15:59:01.345 7123-7123/com.ya.helper.androidlab I/TEST: 执行第201次

为啥是201次呢?因为第一次postLimit是立刻执行的,之后的才会进行合并。可以看到每次时间差不会是那么精确的1s,因为Handler的postDelay不可能做到极其精确的定时。

如果你正在开发的页面,也存在刷新过频的问题,不妨用这个方法一试,无论何处触发刷新,无论场景多么复杂,只要最后使用同一个LimitRequester进行执行,问题迎刃而解!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值