Android開發(fā)工具之OKHttp日志抓?。ǚ翴OS DBDebugToolkit)
聲明
本文章基于OkHttp基礎進行開發(fā),主要是仿IOS調試工具DBDebugToolkit的網(wǎng)絡日志抓取功能,原理十分簡單,對OkHttp設置攔截器,在攔截器中攔截請求數(shù)據(jù)和返回數(shù)據(jù),然后存儲到本地文件中,最后再從本地文件中讀取相應的信息展示,方便開發(fā)人員和測試人員進行網(wǎng)絡接口調試。
Android截圖在最下方?。?!
IOS DBDebugToolkit調試工具
IOS DBDebugToolkit git地址:https://github.com/dbukowski/DBDebugToolkit
本文github地址:https://github.com/coffeelife/HttpLogCode.git
截圖 (調試頁面)


截圖 (詳情頁面)





截圖結束
開篇
截圖結束,以上截圖就是我們想要實現(xiàn)的功能,這是IOS的調試工具,Android并沒有類似的開源工具,所以我們來實現(xiàn)一個類似的這樣工具,方便測試或者后端甚至自己來查看網(wǎng)絡請求情況。(Ps:避免測試或者開發(fā)過程中,總有人找你,說這里我返回的數(shù)據(jù)不是這樣的,測試來找你說這個狀態(tài)不應該這么顯示,你就可以直接告訴他們,你看看請求日志,看看數(shù)據(jù)是怎么樣的)。
原理
最開始也講了,這個東西并不復雜,本項目是通過OkHttp作為請求基礎,OkHttp可以設置攔截器,這個攔截器就是實現(xiàn)該功能的方式。原理:我們通過給我們App的OkHttp請求設置攔截器,攔截發(fā)出去的請求和返回的請求數(shù)據(jù),將結果打印在log里面,方便開發(fā)過程中調試,并將結果保存在一個bean對象中,保存到文件當中(我為了方便保存在了sp中),然后寫一個列表頁面將自己保存的網(wǎng)絡請求數(shù)據(jù)list顯示到頁面中。下面上代碼,最后上截圖。
代碼編寫
建立網(wǎng)絡攔截器
HttpLogInterceptor.java(全部)
/**
* Created by ccx on 2018/07/11
*/
public class HttpLogInterceptor implements Interceptor {
private static final Charset UTF8 = Charset.forName("UTF-8");
public enum Level {
/**
* No logs.
*/
NONE,
/**
* Logs request and response lines.
*
* Example:
* {@code
* --> POST /greeting http/1.1 (3-byte body)
*
* <-- 200 OK (22ms, 6-byte body)
* }
*/
BASIC,
/**
* Logs request and response lines and their respective headers.
*
* Example:
* {@code
* --> POST /greeting http/1.1
* Host: example.com
* Content-Type: plain/text
* Content-Length: 3
* --> END POST
*
* <-- 200 OK (22ms)
* Content-Type: plain/text
* Content-Length: 6
* <-- END HTTP
* }
*/
HEADERS,
/**
* Logs request and response lines and their respective headers and bodies (if present).
*
* Example:
* {@code
* --> POST /greeting http/1.1
* Host: example.com
* Content-Type: plain/text
* Content-Length: 3
*
* Hi?
* --> END POST
*
* <-- 200 OK (22ms)
* Content-Type: plain/text
* Content-Length: 6
*
* Hello!
* <-- END HTTP
* }
*/
BODY
}
public interface Logger {
void log(String message);
void log(String tag, String message);
void json(String tag, String message);
/**
* A {@link HttpLogInterceptor.Logger} defaults output appropriate for the current platform.
*/
HttpLogInterceptor.Logger DEFAULT = new HttpLogInterceptor.Logger() {
@Override
public void log(String message) {
com.gjhealth.library.utils.log.Logger.t("http").d(message);
}
@Override
public void log(String tag, String message) {
com.gjhealth.library.utils.log.Logger.t("http" + tag).d(message);
}
@Override
public void json(String tag, String message) {
com.gjhealth.library.utils.log.Logger.t(tag).json(message);
}
};
}
public HttpLogInterceptor() {
this(HttpLogInterceptor.Logger.DEFAULT);
}
public HttpLogInterceptor(HttpLogInterceptor.Logger logger) {
this.logger = logger;
}
private final HttpLogInterceptor.Logger logger;
private volatile HttpLogInterceptor.Level level = HttpLogInterceptor.Level.NONE;
/**
* Change the level at which this interceptor logs.
*/
public HttpLogInterceptor setLevel(HttpLogInterceptor.Level level) {
if (level == null) throw new NullPointerException("level == null. Use Level.NONE instead.");
this.level = level;
return this;
}
public HttpLogInterceptor.Level getLevel() {
return level;
}
@Override
public Response intercept(Chain chain) throws IOException {
HttpLogInterceptor.Level level = this.level;
long startNs = System.nanoTime();
Request request = chain.request();
HttpCacheBean bean = new HttpCacheBean();
bean.requestTime = DateFormatUtils.getCurrentDateTime();
if (level == HttpLogInterceptor.Level.NONE) {
return chain.proceed(request);
}
boolean logBody = level == HttpLogInterceptor.Level.BODY;
boolean logHeaders = logBody || level == HttpLogInterceptor.Level.HEADERS;
RequestBody requestBody = request.body();
boolean hasRequestBody = requestBody != null;
Connection connection = chain.connection();
Protocol protocol = connection != null ? connection.protocol() : Protocol.HTTP_1_1;
String requestStartMessage = "--> " + request.method() + ' ' + request.url() + ' ' + protocol;
bean.method = request.method();
bean.url = request.url().toString();
bean.protocol = protocol + "";
if (!logHeaders && hasRequestBody) {
requestStartMessage += " (" + requestBody.contentLength() + "-byte body)";
bean.reqContentLength = requestStartMessage;
}
logger.log(requestStartMessage);
bean.requestMsg = requestStartMessage;
if (logHeaders) {
// if (hasRequestBody) {
// // Request body headers are only present when installed as a network interceptor. Force
// // them to be included (when available) so there values are known.
// if (requestBody.contentType() != null) {
// logger.log("Content-Type: " + requestBody.contentType());
// }
// if (requestBody.contentLength() != -1) {
// logger.log("Content-Length: " + requestBody.contentLength());
// }
// }
Headers headers = request.headers();
// StringBuffer sbReq = new StringBuffer();
if (headers.size() > 0) {
JsonObject jsonHeaders = new JsonObject();
for (int i = 0, count = headers.size(); i < count; i++) {
String value = headers.value(i);
if (value != null) {
value = URLDecoder.decode(value, "UTF-8");
}
jsonHeaders.addProperty(headers.name(i), value);
// sbReq.append(headers.name(i) + ":" + value + "\n");
}
logger.json("httphead-req", jsonHeaders.toString());
bean.reqHeaders = jsonHeaders.toString();
}
if (!logBody || !hasRequestBody) {
// logger.log("--> END " + request.method());
} else if (bodyEncoded(request.headers())) {
// logger.log("--> END " + request.method() + " (encoded body omitted)");
} else {
Buffer buffer = new Buffer();
requestBody.writeTo(buffer);
Charset charset = UTF8;
MediaType contentType = requestBody.contentType();
if (contentType != null) {
charset = contentType.charset(UTF8);
}
if (isPlaintext(buffer)) {
logger.json("httprequest", buffer.clone().readString(charset));
bean.requestBody = buffer.readString(charset);
} else {
logger.log("--> END " + request.method() + " (binary "
+ requestBody.contentLength() + "-byte body omitted)");
}
bean.reqContentType = contentType.toString();
}
}
Response response;
try {
response = chain.proceed(request);
} catch (Exception e) {
logger.log("<-- HTTP FAILED: " + e);
bean.error = e.toString();
throw e;
}
long tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs);
ResponseBody responseBody = response.body();
long contentLength = responseBody.contentLength();
String bodySize = contentLength != -1 ? contentLength + "-byte" : "unknown-length";
logger.log("<-- " + response.code() + ' ' + response.message() + ' '
+ response.request().url() + " (" + tookMs + "ms" + (!logHeaders ? ", "
+ bodySize + " body" : "") + ')');
bean.responseMsg = "<-- " + response.code() + ' ' + response.message() + ' '
+ response.request().url() + " (" + tookMs + "ms" + (!logHeaders ? ", "
+ bodySize + " body" : "") + ')';
bean.resContentLength = bodySize;
bean.code = response.code();
bean.msg = response.message();
bean.time = tookMs + "ms";
if (logHeaders) {
// StringBuffer sbRes = new StringBuffer();
Headers headers = response.headers();
JsonObject jsonHeaders = new JsonObject();
for (int i = 0, count = headers.size(); i < count; i++) {
String value = headers.value(i);
if (value != null) {
value = URLDecoder.decode(value, "UTF-8");
}
jsonHeaders.addProperty(headers.name(i), value);
// sbRes.append(headers.name(i) + ":" + value + "\n");
// logger.log(headers.name(i) + ": " + headers.value(i));
}
logger.json("httphead-rsp", jsonHeaders.toString());
bean.resHeaders = jsonHeaders.toString();
if (!logBody || !HttpHeaders.hasBody(response)) {
} else if (bodyEncoded(response.headers())) {
logger.log("<-- END HTTP (encoded body omitted)");
} else if (isPlaintext(responseBody.contentType())) {
BufferedSource source = responseBody.source();
source.request(Long.MAX_VALUE); // Buffer the entire body.
Buffer buffer = source.buffer();
Charset charset = UTF8;
MediaType contentType = responseBody.contentType();
if (contentType != null) {
try {
charset = contentType.charset(UTF8);
} catch (UnsupportedCharsetException e) {
logger.log("");
logger.log("Couldn't decode the response body; charset is likely malformed.");
logger.log("<-- END HTTP");
return response;
}
}
bean.resContentType = contentType.toString();
// if (!isPlaintext(buffer)) {
// logger.log("<-- END HTTP (binary " + buffer.size() + "-byte body omitted)");
// return response;
// }
if (contentLength != 0) {
// logger.log("");
logger.log("body", buffer.clone().readString(charset));
bean.responseBody = buffer.clone().readString(charset);
}
logger.log("<-- END HTTP (" + buffer.size() + "-byte body)");
}
}
HttpLogManager.saveLogBean(bean);
return response;
}
private static boolean isPlaintext(MediaType mediaType) {
if (mediaType == null) return false;
if (mediaType.type() != null && mediaType.type().equals("text")) {
return true;
}
String subtype = mediaType.subtype();
if (subtype != null) {
subtype = subtype.toLowerCase();
if (subtype.contains("x-www-form-urlencoded") || subtype.contains("json") || subtype.contains("xml") || subtype.contains("html")) //
return true;
}
return false;
}
/**
* Returns true if the body in question probably contains human readable text. Uses a small sample
* of code points to detect unicode control characters commonly used in binary file signatures.
*/
static boolean isPlaintext(Buffer buffer) {
try {
Buffer prefix = new Buffer();
long byteCount = buffer.size() < 64 ? buffer.size() : 64;
buffer.copyTo(prefix, 0, byteCount);
for (int i = 0; i < 16; i++) {
if (prefix.exhausted()) {
break;
}
int codePoint = prefix.readUtf8CodePoint();
if (Character.isISOControl(codePoint) && !Character.isWhitespace(codePoint)) {
return false;
}
}
return true;
} catch (EOFException e) {
return false; // Truncated UTF-8 sequence.
}
}
private boolean bodyEncoded(Headers headers) {
String contentEncoding = headers.get("Content-Encoding");
return contentEncoding != null && !contentEncoding.equalsIgnoreCase("identity");
}
}
內部枚舉類Level
public enum Level {
/**
* No logs.
*/
NONE,
/**
* Logs request and response lines.
*
* Example:
* {@code
* --> POST /greeting http/1.1 (3-byte body)
*
* <-- 200 OK (22ms, 6-byte body)
* }
*/
BASIC,
/**
* Logs request and response lines and their respective headers.
*
* Example:
* {@code
* --> POST /greeting http/1.1
* Host: example.com
* Content-Type: plain/text
* Content-Length: 3
* --> END POST
*
* <-- 200 OK (22ms)
* Content-Type: plain/text
* Content-Length: 6
* <-- END HTTP
* }
*/
HEADERS,
/**
* Logs request and response lines and their respective headers and bodies (if present).
*
* Example:
* {@code
* --> POST /greeting http/1.1
* Host: example.com
* Content-Type: plain/text
* Content-Length: 3
*
* Hi?
* --> END POST
*
* <-- 200 OK (22ms)
* Content-Type: plain/text
* Content-Length: 6
*
* Hello!
* <-- END HTTP
* }
*/
BODY
}
//攔截代碼內部片段 Level為NONE,不進行日志打印
if (level == HttpLogInterceptor.Level.NONE) {
return chain.proceed(request);
}
代碼中有一個枚舉類型Level,這個類用來標注log和網(wǎng)絡請求記錄類型,用來區(qū)分打包類型對應不同類型的Log打印和是否記錄網(wǎng)絡請求日志,當打debug和beta包時,可以打印log和記錄網(wǎng)絡請求,當打release包時,因為是線上,就將log和網(wǎng)絡請求日志功能關閉。
在Okhttp中添加攔截器HttpLogInterceptor
OkHttpClient.Builder builder = new OkHttpClient.Builder();
//log相關
HttpLogInterceptor loggingInterceptor = new HttpLogInterceptor().setLevel(
!GlobalEnv.isRelease() ? HttpLogInterceptor.Level.BODY : HttpLogInterceptor.Level.NONE);
//添加OkGo默認debug日志
builder.addInterceptor(loggingInterceptor);
代碼講解:新建okhttp builder,然后新建HttpLogInterceptor攔截器,判斷app包類型,如果是release包就關閉網(wǎng)絡請求日志功能,最后給OkHttp添加攔截器
核心代碼部分(日志收集)
@Override
public Response intercept(Chain chain) throws IOException {
HttpLogInterceptor.Level level = this.level;
long startNs = System.nanoTime();
Request request = chain.request();
HttpCacheBean bean = new HttpCacheBean();
bean.requestTime = DateFormatUtils.getCurrentDateTime();
if (level == HttpLogInterceptor.Level.NONE) {
return chain.proceed(request);
}
boolean logBody = level == HttpLogInterceptor.Level.BODY;
boolean logHeaders = logBody || level == HttpLogInterceptor.Level.HEADERS;
RequestBody requestBody = request.body();
boolean hasRequestBody = requestBody != null;
Connection connection = chain.connection();
Protocol protocol = connection != null ? connection.protocol() : Protocol.HTTP_1_1;
String requestStartMessage = "--> " + request.method() + ' ' + request.url() + ' ' + protocol;
bean.method = request.method();
bean.url = request.url().toString();
bean.protocol = protocol + "";
if (!logHeaders && hasRequestBody) {
requestStartMessage += " (" + requestBody.contentLength() + "-byte body)";
bean.reqContentLength = requestStartMessage;
}
logger.log(requestStartMessage);
bean.requestMsg = requestStartMessage;
if (logHeaders) {
// if (hasRequestBody) {
// // Request body headers are only present when installed as a network interceptor. Force
// // them to be included (when available) so there values are known.
// if (requestBody.contentType() != null) {
// logger.log("Content-Type: " + requestBody.contentType());
// }
// if (requestBody.contentLength() != -1) {
// logger.log("Content-Length: " + requestBody.contentLength());
// }
// }
Headers headers = request.headers();
// StringBuffer sbReq = new StringBuffer();
if (headers.size() > 0) {
JsonObject jsonHeaders = new JsonObject();
for (int i = 0, count = headers.size(); i < count; i++) {
String value = headers.value(i);
if (value != null) {
value = URLDecoder.decode(value, "UTF-8");
}
jsonHeaders.addProperty(headers.name(i), value);
// sbReq.append(headers.name(i) + ":" + value + "\n");
}
logger.json("httphead-req", jsonHeaders.toString());
bean.reqHeaders = jsonHeaders.toString();
}
if (!logBody || !hasRequestBody) {
// logger.log("--> END " + request.method());
} else if (bodyEncoded(request.headers())) {
// logger.log("--> END " + request.method() + " (encoded body omitted)");
} else {
Buffer buffer = new Buffer();
requestBody.writeTo(buffer);
Charset charset = UTF8;
MediaType contentType = requestBody.contentType();
if (contentType != null) {
charset = contentType.charset(UTF8);
}
if (isPlaintext(buffer)) {
logger.json("httprequest", buffer.clone().readString(charset));
bean.requestBody = buffer.readString(charset);
} else {
logger.log("--> END " + request.method() + " (binary "
+ requestBody.contentLength() + "-byte body omitted)");
}
bean.reqContentType = contentType.toString();
}
}
Response response;
try {
response = chain.proceed(request);
} catch (Exception e) {
logger.log("<-- HTTP FAILED: " + e);
bean.error = e.toString();
throw e;
}
long tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs);
ResponseBody responseBody = response.body();
long contentLength = responseBody.contentLength();
String bodySize = contentLength != -1 ? contentLength + "-byte" : "unknown-length";
logger.log("<-- " + response.code() + ' ' + response.message() + ' '
+ response.request().url() + " (" + tookMs + "ms" + (!logHeaders ? ", "
+ bodySize + " body" : "") + ')');
bean.responseMsg = "<-- " + response.code() + ' ' + response.message() + ' '
+ response.request().url() + " (" + tookMs + "ms" + (!logHeaders ? ", "
+ bodySize + " body" : "") + ')';
bean.resContentLength = bodySize;
bean.code = response.code();
bean.msg = response.message();
bean.time = tookMs + "ms";
if (logHeaders) {
// StringBuffer sbRes = new StringBuffer();
Headers headers = response.headers();
JsonObject jsonHeaders = new JsonObject();
for (int i = 0, count = headers.size(); i < count; i++) {
String value = headers.value(i);
if (value != null) {
value = URLDecoder.decode(value, "UTF-8");
}
jsonHeaders.addProperty(headers.name(i), value);
// sbRes.append(headers.name(i) + ":" + value + "\n");
// logger.log(headers.name(i) + ": " + headers.value(i));
}
logger.json("httphead-rsp", jsonHeaders.toString());
bean.resHeaders = jsonHeaders.toString();
if (!logBody || !HttpHeaders.hasBody(response)) {
} else if (bodyEncoded(response.headers())) {
logger.log("<-- END HTTP (encoded body omitted)");
} else if (isPlaintext(responseBody.contentType())) {
BufferedSource source = responseBody.source();
source.request(Long.MAX_VALUE); // Buffer the entire body.
Buffer buffer = source.buffer();
Charset charset = UTF8;
MediaType contentType = responseBody.contentType();
if (contentType != null) {
try {
charset = contentType.charset(UTF8);
} catch (UnsupportedCharsetException e) {
logger.log("");
logger.log("Couldn't decode the response body; charset is likely malformed.");
logger.log("<-- END HTTP");
return response;
}
}
bean.resContentType = contentType.toString();
// if (!isPlaintext(buffer)) {
// logger.log("<-- END HTTP (binary " + buffer.size() + "-byte body omitted)");
// return response;
// }
if (contentLength != 0) {
// logger.log("");
logger.log("body", buffer.clone().readString(charset));
bean.responseBody = buffer.clone().readString(charset);
}
logger.log("<-- END HTTP (" + buffer.size() + "-byte body)");
}
}
HttpLogManager.saveLogBean(bean);
return response;
}
此部分代碼是攔截器攔截到網(wǎng)絡請求之后的對數(shù)據(jù)進行日志收集的代碼,主要是新建一個Log日志Bean對象(HttpCacheBean),通過Request request = chain.request();得到請求詳情,并賦值給log日志bean 對象,主要包括請求url、發(fā)送請求時間time(通過得到當前時間)、請求頭reqHeaders、請求body數(shù)據(jù)requestBody、請求類型method、請求協(xié)議protocol等信息。
返回數(shù)據(jù)主要通過response = chain.proceed(request);得到返回對象之后,依舊復制給log日志bean 對象,主要包括計算的請求時長requestTime、請求狀態(tài)碼code(200、400、401、500等)、請求錯誤error、返回內容長度reqContentLength、請求返回msg responseMsg、返回body數(shù)據(jù)responseBody等。
HttpCacheBean.java
public class HttpCacheBean implements Serializable {
public static String TAG = HttpCacheBean.class.getSimpleName();
public String url;
public String requestTime;
public int code;
public String time;
public String reqHeaders;
public String resHeaders;
public String requestBody;
public String protocol;
public String method;
public String msg;
public String responseBody;
public String reqContentType;
public String resContentType;
public String error;
public String reqContentLength;
public String resContentLength;
public String requestMsg;
public String responseMsg;
}
最后,將bean 對象保存到sp中,我這里面寫了一個manager類,來保存和讀取該文件
HttpLogManager.saveLogBean(bean);
HttpManager.java
package cn.api.gjhealth.cstore.http;
import android.text.TextUtils;
import com.gjhealth.library.http.model.HttpCacheBean;
import com.gjhealth.library.utils.ArrayUtils;
import com.gjhealth.library.utils.SharedUtil;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import cn.api.gjhealth.cstore.app.BaseApp;
import cn.api.gjhealth.cstore.utils.jsonutils.GsonUtil;
public class HttpLogManager {
public static List getLogLists(String keyWords) {
List listBeans = null;
String json = SharedUtil.instance(BaseApp.getContext()).getString(HttpCacheBean.TAG);
if (GsonUtil.isGoodJson(json)) listBeans = GsonUtil.getEntityList(json, HttpCacheBean.class);
if (GsonUtil.isBadJson(json) || listBeans == null) listBeans = new ArrayList<>();
if (TextUtils.isEmpty(keyWords)) {
Collections.reverse(listBeans);
return listBeans;
} else {
List listBeansTemp = new ArrayList<>();
if (!ArrayUtils.isEmpty(listBeans)) {
for (HttpCacheBean bean : listBeans) {
if (bean != null && !TextUtils.isEmpty(bean.url)) {
try {
URL url = new URL(URLDecoder.decode(bean.url));
if (url.getPath().contains(keyWords)) {
listBeansTemp.add(bean);
}
} catch (MalformedURLException e) {
}
}
}
}
Collections.reverse(listBeansTemp);
return listBeansTemp;
}
}
public static void saveLogBean(HttpCacheBean bean) {
List httpCacheBeans = null;
String json = SharedUtil.instance(BaseApp.getContext()).getString(HttpCacheBean.TAG);
if (GsonUtil.isGoodJson(json))
httpCacheBeans = GsonUtil.getEntityList(json, HttpCacheBean.class);
if (GsonUtil.isBadJson(json) || httpCacheBeans == null) httpCacheBeans = new ArrayList<>();
httpCacheBeans.add(bean);
if (!ArrayUtils.isEmpty(httpCacheBeans) && httpCacheBeans.size() > 100) {
httpCacheBeans = httpCacheBeans.subList(httpCacheBeans.size() - 100, httpCacheBeans.size());
}
SharedUtil.instance(BaseApp.getContext()).saveObject(HttpCacheBean.TAG, httpCacheBeans);
}
}
因為我是list顯示的,而且我覺得很久之前的數(shù)據(jù)都是無用的,所以我在保存數(shù)據(jù)的時候都是取數(shù)據(jù)的最后的一百條來存儲,然后recycleview顯示的時候都是直接取所有數(shù)據(jù)顯示。
getLogLists里面有個keywords參數(shù),這個是方便用于搜索查詢的關鍵字,傳空默認返回全部數(shù)據(jù)。可以根據(jù)接口url來匹配查詢接口
GsonUtil.java工具類擴展
package cn.api.gjhealth.cstore.utils.jsonutils;
import android.text.TextUtils;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonParser;
import java.io.Reader;
import java.util.ArrayList;
import java.util.List;
/**
* Created by lc on 2017/5/17.
* Gson解析工具類
*/
public class GsonUtil {
// 是否json數(shù)據(jù)
public static boolean isGoodJson(String json) {
if (TextUtils.isEmpty(json)) {
return false;
}
try {
new JsonParser().parse(json);
return true;
} catch (JsonParseException e) {
return false;
}
}
public static boolean isBadJson(String json) {
return !isGoodJson(json);
}
// 是否存在指定key
public static boolean fieldIsNull(JsonObject command, String methodName) {
return GsonUtil.getKeyValue(command, methodName) == null;
}
/**
* 解析單個
*
* @param jsonStr
* @param entityClass
* @param
* @return
*/
public static T getEntity(String jsonStr, Class entityClass) {
Gson gson = new Gson();
T entity = null;
entity = gson.fromJson(jsonStr, entityClass);
return entity;
}
/**
* 解析list集合
*
* @param jsonStr
* @param entityClass
* @param
* @return
*/
public static List getEntityList(String jsonStr, Class entityClass) {
Gson gson = new Gson();
List list = new ArrayList();
JsonArray jsonArray = new JsonParser().parse(jsonStr).getAsJsonArray();
for (JsonElement element : jsonArray) {
list.add(gson.fromJson(element, entityClass));
}
return list;
}
// Gson解析器
public static Gson gson = new GsonBuilder()
.enableComplexMapKeySerialization()
.setExclusionStrategies(new FooAnnotationExclusionStrategy())//過濾不上傳的字段值
.serializeNulls()// 支持Map的key為復雜對象的形式
.setDateFormat("yyyy-MM-dd HH:mm:ss") // 時間轉化為特定格式
.setPrettyPrinting().create();
/****************** json拆分 ***************************************/
/**
* 從父JsonObject對象中獲取子JsonObject對象
*
* @param obj
* @param key
* @return
*/
public static JsonObject getJsonObject(JsonObject obj, String key) {
JsonElement element = obj.get(key);
return element.getAsJsonObject();
}
/**
* 從父JsonObject對象中獲取子JsonArray對象
*
* @param obj
* @param key
* @return
*/
public static JsonArray getJsonArray(JsonObject obj, String key) {
JsonElement element = obj.get(key);
return element.getAsJsonArray();
}
/**
* 得到根JsonObject
*
* @param jsonStr
* @return
*/
public static JsonObject getRootJsonObject(String jsonStr) {
JsonObject obj = new JsonParser().parse(jsonStr).getAsJsonObject();
return obj;
}
/**
* 得到根JsonArray
*
* @param jsonStr
* @return
*/
public static JsonArray getRootJsonArray(String jsonStr) {
JsonArray obj = new JsonParser().parse(jsonStr).getAsJsonArray();
return obj;
}
/**
* 從JsonObject對象中獲取鍵值
*
* @param obj
* @param key
* @return
*/
public static JsonElement getKeyValue(JsonObject obj, String key) {
JsonElement element = obj.get(key);
return element;
}
/**
* JsonArrary---->>>List
*
* @param jsonArray
* @return
*/
public static List JsonArrayToList(JsonArray jsonArray) {
List obj = new ArrayList();
for (int i = 0; i < jsonArray.size(); i++) {
obj.add(jsonArray.get(i).getAsJsonObject());
}
return obj;
}
public Gson getGson() {
return gson;
}
/****************** bean-jsonString配合builder使用 ***************************************/
// TODO 簡單bean,帶泛型的List------>>>>>String
public static String toJsonString(List json) {
return gson.toJson(json);
}
// TODO 簡單bean,帶泛型的List------>>>>>String
public static String toJsonString(T json) {
return gson.toJson(json);
}
/****************** bean-jsonobject配合builder使用 ***************************************/
// TODO bean-------->>>>jsonObject
public static JsonObject beanToJsonObject(T json) {
return getRootJsonObject(toJsonString(json));
}
// TODO List------>>>>>jsonArrary
public static JsonArray ListToJsonArrary(List json) {
return getRootJsonArray(toJsonString(json));
}
/********************* json--bean配合Gson解析 *************************************/
// TODO 轉換JSONObject對象中的JSONObject為Bean
public static T toBean(JsonObject obj, String key, Class type) {
return gson.fromJson(obj.get(key).toString(), type);
}
// TODO 轉換JSONObject對象中的JsonArray為List
public static List toList(JsonObject obj, String key, Class type) {
return JsonArrayToListBean(obj.get(key).getAsJsonArray(), type);
}
// TODO 直接把JsonArrary對象轉化為List
public static List JsonArrayToListBean(JsonArray jsonArray,
Class type) {
List obj = new ArrayList();
for (int i = 0; i < jsonArray.size(); i++) {
obj.add(gson.fromJson(jsonArray.get(i).toString(), type));
}
return obj;
}
// TODO 直接把json對象轉化為bean
public static T JsonObjectToBean(JsonObject jsonObject, Class type) {
return gson.fromJson(jsonObject.toString(), type);
}
// 檢查對象是否存在
public static boolean checkJsonObejct(JsonObject root, String key) {
if ((!getKeyValue(root, key).isJsonNull())
&& getKeyValue(root, key).isJsonObject()) {
return true;
}
return false;
}
// 檢查數(shù)組是否存在
public static boolean checkJsonArray(JsonObject root, String key) {
if ((!getKeyValue(root, key).isJsonNull())
&& getKeyValue(root, key).isJsonArray()) {
return true;
}
return false;
}
public static JsonArray getRootJsonArrayByInputStream(Reader jsonStr) {
JsonArray obj = new JsonParser().parse(jsonStr).getAsJsonArray();
return obj;
}
}
網(wǎng)絡請求數(shù)據(jù)顯示
顯示頁面很簡單,如果想看效果,直接到最下面看UI效果圖。
頁面就一個搜索框加一個recycleview列表顯示,顯示網(wǎng)絡請求url和請求狀態(tài)碼,點擊搜索,實時顯示搜索結果。直接貼代碼
HttpListActivity.java
public class HttpListActivity extends BaseSwipeBackActivity {
@Bind(R.id.index_app_name)
TextView indexAppName;
@Bind(R.id.recycler_view)
RecyclerView recyclerView;
@Bind(R.id.smart_rl)
SmartRefreshLayout refreshLayout;
@Bind(R.id.et_name)
EditText etName;
private HttpListAdapter adapter;
private String mKeyWords = "";
List mListBeans;
@Override
protected void onInitialization(Bundle bundle) {
}
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
adapter.setNewData(mListBeans);
refreshLayout.finishRefresh();
}
};
@Override
protected void initView(Bundle bundle) {
indexAppName.setText("Http請求Log日志");
initList();
setListDemo();
etName.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
mKeyWords = charSequence.toString().trim();
setListDemo();
}
@Override
public void afterTextChanged(Editable editable) {
}
});
}
private void setListDemo() {
new Thread(new Runnable() {
@Override
public void run() {
mListBeans = HttpLogManager.getLogLists(mKeyWords);
mHandler.sendEmptyMessage(0);
}
}).start();
}
private void initList() {
ListEmptyView emptyView = new ListEmptyView(getContext());
adapter = new HttpListAdapter();
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
recyclerView.setAdapter(adapter);
adapter.setOnItemClickListener(new BaseQuickAdapter.OnItemClickListener() {
@Override
public void onItemClick(BaseQuickAdapter adapter, View view, int position) {
Bundle bundle = new Bundle();
bundle.putSerializable(HttpCacheBean.TAG, (HttpCacheBean) adapter.getItem(position));
gStartActivity(HttpDetailsActivity.class, bundle);
}
});
adapter.setEmptyView(emptyView);
refreshLayout.setEnableLoadMore(false);
refreshLayout.setOnRefreshListener(new OnRefreshListener() {
@Override
public void onRefresh(@NonNull RefreshLayout refreshLayout) {
setListDemo();
}
});
}
@Override
protected void initData(Bundle bundle) {
}
@Override
protected int getLayoutId() {
return R.layout.activity_httplog_layout;
}
@OnClick(R.id.img_back)
public void onViewClicked() {
finish();
}
}
注意:這里因為有些請求返回數(shù)據(jù)量太大,直接查詢100條顯示頁面會卡住,所以這里用了handler進行耗時處理,顯示更加流暢
activity_http_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/bg_color"
android:orientation="vertical">
<include layout="@layout/titlebar_base_layout" />
<EditText
android:id="@+id/et_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/dimen_8dp"
android:background="@color/color_EDEDED"
android:focusable="true"
android:focusableInTouchMode="true"
android:hint="接口名稱"
android:padding="@dimen/dimen_10dp"
android:textSize="@dimen/dimen_14dp" />
<com.scwang.smartrefresh.layout.SmartRefreshLayout
android:id="@+id/smart_rl"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/title_bar" />
</com.scwang.smartrefresh.layout.SmartRefreshLayout>
</LinearLayout>
http日志詳情顯示頁面
HttpDetailsActivity.java
public class HttpDetailsActivity extends BaseSwipeBackActivity {
HttpCacheBean httpCacheBean;
@Bind(R.id.ll_back)
LinearLayout llBack;
@Bind(R.id.index_app_name)
TextView indexAppName;
@Bind(R.id.tv_title_right)
TextView tvTitleRight;
@Bind(R.id.tv_req_allUrl)
TextView tvReqAllUrl;
@Bind(R.id.tv_req_url)
TextView tvReqUrl;
@Bind(R.id.tv_req_code)
TextView tvReqCode;
@Bind(R.id.tv_req_type)
TextView tvReqType;
@Bind(R.id.tv_req_header)
TextView tvReqHeader;
@Bind(R.id.tv_req_data)
TextView tvReqData;
@Bind(R.id.ll_request)
LinearLayout llRequest;
@Bind(R.id.tv_res_allUrl)
TextView tvResAllUrl;
@Bind(R.id.tv_res_url)
TextView tvResUrl;
@Bind(R.id.tv_res_time)
TextView tvResTime;
@Bind(R.id.tv_res_code)
TextView tvResCode;
@Bind(R.id.tv_res_type)
TextView tvResType;
@Bind(R.id.tv_res_header)
TextView tvResHeader;
@Bind(R.id.tv_res_error)
TextView tvResError;
@Bind(R.id.tv_res_body)
TextView tvResBody;
@Bind(R.id.ll_response)
LinearLayout llResponse;
@Bind(R.id.tv_content_type)
TextView tvContentType;
@Bind(R.id.tv_req_time)
TextView tvReqTime;
@Override
protected void onInitialization(Bundle bundle) {
httpCacheBean = (HttpCacheBean) bundle.getSerializable(HttpCacheBean.TAG);
}
@Override
protected void initView(Bundle bundle) {
indexAppName.setText("請求詳情");
}
@Override
protected void initData(Bundle bundle) {
if (httpCacheBean != null) {
if (!TextUtils.isEmpty(httpCacheBean.url) && !TextUtils.isEmpty(httpCacheBean.method)) {
if (httpCacheBean.method.equals("GET")) {
try {
URL url = new URL(URLDecoder.decode(httpCacheBean.url));
httpCacheBean.requestBody = url.getQuery();
} catch (MalformedURLException e) {
}
}
}
tvReqTime.setText(httpCacheBean.requestTime);
tvReqAllUrl.setText(TextUtils.isEmpty(httpCacheBean.requestMsg) ? "--" : URLDecoder.decode(httpCacheBean.requestMsg));
tvReqUrl.setText(TextUtils.isEmpty(httpCacheBean.url) ? "--" : URLDecoder.decode(httpCacheBean.url));
tvReqCode.setText(TextUtils.isEmpty(httpCacheBean.code + "") ? "--" : httpCacheBean.code + "");
tvReqHeader.setText(TextUtils.isEmpty(httpCacheBean.reqHeaders) ? "--" : StringUtil.formatJson(httpCacheBean.reqHeaders));
tvReqData.setText(TextUtils.isEmpty(httpCacheBean.requestBody) ? "--" : StringUtil.formatJson(httpCacheBean.requestBody));
tvReqType.setText(TextUtils.isEmpty(httpCacheBean.method) ? "--" : httpCacheBean.method);
tvContentType.setText(TextUtils.isEmpty(httpCacheBean.reqContentType) ? "--" : httpCacheBean.reqContentType);
tvResAllUrl.setText(TextUtils.isEmpty(httpCacheBean.responseMsg) ? "--" : URLDecoder.decode(httpCacheBean.responseMsg));
tvResUrl.setText(TextUtils.isEmpty(httpCacheBean.url) ? "--" : URLDecoder.decode(httpCacheBean.url));
tvResCode.setText(TextUtils.isEmpty(httpCacheBean.code + "") ? "--" : httpCacheBean.code + "");
tvResHeader.setText(TextUtils.isEmpty(httpCacheBean.resHeaders) ? "--" : StringUtil.formatJson(httpCacheBean.resHeaders));
tvResBody.setText(TextUtils.isEmpty(httpCacheBean.responseBody) ? "--" : httpCacheBean.responseBody);
tvResTime.setText(TextUtils.isEmpty(httpCacheBean.time) ? "--" : httpCacheBean.time);
tvResType.setText(TextUtils.isEmpty(httpCacheBean.resContentType) ? "--" : httpCacheBean.resContentType);
tvResError.setText(TextUtils.isEmpty(httpCacheBean.error) ? "--" : httpCacheBean.error);
if (httpCacheBean.code >= 200 && httpCacheBean.code < 300) {
tvReqCode.setTextColor(Color.parseColor("#00FF00"));
tvResCode.setTextColor(Color.parseColor("#00FF00"));
}else if (httpCacheBean.code >= 400) {
tvReqCode.setTextColor(Color.parseColor("#FF0000"));
tvResCode.setTextColor(Color.parseColor("#FF0000"));
} else {
tvReqCode.setTextColor(Color.parseColor("#0000FF"));
tvResCode.setTextColor(Color.parseColor("#0000FF"));
}
}
}
@Override
protected int getLayoutId() {
return R.layout.activity_http_details_layout;
}
@OnClick(R.id.ll_back)
public void onViewClicked() {
finish();
}
}
activity_http_details_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/bg_color"
android:orientation="vertical">
<include layout="@layout/titlebar_base_simple_layout" />
<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="none">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/color_EDEDED"
android:padding="@dimen/dimen_5dp"
android:text="Request:"
android:textSize="15dp" />
<LinearLayout
android:id="@+id/ll_request"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/dimen_5dp"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/dimen_5dp"
android:text="請求時間:"
android:textColor="@color/color_4A4A4A"
android:textSize="12dp" />
<TextView
android:id="@+id/tv_req_time"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/dimen_5dp"
android:lineSpacingMultiplier="1.2"
android:textColor="@color/color_4A4A4A"
android:layout_margin="@dimen/dimen_5dp"
android:background="@color/color_EDEDED"
android:textSize="12dp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/dimen_5dp"
android:text="請求整體:"
android:textColor="@color/color_4A4A4A"
android:textSize="12dp" />
<TextView
android:id="@+id/tv_req_allUrl"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/dimen_5dp"
android:lineSpacingMultiplier="1.2"
android:textColor="@color/color_4A4A4A"
android:layout_margin="@dimen/dimen_5dp"
android:background="@color/color_EDEDED"
android:textIsSelectable="true"
android:textSize="12dp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/dimen_5dp"
android:text="請求Url:"
android:textColor="@color/color_4A4A4A"
android:textSize="12dp" />
<TextView
android:id="@+id/tv_req_url"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/dimen_5dp"
android:lineSpacingMultiplier="1.2"
android:textColor="@color/color_4A4A4A"
android:layout_margin="@dimen/dimen_5dp"
android:background="@color/color_EDEDED"
android:textIsSelectable="true"
android:textSize="12dp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/dimen_5dp"
android:text="請求數(shù)據(jù):"
android:textColor="@color/color_4A4A4A"
android:textSize="12dp" />
<TextView
android:id="@+id/tv_req_data"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/dimen_5dp"
android:lineSpacingMultiplier="1.2"
android:textColor="@color/color_4A4A4A"
android:layout_margin="@dimen/dimen_5dp"
android:background="@color/color_EDEDED"
android:textIsSelectable="true"
android:textSize="12dp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/dimen_5dp"
android:text="狀態(tài)碼:"
android:textColor="@color/color_4A4A4A"
android:textSize="12dp" />
<TextView
android:id="@+id/tv_req_code"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/dimen_5dp"
android:lineSpacingMultiplier="1.2"
android:textColor="@color/color_4A4A4A"
android:layout_margin="@dimen/dimen_5dp"
android:background="@color/color_EDEDED"
android:textIsSelectable="true"
android:textSize="12dp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/dimen_5dp"
android:text="請求類型:"
android:textColor="@color/color_4A4A4A"
android:textSize="12dp" />
<TextView
android:id="@+id/tv_req_type"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/dimen_5dp"
android:lineSpacingMultiplier="1.2"
android:textColor="@color/color_4A4A4A"
android:layout_margin="@dimen/dimen_5dp"
android:background="@color/color_EDEDED"
android:textIsSelectable="true"
android:textSize="12dp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/dimen_5dp"
android:text="請求ContentType:"
android:textColor="@color/color_4A4A4A"
android:textSize="12dp" />
<TextView
android:id="@+id/tv_content_type"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/dimen_5dp"
android:lineSpacingMultiplier="1.2"
android:textColor="@color/color_4A4A4A"
android:layout_margin="@dimen/dimen_5dp"
android:background="@color/color_EDEDED"
android:textIsSelectable="true"
android:textSize="12dp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/dimen_5dp"
android:text="請求頭:"
android:textColor="@color/color_4A4A4A"
android:textSize="12dp" />
<TextView
android:id="@+id/tv_req_header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/dimen_5dp"
android:lineSpacingMultiplier="1.2"
android:textColor="@color/color_4A4A4A"
android:layout_margin="@dimen/dimen_5dp"
android:background="@color/color_EDEDED"
android:textIsSelectable="true"
android:textSize="12dp" />
</LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/dimen_10dp"
android:background="@color/color_EDEDED"
android:padding="@dimen/dimen_5dp"
android:text="Response:"
android:textSize="15dp" />
<LinearLayout
android:id="@+id/ll_response"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="@dimen/dimen_5dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/dimen_5dp"
android:text="返回請求整體:"
android:textColor="@color/color_4A4A4A"
android:textSize="12dp" />
<TextView
android:id="@+id/tv_res_allUrl"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/dimen_5dp"
android:lineSpacingMultiplier="1.2"
android:textColor="@color/color_4A4A4A"
android:layout_margin="@dimen/dimen_5dp"
android:background="@color/color_EDEDED"
android:textIsSelectable="true"
android:textSize="12dp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/dimen_5dp"
android:text="返回請求Url:"
android:textColor="@color/color_4A4A4A"
android:textSize="12dp" />
<TextView
android:id="@+id/tv_res_url"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/dimen_5dp"
android:lineSpacingMultiplier="1.2"
android:textColor="@color/color_4A4A4A"
android:layout_margin="@dimen/dimen_5dp"
android:background="@color/color_EDEDED"
android:textIsSelectable="true"
android:textSize="12dp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/dimen_5dp"
android:text="返回請求時間:"
android:textColor="@color/color_4A4A4A"
android:textSize="12dp" />
<TextView
android:id="@+id/tv_res_time"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/dimen_5dp"
android:lineSpacingMultiplier="1.2"
android:textColor="@color/color_4A4A4A"
android:layout_margin="@dimen/dimen_5dp"
android:background="@color/color_EDEDED"
android:textIsSelectable="true"
android:textSize="12dp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/dimen_5dp"
android:text="狀態(tài)碼:"
android:textColor="@color/color_4A4A4A"
android:textSize="12dp" />
<TextView
android:id="@+id/tv_res_code"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/dimen_5dp"
android:lineSpacingMultiplier="1.2"
android:textColor="@color/color_4A4A4A"
android:layout_margin="@dimen/dimen_5dp"
android:background="@color/color_EDEDED"
android:textIsSelectable="true"
android:textSize="12dp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/dimen_5dp"
android:text="返回內容類型:"
android:textColor="@color/color_4A4A4A"
android:textSize="12dp" />
<TextView
android:id="@+id/tv_res_type"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/dimen_5dp"
android:lineSpacingMultiplier="1.2"
android:textColor="@color/color_4A4A4A"
android:layout_margin="@dimen/dimen_5dp"
android:background="@color/color_EDEDED"
android:textIsSelectable="true"
android:textSize="12dp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/dimen_5dp"
android:text="返回頭:"
android:textColor="@color/color_4A4A4A"
android:textSize="12dp" />
<TextView
android:id="@+id/tv_res_header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/dimen_5dp"
android:lineSpacingMultiplier="1.2"
android:textColor="@color/color_4A4A4A"
android:layout_margin="@dimen/dimen_5dp"
android:background="@color/color_EDEDED"
android:textIsSelectable="true"
android:textSize="12dp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/dimen_5dp"
android:text="錯誤信息error:"
android:textColor="@color/color_4A4A4A"
android:textSize="12dp" />
<TextView
android:id="@+id/tv_res_error"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/dimen_5dp"
android:lineSpacingMultiplier="1.2"
android:textColor="@color/color_4A4A4A"
android:layout_margin="@dimen/dimen_5dp"
android:textIsSelectable="true"
android:background="@color/color_EDEDED"
android:textSize="12dp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/dimen_5dp"
android:text="body:"
android:textColor="@color/color_4A4A4A"
android:textSize="12dp" />
<TextView
android:id="@+id/tv_res_body"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/dimen_5dp"
android:lineSpacingMultiplier="1.2"
android:background="@color/color_EDEDED"
android:padding="@dimen/dimen_5dp"
android:textIsSelectable="true"
android:textColor="@color/color_4A4A4A"
android:textSize="12dp" />
</LinearLayout>
</LinearLayout>
</android.support.v4.widget.NestedScrollView>
</LinearLayout>
HttpListAdapter.java
public class HttpListAdapter extends BaseQuickAdapter {
public HttpListAdapter() {
super(R.layout.item_http_view);
}
@Override
protected void convert(BaseViewHolder helper, HttpCacheBean item) {
helper.setText(R.id.tv_code, TextUtils.isEmpty(item.code + "") ? "none" : item.code + "");
helper.setText(R.id.tv_method, TextUtils.isEmpty(item.method) ? "none" : item.method);
helper.setText(R.id.tv_url, TextUtils.isEmpty(item.url) ? "--" : URLDecoder.decode(item.url));
}
}
item_http_view.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="10dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/tv_method"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/white"
android:background="@color/gray"
android:paddingLeft="@dimen/dimen_10dp"
android:paddingRight="@dimen/dimen_10dp"
android:textSize="14dp" />
<TextView
android:id="@+id/tv_code"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/dimen_5dp"
android:textColor="@color/white"
android:paddingLeft="@dimen/dimen_10dp"
android:paddingRight="@dimen/dimen_10dp"
android:background="@color/color_FA6469"
android:textSize="14dp" />
</LinearLayout>
<TextView
android:id="@+id/tv_url"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/color_2D2D2D"
android:textSize="14dp" />
<View
android:layout_width="match_parent"
android:layout_height="@dimen/dimen_1dp"
android:background="@color/color_EDEDED"/>
</LinearLayout>
全部代碼粘貼完畢。大家學習借鑒,本文章都是自己研究創(chuàng)作,有瑕疵,請大家多多包涵,共同學習進步。
本文只截取部分代碼上傳到github,屬于項目集成的,沒有時間分離,要借鑒移步github項目下載部分代碼,合并到自己分支。
Android版截圖



