关于多次重复网络请求问题

Posted 梁飞宇

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了关于多次重复网络请求问题相关的知识,希望对你有一定的参考价值。

问题分析

一个页面,可以通过点击不同的模块获取相应的数据。但是,当用户频繁点击的时候,有的模块网络请求数据返回会比较慢,这个时候返回的数据就会覆盖当前模块的数据。

解决方法

加锁处理

切换模块时,会对同一个API进行多次请求,但因为调用的接口都是一样的,所以最好就是加上锁,防止重复请求造成网络资源浪费。

 @synchronized (self) //加锁,避免数组重复创建添加等问题
         static NSMutableArray * successBlocks;//用数组保存回调
         static NSMutableArray * failureBlocks;
         static dispatch_once_t onceToken;
         dispatch_once(&onceToken, ^//仅创建一次数组
            successBlocks = [NSMutableArray new];
            failureBlocks = [NSMutableArray new];
         );
         if (success) //每调用一次此函数,就把回调加进数组中
            [successBlocks addObject:success];
         
         if (failure) 
            [failureBlocks addObject:failure];
         

         static BOOL isProcessing = NO;
         if (isProcessing == YES) //如果已经在请求了,就不再发出新的请求
            return;
         
         isProcessing = YES;
         [self callerPostTransactionId:transactionId parameters:dic showActivityIndicator:showActivityIndicator showErrorAlterView:showErrorAlterView success:^(id responseObject) 
            @synchronized (self) //网络请求的回调也要加锁,这里是另一个线程了
               for (successBlock eachSuccess in successBlocks) //遍历回调数组,把结果发给每个调用者
                  eachSuccess(responseObject);
               
               [successBlocks removeAllObjects];
               [failureBlocks removeAllObjects];
               isProcessing = NO;
            
          failure:^(id data) 
            @synchronized (self) 
               for (failureBlock eachFailure in failureBlocks) 
                  eachFailure(data);
               
               [successBlocks removeAllObjects];
               [failureBlocks removeAllObjects];
               isProcessing = NO;
            
         ];
      

取消请求

取消全部请求:

[manager.operationQueue cancelAllOperations];

取消单个请求,以post请求为例:

AFHTTPRequestOperation *post =[manager POST:nil parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) 
    //doing something
 failure:^(AFHTTPRequestOperation *operation, NSError *error) 
    // error handling.
];
//Cancel operation
[post cancel];

AFNetworking取消正在进行的网络请求:

//单例模式
+ (HttpManager *)sharedManager 
  static dispatch_once_t once;
  dispatch_once(&once, ^
    httpManager = [[HttpManager alloc] init];
  );
  return httpManager;


//网络类初始化
- (id)init
  self = [super init];
  if(self)
    manager = [AFHTTPSessionManager manager];
    manager.requestSerializer = [AFJSONRequestSerializer serializer];
    manager.responseSerializer = [AFHTTPResponseSerializer serializer];
  
  return self;
[[HttpManager sharedManager] dataFromWithBaseURL:BaseURL path:url method:@"POST" timeInterval:10 params:parmas success:^(NSURLRequest *request, NSURLResponse *response, id JSON) 

 failure:^(NSURLRequest *request, NSURLResponse *response, NSError *error, id JSON) 

 error:^(id JSON) 

 finish:^(id JSON) 

];

取消正在进行的网络请求

- (void)cancelRequest 
  if ([manager.tasks count] > 0) 
    NSLog(@"返回时取消网络请求");
    [manager.tasks makeObjectsPerformSelector:@selector(cancel)];
    //NSLog(@"tasks = %@",manager.tasks);
  

同一个请求多次请求时,短时间忽略相同的请求

当进行刷新操作时,如果在请求还没有返回之前,一直在刷新操作,不管是狂点还是乱点。那么第一个请求发出后,短时间内可以不进行重复请求。

同一个请求多次请求时,取消之前发出的请求

如果是在搜索操作,那么每次输入关键字的时候,之前发出的请求可以取消,仅仅显示最后的请求结果。 采用的方法为创建一个BaseViewModel,所有的请求操作继承BaseViewModel,在发起请求之前进行一次判断。

#pragma mark - 忽略请求

/** 忽略请求,当请求的url和参数都是一样的时候,在短时间内不发起再次请求, 默认3秒 */
- (BOOL)ignoreRequestWithUrl:(NSString *)url params:(NSDictionary *)params;

/** 忽略请求,当请求的url和参数都是一样的时候,在短时间内不发起再次请求 */
- (BOOL)ignoreRequestWithUrl:(NSString *)url params:(NSDictionary *)params timeInterval:(NSTimeInterval)timeInterval;


#pragma mark - 取消之前的请求

/** 取消之前的同一个url的网络请求
 *  在failure分支中,判断如果是取消操作,那么不做任何处理
 *  在success和failure分支中,都要调用clearTaskSessionWithUrl:方法,进行内存释放
 */
