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

前言

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

private OnNetConsoleListener mListener;

private static final ArrayMap 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 getConsoleList() {

return sConsoleList;

}

private synchronized List 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 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,程序切换到前台的时候,再次显示。

最后

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

2e5f8df411c8

网络请求输出工具1

2e5f8df411c8

网络请求输出工具2

2e5f8df411c8

网络请求输出工具3

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值