支持手機和控制檯log的小工具-ZeusLog

0x00 解決問題

  1. 網絡請求查看比較麻煩,需要fiddler/charles代理,再格式化json。手機端可以像打印日誌一樣打印json格式化後的log ,其中【log內容區透傳操作,不影響操作app,側邊欄控制內容區過長的滑動】
  2. 系統Log比較挫,限制多多。優化系統控制檯log打印

項目地址 https://github.com/xsfelvis/ZeusLog

0x01 ZeusLog

主要分爲兩大塊,移動端Log和控制檯Log,先上圖

移動端

主要支持

  • 顯示當前Actvity的名稱
  • 顯示所需打印網絡請求的內容,內容部分透傳點擊事件,不影響app使用
  • 右邊有一個長條控制區域,可以滑動聯動內容區域,便於閱讀長的網絡請求

具體如下

【黃色區域】 當前Actvity名稱

【紅色區域】 log日誌開啓或者關閉,關閉後右邊控制區域也隨之消失,只有當前的Activity名稱,如下

【橙色區域】 網絡請求格式化顯示區域,該部分透傳所有點擊事件,從而使整個App使用不受影響

【綠色區域】 自定義sideBar,滑動該區域控制橙色區域長文本滾動閱讀

技術點:

沒有采用window去實現,原因很簡單,兼容性不好,現在各大廠商對自己的windows權限管理都很緊,而且正好嘗試一下自定義view+事件分發,有了想法,一時技癢,就擼一個唄,當然你也可以有其他更好的思路,也可以跟我交流。

主要採用了自定義viewgroup+自定義viewSideDragBar【綠色部分】,通過橙色部分顯示內容,透傳點擊事件從而不影響用戶對app操作,SideDragBar控制內容區域的滾動,從而不影響內容區長文本的閱讀。

幾個關鍵點:

  1. 附着到當前Activity屏幕

獲取屏幕的content區域內容,然後將我們的自定義viewgroup add進去

 ViewGroup rootView = (ViewGroup) activity.findViewById(Window.ID_ANDROID_CONTENT);

首先我們需要知道android中window的位置,下面摘了一張圖

  1. 透傳文本區域的點擊事件

我們希望屏幕日誌只是給用戶用來查看,而不要影響用戶對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>
  1. 長日誌如何滑動查看

這裏思考了一會,最後給出的解決方案就是在邊欄加一個自定義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循環一個一個字符的去解析,感覺實在不夠優雅,而且效率也很低,其實JSONObjectJSONArray就可以解決這個問題


 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

幾個關鍵點

  1. 顯示行號、函數名

這個需要使用到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;
        }
  1. 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);

最後感謝你寶貴的時間閱讀,如果你喜歡的話可以點贊收藏,也可以關注我的賬號,大家一起交流技術。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章