美文网首页Android技术知识Android知识Android 技术开发
Android:告别Log,在手机上显示网络请求

Android:告别Log,在手机上显示网络请求

作者: Kisson | 来源:发表于2016-11-11 10:08 被阅读1287次

    前言

    我们会经常碰到一个很恼人的场景:当后端出现访问或数据错误导致App出现Bug时,测试人员首先会给前端开发提一个bug,这时候我们得连上手机看下网络日志,看到底是前端bug还是后端问题。如果是release版的话就更麻烦了,因为log被屏蔽,还得自己重新Run程序。
    基于此,我考虑能不能把网络请求的Request和Response直接在手机屏幕上显示,这样方便调试。

    定位网络Request和Response的地方

    绝大部分的App开发都会自己定制的一套网络请求框架,因此第一步就是定位Request的和Response具体在哪生成。
    一般而言,我们请求的传的Url都是拼接而成,BaseUrl+RequestType。不同的请求就是RequestType不同。因此我们可以把RequestType作为请求的key,来对应每个请求的Request和Response。

    另外:有些请求不是BaseUrl+RequestType的形式,而是完成的一个URL。根据入参不同来表示不同的请求,那么这种情况选key就因需求而定了。

    如下代码是我自己封装的一个类,用于保存Request的和Response,供各位参考。

    public class NetHelper {
        //max size
        private static final int MAX_SIZE = 5;
    
        private static NetHelper sInstance;
        //save net console information
        private static final List<ConsoleInfo> sConsoleList = new ArrayList<>();
    
        private OnNetConsoleListener mListener;
    
        private static final ArrayMap<String, ConsoleInfo> sConsoleArrayMap = new ArrayMap<>();
    
        private NetHelper() {
    
        }
    
        synchronized public static NetHelper getInstance() {
            if (sInstance == null) {
                sInstance = new NetHelper();
            }
            return sInstance;
        }
    
        public void setOnRequestListener(OnNetConsoleListener listener) {
            this.mListener = listener;
        }
    
    
        public synchronized void putRequest(String key, String value) {
            if (sConsoleArrayMap.containsKey(key)) {
                sConsoleArrayMap.remove(key);
            }
    
            if (sConsoleArrayMap.size() > MAX_SIZE) {
                sConsoleArrayMap.remove(sConsoleArrayMap.keyAt(0));
            }
    
            ConsoleInfo consoleInfo = new ConsoleInfo();
            CacheInfo cacheRequest = new CacheInfo();
            cacheRequest.setTime(getDayTime());
            cacheRequest.setValue(value);
            cacheRequest.setRequestType(key);
            consoleInfo.setRequestType(key);
            consoleInfo.setRequest(cacheRequest);
            sConsoleArrayMap.put(key, consoleInfo);
            if (mListener != null) {
                mListener.onNetConsole(getConsoleInfoList());
            }
        }
    
        public synchronized void putResponse(String key, String value) {
            CacheInfo cacheResponse = new CacheInfo();
            cacheResponse.setTime(getDayTime());
            cacheResponse.setValue(value);
            cacheResponse.setRequestType(key);
            if (sConsoleArrayMap.containsKey(key)) {
                sConsoleArrayMap.get(key).setResponse(cacheResponse);
            }
            if (mListener != null) {
                mListener.onNetConsole(getConsoleInfoList());
            }
        }
    
        public List<ConsoleInfo> getConsoleList() {
            return sConsoleList;
        }
    
        private synchronized List<ConsoleInfo> getConsoleInfoList() {
            sConsoleList.clear();
            sConsoleList.addAll(sConsoleArrayMap.values());
            return sConsoleList;
        }
    
        private static String getDayTime() {
            SimpleDateFormat formatter = new SimpleDateFormat("HH:mm:ss");
            Date currentTime = new Date();
            return formatter.format(currentTime);
        }
    
        public interface OnNetConsoleListener {
            void onNetConsole(List<ConsoleInfo> consoleInfoList);
        }
    
        public static class CacheInfo {
    
            String value;
    
            String time;
    
            String requestType;
    
            public String getValue() {
                return value;
            }
    
            public void setValue(String value) {
                this.value = value;
            }
    
            public String getTime() {
                return time;
            }
    
            public void setTime(String time) {
                this.time = time;
            }
    
            public String getRequestType() {
                return requestType;
            }
    
            public void setRequestType(String requestType) {
                this.requestType = requestType;
            }
        }
    
    
        public static class ConsoleInfo {
    
            private String requestType;
    
            private CacheInfo request;
    
            private CacheInfo response;
    
            public CacheInfo getResponse() {
                return response;
            }
    
            public void setResponse(CacheInfo response) {
                this.response = response;
            }
    
            public CacheInfo getRequest() {
                return request;
            }
    
            public void setRequest(CacheInfo request) {
                this.request = request;
            }
    
            public String getRequestType() {
                return requestType;
            }
    
            public void setRequestType(String requestType) {
                this.requestType = requestType;
            }
        }
    }
    

    App上显示网络请求

    App上要显示网络请求,肯定是要用一个独立且常驻的Window来显示,即与Activity的生命周期无关。

    系统类型的Window就满足条件(Window相关的概念我准备在后续章节进行详细说明,绝对干货)。我们能够使用的系统类型Window常见有两种,分别是TYPE_TOAST、TYPE_SYSTEM_ALERT,但是TYPE_SYSTEM_ALERT类型Window需要权限(某些国产ROM不仅仅需要在manifest中声明权限,同时需要用户允许悬浮框权限)。到这基本上确定TYPE_TOAST类型Window就是最优解。

        public void generateTypeToast() {
            WindowManager wm = (WindowManager) getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
            final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
            params.type = WindowManager.LayoutParams.TYPE_TOAST;
            params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
            params.width = WindowManager.LayoutParams.MATCH_PARENT;
            params.height = WindowManager.LayoutParams.WRAP_CONTENT;
            params.gravity = Gravity.CENTER;
            final View view = LayoutInflater.from(this).inflate(R.layout.window_toast, null);
            wm.addView(view, params);
        }
    

    以上代码是创建TYPE_TOAST类型Window。
    本来这一切都是美好的,但是测试的发现,小米手机的TYPE_TOAST类型Window也需要用户允许悬浮框。
    后来搜索资料的时候找到Android中通过反射来设置Toast的显示时间这篇文章,我们可以通过改变Toast时间来达到常驻Window的目的。
    本来这一切都是美好的,但后来测试发现,又TM是小米,在MIUI8中,对“反射改变Toast显示时间方案”进行了限制。在该方案中,Window是能正常显示,但是Window的位置不能再改变(即我们不能移动Window,只能固定在某个位置),否则会报错“java.lang.IllegalArgumentException: Window type can not be changed after the window is added.”。这个错误报的莫名其妙!

    当然我们也可以对miui8进行特殊处理,其他ROM手机按照反射toast方案来解决。但是毕竟反射的方案不稳定,以防万一,最终我采取了常规方案。毕竟这只是开发工具,并不需要所有手机都兼容,当需要使用该工具的时候,提示下需要申请对应的权限就行。
    最后,我们可以创建service来显示网络请求输出工具。因为系统类型的Window的生命周期是和应用程序进程生命相关,所以为了更友好的交互,当应用程序进入后台的时候,我们应该关闭网络请求输出Window,程序切换到前台的时候,再次显示。

    最后

    放几张图片给大家看看在项目中应用的效果。


    网络请求输出工具1 网络请求输出工具2 网络请求输出工具3

    如果您觉得有用,请点个赞吧。

    相关文章

      网友评论

        本文标题:Android:告别Log,在手机上显示网络请求

        本文链接:https://www.haomeiwen.com/subject/kfsxpttx.html