- (void)cancelLastTaskSessionWithUrl:(NSString *)url currentTaskSession:(NSURLSessionTask *)task;

/** 清除url绑定的sessionTask */
- (void)clearTaskSessionWithUrl:(NSString *)url;

 

@property (nonatomic, strong) NSMutableDictionary *requestTimeMDic;
@property (nonatomic, strong) NSMutableDictionary *cancelTaskMDic;

- (BOOL)ignoreRequestWithUrl:(NSString *)url params:(NSDictionary *)params 
    return [self ignoreRequestWithUrl:url params:params timeInterval:kRequestTimeInterval];


- (BOOL)ignoreRequestWithUrl:(NSString *)url params:(NSDictionary *)params timeInterval:(NSTimeInterval)timeInterval 
    NSString *requestStr = [NSString stringWithFormat:@"%@%@", url, [params uq_URLQueryString]];
    NSString *requestMD5 = [NSString md5:requestStr];
    NSTimeInterval nowTime = [[NSDate date] timeIntervalSince1970];
    NSNumber *lastTimeNum = [self.requestTimeMDic objectForKey:requestMD5];
    WS(weakSelf);
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeInterval * NSEC_PER_SEC)), dispatch_get_main_queue(), ^
        // 超过忽略时间后,将值清空
        [weakSelf.requestTimeMDic removeObjectForKey:requestMD5];
    );
    if (timeInterval < (nowTime - [lastTimeNum doubleValue])) 
        if (0.01 > [lastTimeNum doubleValue]) 
            [self.requestTimeMDic setObject:@(nowTime) forKey:requestMD5];
        
        return NO;
     else 
        return YES;
    


- (void)cancelLastTaskSessionWithUrl:(NSString *)url currentTaskSession:(NSURLSessionTask *)task 
    NSURLSessionTask *lastSessionTask = [self.cancelTaskMDic objectForKey:url];
    if (nil == lastSessionTask) 
        [self.cancelTaskMDic setObject:task forKey:url];
        return;
    
    [lastSessionTask cancel];


- (void)clearTaskSessionWithUrl:(NSString *)url 
    [self.cancelTaskMDic removeObjectForKey:url];

#pragma mark - Getter Methods

- (NSMutableDictionary *)requestTimeMDic 
    if (nil == _requestTimeMDic) 
        _requestTimeMDic = [[NSMutableDictionary alloc] initWithCapacity:5];
    
    return _requestTimeMDic;


- (NSMutableDictionary *)cancelTaskMDic 
    if (nil == _cancelTaskMDic) 
        _cancelTaskMDic = [[NSMutableDictionary alloc] initWithCapacity:5];
    
    return _cancelTaskMDic;

PGNetworkHelper

PGNetworkHelper - PINCache作为AFNetworking缓存层,将AFNetworking请求的数据缓存起来,支持取消当前网络请求,以及取消所有的网络请求,除了常用的Get,Post方法,也将上传图片以及下载文件进行了封装,同样支持同步请求,使用方法极其简单。

Android防止按钮快速重复点击

JAndroid防止按钮快速重复点击

问题发现

做android开发时很可能会遇到这样的场景:

一个Button响应点击后发起网络请求。由于当前网络情况较差等原因请求结果没有及时返回,这个按钮又被用户重复点击,或者用户故意快速点击,造成请求多次发起。

快速重复点击引起的网络请求多次发起,往往都不是我们希望看到的,会产生交互上的逻辑混乱,如果是涉及到提交订单这样的请求,还可能会对用户造成损失,所以这样的情况需要避免。

防止快速重复点击的方案很多。可以在服务端做校验,可以在客户端交互上做限制,也可以在客户端响应点击事件时做校验。其中,在客户端响应点击事件时做校验的方法,可以很方便在网上搜索到:

一、全局

public class Utils {
        // 两次点击按钮之间的点击间隔不能少于1000毫秒
        private static final int MIN_CLICK_DELAY_TIME = 1000;
        private static long lastClickTime;
     
        public static boolean isFastClick() {
            boolean flag = false;
            long curClickTime = System.currentTimeMillis();
            if ((curClickTime - lastClickTime) >= MIN_CLICK_DELAY_TIME) {
                flag = true;
            }
            lastClickTime = curClickTime;
            return flag;
        }
    }
btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (Utils.isFastClick()) {
                    // 进行点击事件后的逻辑操作
                }
            }

参考

1、https://zhuanlan.zhihu.com/p/34841081
2、https://www.cnblogs.com/dingxiansen/p/10442255.html

以上是关于关于多次重复网络请求问题的主要内容,如果未能解决你的问题,请参考以下文章

Android防止按钮快速重复点击

为啥会重复请求didUpdateBMKUserLocation这个方法

关于接口并发问题,webservice,等http请求

关于接口并发问题,webservice,等http请求

关于接口并发问题,webservice,等http请求

关于tcp粘包tcp协议对应用层多次请求的消息边界处理