前言: Android中Logcat長(zhǎng)日志打印不全的問(wèn)題很多人都知道,網(wǎng)上也有很多解決方案热凹,但問(wèn)題是這些答案確定是正解嗎泵喘?
說(shuō)一下我的經(jīng)過(guò):
- 最開始發(fā)現(xiàn)Logcat長(zhǎng)日志打印不全泪电,然后我就去網(wǎng)上找了答案,很多文章說(shuō)的是打印日志最長(zhǎng)是4k長(zhǎng)度的字符串纪铺,我用長(zhǎng)英文字母的字符串測(cè)試后相速,大概代碼是這樣子的(錯(cuò)誤代碼):
/**
* 超4000字符時(shí)分段打印
*
* @param priority
* @param tag
* @param content
*/
private void print(int priority, String tag, String content) {
if (content == null) {
content = "";
}
// 不超過(guò)4000時(shí)正常打印
if (content.length() <= 4000) {
Log.println(priority, tag, content);
return;
}
int count = 1;
// 超過(guò)4000時(shí)分段打印
for (int i = 0; i < content.length(); i += 4000) {
String desc = String.format("分段打印(%s):", count);
String splitContent = i + 4000 < content.length() ? content.substring(i, i + 4000) : content.substring(i, content.length());
Log.println(priority, tag, desc + splitContent);
count++;
}
}
本以為很完美,但使用過(guò)程中還是老出現(xiàn)奇怪的問(wèn)題鲜锚,有的日志打印正常突诬,有的日志打印卻會(huì)丟失,這就讓人很難受了芜繁,我一度以為是我打印日志過(guò)多旺隙,控制臺(tái)選擇性丟棄了部分日志,因?yàn)橐郧叭罩敬蛴∵^(guò)多把控制臺(tái)都搞崩潰過(guò)骏令。
當(dāng)有一次打印超長(zhǎng)的一個(gè)json字符串蔬捷,然后我分段復(fù)制出來(lái)拼接诈乒,但是一頓操作后卻不能拼接成完整json衰伯,原因就是特么又有日志丟了瞳步,我那個(gè)氣哦~(垃圾AS祠乃,毀我青春)唉,那再接再厲看看啥情況唄款青。
我就用控制變量法渔彰,反復(fù)測(cè)試打印速度沥匈、文本長(zhǎng)度聪黎、文本內(nèi)容等罕容,在什么情況下會(huì)丟日志,最后發(fā)現(xiàn)就是上面代碼的問(wèn)題稿饰,AS控制臺(tái)打印的最大長(zhǎng)度不是按字符串長(zhǎng)度算的,而字節(jié)長(zhǎng)度B恫础:砹!
為什么會(huì)偶爾丟日志惭笑?字符串默認(rèn)是UTF-8編碼侣姆,它是一種變長(zhǎng)編碼,一個(gè)字符用1~4個(gè)字節(jié)表示沉噩,數(shù)字捺宗、字母及其他符號(hào)字符用1個(gè)字節(jié)表示,中文字符用3個(gè)字節(jié)表示川蒙,所以偶爾有超長(zhǎng)字符串蚜厉,且包含多個(gè)中文等字符時(shí),其長(zhǎng)度就可能超過(guò)最大限制畜眨,導(dǎo)致打印丟失昼牛。
解決方案
經(jīng)多次測(cè)試术瓮,控制臺(tái)最多打印了4062個(gè)字節(jié)(不同版本等情況會(huì)稍有出入),總的來(lái)說(shuō)打印4000個(gè)字節(jié)在不同情況都是穩(wěn)妥的贰健。做法就是分段打印胞四,這里需要將字符串轉(zhuǎn)字節(jié)數(shù)組,將字節(jié)數(shù)組分段伶椿,再重新轉(zhuǎn)字符串依次打印輸出辜伟。
正解代碼實(shí)現(xiàn)
/**
* 打印日志到控制臺(tái)(解決Android控制臺(tái)丟失長(zhǎng)日志記錄)
*
* @param priority
* @param tag
* @param content
*/
public void print(int priority, String tag, String content) {
// 1. 測(cè)試控制臺(tái)最多打印4062個(gè)字節(jié),不同情況稍有出入(注意:這里是字節(jié)脊另,不是字符S沃纭!)
// 2. 字符串默認(rèn)字符集編碼是utf-8尝蠕,它是變長(zhǎng)編碼一個(gè)字符用1~4個(gè)字節(jié)表示
// 3. 這里字符長(zhǎng)度小于1000烘豌,即字節(jié)長(zhǎng)度小于4000,則直接打印看彼,避免執(zhí)行后續(xù)流程廊佩,提高性能哈
if (content.length() < 1000) {
Log.println(priority, tag, content);
return;
}
// 一次打印的最大字節(jié)數(shù)
int maxByteNum = 4000;
// 字符串轉(zhuǎn)字節(jié)數(shù)組
byte[] bytes = content.getBytes();
// 超出范圍直接打印
if (maxByteNum >= bytes.length) {
Log.println(priority, tag, content);
return;
}
// 分段打印計(jì)數(shù)
int count = 1;
// 在數(shù)組范圍內(nèi),則循環(huán)分段
while (maxByteNum < bytes.length) {
// 按字節(jié)長(zhǎng)度截取字符串
String subStr = cutStr(bytes, maxByteNum);
// 打印日志
String desc = String.format("分段打印(%s):%s", count++, subStr);
Log.println(priority, tag, desc);
// 截取出尚未打印字節(jié)數(shù)組
bytes = Arrays.copyOfRange(bytes, subStr.getBytes().length, bytes.length);
// 可根據(jù)需求添加一個(gè)次數(shù)限制靖榕,避免有超長(zhǎng)日志一直打印
/*if (count == 10) {
break;
}*/
}
// 打印剩余部分
Log.println(priority, tag, String.format("分段打印(%s):%s", count, new String(bytes)));
}
/**
* 按字節(jié)長(zhǎng)度截取字節(jié)數(shù)組為字符串
*
* @param bytes
* @param subLength
* @return
*/
public String cutStr(byte[] bytes, int subLength) {
// 邊界判斷
if (bytes == null || subLength < 1) {
return null;
}
// 超出范圍直接返回
if (subLength >= bytes.length) {
return new String(bytes);
}
// 復(fù)制出定長(zhǎng)字節(jié)數(shù)組标锄,轉(zhuǎn)為字符串
String subStr = new String(Arrays.copyOf(bytes, subLength));
// 避免末尾字符是被拆分的,這里減1使字符串保持完整
return subStr.substring(0, subStr.length() - 1);
}
以下為測(cè)試代碼
public void test() {
JSONArray array = new JSONArray();
StringBuilder builder1 = new StringBuilder();
StringBuilder builder2 = new StringBuilder();
for (int i = 0; i < 1000; i++) {
array.put("好h哦~");
builder1.append("哈哈哦哦");
builder2.append("hello1024");
}
String json = array.toString();
String str1 = builder1.toString();
String str2 = builder2.toString();
// json打印了2581個(gè)字符茁计,字節(jié)長(zhǎng)度為4057料皇,其余丟失
Log.i("defaultTag", json);
// str1打印了1352個(gè)字符,字節(jié)長(zhǎng)度為4056星压,其余丟失
Log.i("defaultTag", str1);
// str2打印了4055個(gè)字符践剂,字節(jié)長(zhǎng)度為4055,其余丟失
Log.i("defaultTag", str2);
// 以下為分段打印娜膘,看是否有丟失日志
print(Log.INFO, "defaultTag", json);
print(Log.INFO, "defaultTag", str1);
print(Log.INFO, "defaultTag", str2);
}
注意事項(xiàng)
- 日志打印為了保證性能逊脯,要減少調(diào)用字符串的getBytes()方法,這個(gè)方法會(huì)遍歷字符串竣贪,將每個(gè)字符編碼為不同的字節(jié)军洼。
- 可根據(jù)需求添加一個(gè)次數(shù)限制,避免有非常長(zhǎng)的日志一直打印演怎,如果本身需要全部打印就忽略這個(gè)問(wèn)題匕争。