0x00 解決問題
- 網絡請求查看比較麻煩,需要fiddler/charles代理,再格式化json。手機端可以像打印日誌一樣打印json格式化後的log ,其中【log內容區透傳操作,不影響操作app,側邊欄控制內容區過長的滑動】
- 系統Log比較挫,限制多多。優化系統控制檯log打印
項目地址 https://github.com/xsfelvis/ZeusLog
0x01 ZeusLog
主要分爲兩大塊,移動端Log和控制檯Log,先上圖
移動端
主要支持
- 顯示當前Actvity的名稱
- 顯示所需打印網絡請求的內容,內容部分透傳點擊事件,不影響app使用
- 右邊有一個長條控制區域,可以滑動聯動內容區域,便於閱讀長的網絡請求
具體如下
【黃色區域】 當前Actvity名稱
【紅色區域】 log日誌開啓或者關閉,關閉後右邊控制區域也隨之消失,只有當前的Activity名稱,如下
【橙色區域】 網絡請求格式化顯示區域,該部分透傳所有點擊事件,從而使整個App使用不受影響
【綠色區域】 自定義sideBar,滑動該區域控制橙色區域長文本滾動閱讀
技術點:
沒有采用window去實現,原因很簡單,兼容性不好,現在各大廠商對自己的windows權限管理都很緊,而且正好嘗試一下自定義view+事件分發,有了想法,一時技癢,就擼一個唄,當然你也可以有其他更好的思路,也可以跟我交流。
主要採用了自定義viewgroup+自定義view
SideDragBar
【綠色部分】,通過橙色部分顯示內容,透傳點擊事件從而不影響用戶對app操作,SideDragBar
控制內容區域的滾動,從而不影響內容區長文本的閱讀。
幾個關鍵點:
- 附着到當前Activity屏幕
獲取屏幕的content區域內容,然後將我們的自定義viewgroup add進去
ViewGroup rootView = (ViewGroup) activity.findViewById(Window.ID_ANDROID_CONTENT);
首先我們需要知道android中window的位置,下面摘了一張圖
- 透傳文本區域的點擊事件
我們希望屏幕日誌只是給用戶用來查看,而不要影響用戶對app的操作,但是也要對長文本支持,這樣採用重寫ScrollView包裹一個textView的方法來解決這個問題,這個ScrollView的攔截事件方法均被重寫
@Override
public boolean onTouchEvent(MotionEvent ev) {
return false;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return false;
}
這樣這個scrollview就可以透傳了,
<xsf.zeuslibrary.zeusMobile.ScrollViewSV
android:id="@+id/svContent"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="9">
<TextView
android:textColor="@color/white"
android:id="@+id/tvShowInfo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="日誌顯示 \n"
android:textSize="12dp"/>
</xsf.zeuslibrary.zeusMobile.ScrollViewSV>
- 長日誌如何滑動查看
這裏思考了一會,最後給出的解決方案就是在邊欄加一個自定義view SideDragBar【綠色部分】
通過重寫dispatchTouchEvent
,記錄滑動變化,然後使用控制傳入的scrollview滾動
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
final int action = event.getAction();
//記錄當前點擊位置
int x = (int) event.getX();
int y = (int) event.getY();
switch (action) {
case MotionEvent.ACTION_DOWN:
lastX = x;
lastY = y;
case MotionEvent.ACTION_MOVE:
int offsetX = x-lastX;
int offsetY = y-lastY;
sv.smoothScrollBy(0,offsetY);
break;
case MotionEvent.ACTION_UP:
break;
}
return true;
}
總之就是通過重寫scrollview使得控制滾動,從而解決這個問題
4.格式化json數據
關於這個,剛開始在網上找了一下,很多都是直接for循環一個一個字符的去解析,感覺實在不夠優雅,而且效率也很低,其實JSONObject
、JSONArray
就可以解決這個問題
String message;
try{
if(msg.startsWith("{")){
JSONObject jsonObject = new JSONObject(msg);
message = jsonObject.toString(ZeusLog.JSON_INDENT);
}else if(msg.startsWith("[")){
JSONArray jsonArray = new JSONArray(msg);
message = jsonArray.toString(ZeusLog.JSON_INDENT);
}else {
message = msg;
}
}catch (JSONException e){
message = msg;
}
控制檯
控制檯有一些比較全的Log庫,如orhanobut/logger、JakeWharton/timber等,感覺這些庫寫的都很重,當然功能很全,個人不太不喜歡他們打印log格式化方式,而且也想看看具體原理還有也不想增加無需求的功能,如xml格式化等,最終自己搓了一個支持基本log+多參數+json格式,具體如下:
- 支持顯示行號
- 支持顯示Log所在函數名稱
- 支持無Tag快捷打印
- 支持在Android Studio開發IDE中,點擊函數名稱,跳轉至Log所在位置
- 支持JSON字符串解析打印
- 支持無限長字符串打印,無Logcat4000字符限制
- 支持變長參數,任意個數打印參數
- 支持設置全局Tag
基本tag
無tag顯示當前類名
格式化輸出json
多參數log
幾個關鍵點
- 顯示行號、函數名
這個需要使用到Thread.currentThread().getStackTrace()
返回的是一個StackTraceElement數組,內容爲調用函數堆棧,並且以調用層級關係保存。android中對應返回值數組是19個,而且最終都是調用
dalvik.system.NativeStart.main(Native Method)
有興趣的可以斷點看下,其實日誌打印工具類實現都離不開它的應用,然後獲取對應index的StackTraceElement你就可以獲取當前行號,類名,部分代碼如下
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
StackTraceElement targetElement = stackTrace[stackTraceIndex];
String className = targetElement.getClassName();
String[] classNameInfo = className.split("\\.");
if (classNameInfo.length > 0) {
className = classNameInfo[classNameInfo.length - 1] + SUFFIX;
}
if (className.contains("$")) {
className = className.split("\\$")[0] + SUFFIX;
}
String methodName = targetElement.getMethodName();
int lineNumber = targetElement.getLineNumber();
if (lineNumber < 0) {
lineNumber = 0;
}
- log長字符的限制
先介紹下Android中Log的實現結構
可以看出大致過程 App通過util.log.產生日誌->JVM->JNI(Native C)調用->log_write的sys_call()->logger驅動->dispatch分發給訂閱者,android打印受限問題就出在這個Logger驅動上
#define LOGGER_ENTRY_MAX_LEN (4*1024)
#define LOGGER_ENTRY_MAX_PAYLOAD \\
(LOGGER_ENTRY_MAX_LEN - sizeof(struct logger_entry))
可以看出,系統的顯示單條Log長度是有限制的4*1024字符長度,那麼我們打印日誌的時候可以對症下藥,在寫自己的log日誌時可以對msg做下處理,採取分段打印
public static void printDefault(int type, String tag, String msg) {
int index = 0;
int length = msg.length();
int countOfSub = length / MAX_LENGTH;
if (countOfSub > 0) {
for (int i = 0; i < countOfSub; i++) {
String sub = msg.substring(index, index + MAX_LENGTH);
printSub(type, tag, sub);
index += MAX_LENGTH;
}
printSub(type, tag, msg.substring(index, length));
} else {
printSub(type, tag, msg);
}
}
0x02 How to Use
compile 'com.xsf:zeusLog:1.0.0'
移動端Log使用很簡單,需要在你想打印的地方,調用如下API即可
ZeusMobileView.startZeus(MainActivity.this).setJsonStr(JSON_LONG);
控制檯Log 需要先初始化安全等級
ZeusLog.init(BuildConfig.DEBUG);
表示僅僅在debug包下打印日誌,如果不初始化也可使用,但是需要注意release包保護
然後如同使用系統API一樣使用即可
不帶tag
- ZeusLog.v(LOG_MSG);
- ZeusLog.d(LOG_MSG);
- ZeusLog.i(LOG_MSG);
- ZeusLog.w(LOG_MSG);
- ZeusLog.e(LOG_MSG);
- ZeusLog.a(LOG_MSG);
帶tag
- ZeusLog.v(TAG, LOG_MSG);
- ZeusLog.d(TAG, LOG_MSG);
- ZeusLog.i(TAG, LOG_MSG);
- ZeusLog.w(TAG, LOG_MSG);
- ZeusLog.e(TAG, LOG_MSG);
- ZeusLog.a(TAG, LOG_MSG);
json格式化
- ZeusLog.printJsonStr(JSON);
多個參數
- ZeusLog.v(TAG, LOG_MSG, “params1”, “params2”, this);
- ZeusLog.d(TAG, LOG_MSG, “params1”, “params2”, this);
- ZeusLog.i(TAG, LOG_MSG, “params1”, “params2”, this);
- ZeusLog.w(TAG, LOG_MSG, “params1”, “params2”, this);
- ZeusLog.e(TAG, LOG_MSG, “params1”, “params2”, this);
- ZeusLog.a(TAG, LOG_MSG, “params1”, “params2”, this);
最後感謝你寶貴的時間閱讀,如果你喜歡的話可以點贊收藏,也可以關注我的賬號,大家一起交流技術。