Android技术知识Android知识Android 技术开发

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

2016-11-11  本文已影响1287人  Kisson

前言

我们会经常碰到一个很恼人的场景:当后端出现访问或数据错误导致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

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

上一篇下一篇

猜你喜欢

热点阅读