2019-12-27
关键字:监听多层网络结果、监听网络结果回调
在做网络通信的时候,很重要的一点就是要“等待通信结果”。
在很多场景下都要求用户在发起一个通信请求后,必须等待服务器返回的结果才能进行下一步的操作。通常我们都会在发起通信请求后弹出一个弹窗来强制让用户等待,直至拿到通信结果才将这个等待弹窗隐藏掉。这种单层单个的网络通信要监听它的通信结果倒还好做。但难免我们会遇到那种多层级的网络通信,如下示意图:
如上图所示,每一个节点都是一个网络请求。它要求,在A请求有结果时立即发起A1通信与A2通信。在A1有结果或A1发起后尚未有结果时再立即发起A11与A12通信。与此同时,还有一个B通信与A通信同步发起。而我们的用户必须要等到这 8 个网络通信全部返回结果以后才能进行下一步的操作。而又由于网络通信取决于你的网络环境完全是异步的,我们无法保证哪一层的哪一个通信先返回结果。
这是不是就有点难度了?
延时吗?别开玩笑了,最low的做法。
另外写个子线程一直循环扫描每个通信的结果吗?行是行,但考虑到比上图模型更为复杂的情况,代价是不是有点大了?
像这种场景,最优雅的方式就是让各个通信节点“主动”将结果告诉通信发起方。什么是主动呢?说白了就是“回调”。我们每发起一个网络通信,就注册一个结果回调方法,当这个通信完成后,通过回调方法来告知结果。因为发起方知道自己到底发了多少个通信请求,此时发起方再逐个检查自己所有的请求是否都完成了。若是,则通信结束。若否,则设置相应标志位后继续等待。
笔者将这种监听模型称作“嵌套型网络通信结果监听器”。
它存在的意义就是同样以嵌套型的方式来管理每一个网络通信状态及其结果回调。
这个监听器主要由两个模块构成:
1、管理类;
2、核心类;
管理类是与用户,或者说是通信发起方交互的模块。所有网络通信都要向管理类来注册状态监听。简单来说,管理类就是用来管理所有网络通信状态的模块。
核心类就是一个网络通信的抽象。每发起一个新的网络通信,都应该实例化一个核心类对象,用于跟踪这个通信状态。同时,为了实现“嵌套”,核心类中还应有一个核心类的集合变量。即这个核心类也是一个“单向链表类”,它内部持有指向另一个核心类的引用。如此便可实现“嵌套”的目的。
监听器的模型大致如下图所示:
多说无益,直接贴上源码,有需要的同学可以根据自己的实际需求更改或直接拿来使用:
package com.demo.communication; import android.os.CountDownTimer; import com.demo.util.Logger; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; /** * 嵌套型的网络通信过程结果监听。 * chorm at 2019-11-06 16:10 * */ public class NestedInetComListenerManager { //todo 必须再添加一个层级检测机制。层级机制很有必要,可以避免因时序不对导致监控数量不对的问题。 private static final String TAG = "NestedInetComListenerManager"; public static final String RESULT_DOING = "1#"; public static final String RESULT_ERROR = "2#"; public static final String RESULT_SUCCES = "3#"; private static final int DEFAULT_TIMEOUT = 20; // 20s. public static final int RESULT_SUCCESS = 0; public static final int RESULT_FAILURE = 1; public static final int RESULT_TIMEOUT = 2; private Map<String, Listener> chains; //第一层 private List<Listener.Coordinate> coordinates; private OnNestedResultCallback callback; public NestedInetComListenerManager(OnNestedResultCallback callback){ this(callback, DEFAULT_TIMEOUT); } /** * @param timeout 超时时长,单位:秒。 * */ public NestedInetComListenerManager(OnNestedResultCallback callback, long timeout){ chains = new HashMap<>(); coordinates = new ArrayList<>(); this.callback = callback; new CountDownTimer(timeout * 1000, 1000){ @Override public void onTick(long millisUntilFinished) { } @Override public void onFinish() { Logger.d(TAG, "Communication procedure time out"); notifyResult(RESULT_TIMEOUT); } }.start(); } /** * 往第一层添加监听。 * */ public Listener.Coordinate add(String uniqueName){ Logger.v(TAG, "add():" + uniqueName); Listener listener = new Listener(); Listener.Coordinate coordinate; if(coordinates.size() == 0) { coordinate = new Listener.Coordinate(0, chains.size()); }else{ coordinate = new Listener.Coordinate(coordinates.get(coordinates.size() - 1).depth, chains.size()); } listener.coordinate = coordinate; chains.put(uniqueName, listener); coordinates.add(coordinate); return coordinate; } /** * 从第一层取出监听。 * */ public Listener get(String name){ if(name == null) { Logger.d(TAG, "get listener failed cause name is null"); return null; } if(chains.containsKey(name)) { return chains.get(name); } return null; } /** * 遍历所有监听,检查是否回调结束。 * */ public void check() { Logger.v(TAG, "check()"); Logger.d(TAG, "-------------------------------------------------"); Logger.d(TAG, "----------------- traversal ----------------------"); Logger.d(TAG, "-------------------------------------------------"); traverse(); Set<String> keys = chains.keySet(); boolean ret = true; for(String name:keys){ Listener l = chains.get(name); ret = l.check(); if(ret) { }else{ //只要有一个通信没完成,那整个通信过程就没有完成。 Logger.d(TAG, "Not finish yet"); break; } } if(ret) { Logger.i(TAG, "All internet communication finished"); notifyResult(RESULT_SUCCESS); } } public void traverse() { Set<String> keys = chains.keySet();//这是第一层的keyset,肯定不为空。 for(String key:keys){ Logger.d(TAG, "++key:" + key + ",msg:" + chains.get(key).msg); kkk(chains.get(key), 2); } } private void kkk(Listener l, int layer){ if(l.chains != null) { Set<String> keys = l.chains.keySet(); StringBuilder sb = new StringBuilder(); for(int i = 0; i < layer; i++){ sb.append("++"); } for(String key:keys){ Logger.d(TAG, sb.toString() + "key:" + key + ",msg:" + l.chains.get(key).msg); kkk(l.chains.get(key), layer + 1); } } } private void notifyResult(int result){ if(callback != null) { callback.onNestedResult(result); callback = null; } } public interface OnNestedResultCallback { void onNestedResult(int resultType); } /** * 监听嵌套型网络通信。 * */ public static class Listener { private static final String TAG = "NestedMan.Listener"; public Coordinate coordinate; /** * format: type#errno:msginfo * eg: RESULT_ERROR#3:username incorrect * */ public String msg; private Map<String, Listener> chains; // 第 2 ~ n 层 public Listener(){ } public Coordinate add(String name){ Logger.v(TAG, "add(),name:" + name); if(chains == null) { chains = new HashMap<>(); } Listener l2 = new Listener(); Coordinate c = new Coordinate(coordinate.depth + 1, chains.size()); l2.coordinate = c; chains.put(name, l2); return c; } /** * 取子层级中的监听器。 * */ public Listener get(String name){ if(name == null) { return null; } if(chains.containsKey(name)) { return chains.get(name); } return null; } /** * 检查 msg 中的内容。 * 当网络通信从发出请求到接收到服务器的响应才算是完成了网络通信过程。 * @return true 当前网络通信已完成。 false 当前网络通信尚未完成。 * */ public boolean check() { Logger.d(TAG, "check()"); /* * 先检查自己的 msg,如果自己的 msg 显示完成了,则检查其子链的msg。 * */ if(msg == null) { return false; } if(msg.startsWith(NestedInetComListenerManager.RESULT_DOING)) { return false; } if(chains != null) { Set<String> keys = chains.keySet(); for(String name:keys){ Listener l = chains.get(name); if(!l.check()){ return false; } } } return true; } public void finish(String msg) { Logger.v(TAG, "finish,msg:" + msg); this.msg = msg; } /** * 暂时无用。2019-12-27 * */ public static class Coordinate{ public int depth; public int number; public Coordinate(int d, int n){ Logger.v(TAG, "new Coordinate,depth:" + d + ",number:" + n); depth = d; number = n; } } } }
以下是一个使用示例,该示例展示的是一个“一层三级”嵌套型网络通信结果回调。当下列代码末尾的 onNestedResult() 方法被调用时,则表示该一层三级嵌套通信已经全部通信完成。
package com.demo.communication; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.zhy.http.okhttp.OkHttpUtils; public class Initialization implements NestedInetComListenerManager.OnNestedResultCallback { private static final String TAG = "Initialization"; private static final String NAME_GET_TRANSPORT_TYPE = "tt:"; private static final String NAME_GET_IOT_LIST = "it:"; private static final String NAME_GET_GOODS_TYPE = "gt:"; private NestedInetComListenerManager nestedInetComListenerManager; public Initialization(){ nestedInetComListenerManager = new NestedInetComListenerManager(this); getTransportType(); } private void getTransportType(){ Logger.v(TAG, "getTransportType"); OkHttpUtils .post() .url(Server.URL_INITIALIZATION_GET_TRANSPORT_TYPE) .execute(new JSONHttpCallback() { @Override protected void onError(Exception e, int id, int errno) { nestedInetComListenerManager .get(NAME_GET_TRANSPORT_TYPE) .finish(NestedInetComListenerManager.RESULT_ERROR + errno + ":null"); nestedInetComListenerManager.check(); } @Override protected void onResponse(JSONObject jbody) { getIotList(tt); nestedInetComListenerManager .get(NAME_GET_TRANSPORT_TYPE) .finish(NestedInetComListenerManager.RESULT_SUCCES + "0:null"); nestedInetComListenerManager.check(); } }); //register a internet communication listener. nestedInetComListenerManager.add(NAME_GET_TRANSPORT_TYPE); //layer1 } /** * 即界面中的 “221/车(EK)”。 * */ private void getIotList(final TransportType tt){ Logger.v(TAG, "getIotList,name:" + tt.getName()); OkHttpUtils .post() .url(Server.URL_INITIALIZATION_GET_IOT_LIST) .execute(new JSONHttpCallback() { @Override protected void onError(Exception e, int id, int errno) { nestedInetComListenerManager .get(NAME_GET_TRANSPORT_TYPE) .get(NAME_GET_IOT_LIST + tt.getName() + tt.hashCode()) .finish(NestedInetComListenerManager.RESULT_ERROR + errno + ":onError"); nestedInetComListenerManager.check(); } @Override protected void onResponse(JSONObject jbody) { getGoodsType(NAME_GET_IOT_LIST + tt.getName() + tt.hashCode(), carCode); nestedInetComListenerManager .get(NAME_GET_TRANSPORT_TYPE) .get(NAME_GET_IOT_LIST + tt.getName() + tt.hashCode()) .finish(NestedInetComListenerManager.RESULT_SUCCES + "0:success"); nestedInetComListenerManager.check(); } }); nestedInetComListenerManager.get(NAME_GET_TRANSPORT_TYPE).add(NAME_GET_IOT_LIST + tt.getName() + tt.hashCode()); //layer2 } // getIotList() -- end private void getGoodsType(final String parentName, final CarCode carCode){ OkHttpUtils .post() .url(Server.URL_INITIALIZATION_GET_GOODS_TYPE) .execute(new JSONHttpCallback() { @Override protected void onError(Exception e, int id, int errno) { nestedInetComListenerManager.get(NAME_GET_TRANSPORT_TYPE) .get(parentName) .get(NAME_GET_GOODS_TYPE + carCode.getIotName() + carCode.hashCode()) .finish(NestedInetComListenerManager.RESULT_ERROR + "-1:null"); nestedInetComListenerManager.check(); } @Override protected void onResponse(JSONObject jbody) { nestedInetComListenerManager .get(NAME_GET_TRANSPORT_TYPE) //layer1 .get(parentName) //layer2 .get(NAME_GET_GOODS_TYPE + carCode.getIotName() + carCode.hashCode()) //layer3 .finish(NestedInetComListenerManager.RESULT_SUCCES + "0:null"); nestedInetComListenerManager.check(); } }); nestedInetComListenerManager .get(NAME_GET_TRANSPORT_TYPE) .get(parentName) .add(NAME_GET_GOODS_TYPE + carCode.getIotName() + carCode.hashCode()); } @Override public void onNestedResult(int rt) { Logger.v(TAG, "onNestedResult,result type:" + rt); } }
当然,其实笔者这个“嵌套型网络通信结果监听器”仍旧是不完善的,在某些场景下可能会导致监控不准确。例如在代码上部笔者以 TODO 标记出来的场景。但总得来说,它已经能够应付大多数场景了,就笔者的实际使用来看,监控准确度还是很高的。至于那些问题,笔者暂时也没有精力去修复,就先让它以 TODO 的形式记录在那儿吧。