Flutter學(xué)習(xí)LogUtil封裝與實(shí)現(xiàn)實(shí)例詳解
一. 為什么要封裝打印類
雖然 flutter/原生給我們提供了日志打印的功能,但是超出一定長(zhǎng)度以后會(huì)被截?cái)?/p>
Json打印擠在一起看不清楚
堆棧打印深度過(guò)深多打印一些不需要的東西
實(shí)現(xiàn) log 的多種展示方式
二. 需要哪些類
為了可以實(shí)現(xiàn)對(duì)日志的多種內(nèi)容格式化和各種顯示輸出所以抽出來(lái)以下幾個(gè)類
- 一些常量的字符串表示
- 對(duì)日志內(nèi)容的打印輸出抽象類
- 對(duì)日志內(nèi)容格式化的抽象類
- 日志工具的config類
- 日志工具的管理類
- 日志工具的Util類
三. 打印輸出的抽象類
打印類核心的功能就是打印日志 所以它有一個(gè)方法就是打印的方法
而我們要打印輸出的內(nèi)容有 當(dāng)前 log等級(jí) log的tag 需要打印的數(shù)據(jù) 當(dāng)前堆棧信息 亦或是獲取的Json數(shù)據(jù)
/// 日志打印輸出的接口類 abstract class IHCLogPrint { void logPrint({ required LogType type, required String tag, required String message, StackTrace? stackTrace, Map<String, dynamic>? json, }); }
四. 格式化日志內(nèi)容
這里定義一個(gè)IHCLogFormatter抽象類
///格式化的接口類 abstract class IHCLogFormatter<T> { String format(T data); }
格式化堆棧
堆棧的格式例如這樣
#0 LogUtil._logPrint (package:com.halfcity.full_flutter_app/utils/log/log_util.dart:104:42)
#1 LogUtil._logPrint (package:com.halfcity.full_flutter_app/utils/log/log_util.dart:104:42)
#2 LogUtil._logPrint (package:com.halfcity.full_flutter_app/utils/log/log_util.dart:104:42)
....
會(huì)返回來(lái)很多無(wú)用的數(shù)據(jù) 而我們實(shí)際用到的也不過(guò)前五層就可以了
所以需要一個(gè)工具來(lái)剔除無(wú)用的數(shù)據(jù)和當(dāng)前自己的包名
堆棧裁切工具類
class StackTraceUtil { ///正則表達(dá)式 表示#+數(shù)字+空格的格式 static final RegExp _startStr = RegExp(r'#\d+[\s]+'); ///正則表達(dá)式表示 多個(gè)非換行符+ (非空) 正則表達(dá)式中()代表子項(xiàng) 如果需要正則()需要轉(zhuǎn)義\( \) ///了解更多 https://www.runoob.com/regexp/regexp-syntax.html static final RegExp _stackReg = RegExp(r'.+ \(([^\s]+)\)'); /// 把StackTrace 轉(zhuǎn)成list 并去除無(wú)用信息 /// [stackTrace] 堆棧信息 ///#0 LogUtil._logPrint (package:com.halfcity.full_flutter_app/utils/log/log_util.dart:104:42) static List<String> _fixStack(StackTrace stackTrace) { List tempList = stackTrace.toString().split("\n"); List<String> stackList = []; for (String str in tempList) { if (str.startsWith(_startStr)) { //又是#號(hào)又是空格比較占地方 這里省去了 如果你不想省去直接傳入str即可 stackList.add(str.replaceFirst(_startStr, ' ')); } } return stackList; } ///獲取剔除忽略包名及其其他無(wú)效信息的堆棧 /// [stackTrace] 堆棧 /// [ignorePackage] 需要忽略的包名 static List<String> _getRealStackTrack( StackTrace stackTrace, String ignorePackage) { ///由于Flutter 上的StackTrack上的不太一樣,Android返回的是list flutter返回的是StackTrack 所以需要手動(dòng)切割 再處理 List<String> stackList = _fixStack(stackTrace); int ignoreDepth = 0; int allDepth = stackList.length; //倒著查詢 查到倒數(shù)第一包名和需要屏蔽的包名一致時(shí),數(shù)據(jù)往上的數(shù)據(jù)全部舍棄掉 for (int i = allDepth - 1; i > -1; i--) { Match? match = _stackReg.matchAsPrefix(stackList[i]); //如果匹配且第一個(gè)子項(xiàng)也符合 group 0 表示全部 剩下的數(shù)字看子項(xiàng)的多少返回 if (match != null && (match.group(1)!.startsWith("package:$ignorePackage"))) { ignoreDepth = i + 1; break; } } stackList = stackList.sublist(ignoreDepth); return stackList; } /// 裁切堆棧 /// [stackTrace] 堆棧 /// [maxDepth] 深度 static List<String> _cropStackTrace(List<String> stackTrace, int? maxDepth) { int realDeep = stackTrace.length; realDeep = maxDepth != null && maxDepth > 0 ? min(maxDepth, realDeep) : realDeep; return stackTrace.sublist(0, realDeep); } ///裁切獲取到最終的stack 并獲取最大深度的棧信息 static getCroppedRealStackTrace( {required StackTrace stackTrace, ignorePackage, maxDepth}) { return _cropStackTrace( _getRealStackTrack(stackTrace, ignorePackage), maxDepth); } }
格式化堆棧信息
class StackFormatter implements ILogFormatter<List<String>> { @override String format(List<String> stackList) { ///每一行都設(shè)置成單獨(dú)的 字符串 StringBuffer sb = StringBuffer(); ///堆棧是空的直接返回 if (stackList.isEmpty) { return ""; ///堆棧只有一行那么就返回 - 堆棧 } else if (stackList.length == 1) { return "\n\t-${stackList[0].toString()}\n"; ///多行堆棧格式化 } else { for (int i = 0; i < stackList.length; i++) { if (i == 0) { sb.writeln("\n\t┌StackTrace:"); } if (i != stackList.length - 1) { sb.writeln("\t├${stackList[i].toString()}"); } else { sb.write("\t└${stackList[i].toString()}"); } } } return sb.toString(); } }
格式化JSON
class JsonFormatter extends ILogFormatter<Map<String, dynamic>> { @override String format(Map<String, dynamic> data) { ///遞歸調(diào)用循環(huán)遍歷data 在StringBuffer中添加StringBuffer String finalString = _forEachJson(data, 0); finalString = "\ndata:$finalString"; return finalString; } /// [data] 傳入需要格式化的數(shù)據(jù) /// [spaceCount] 需要添加空格的長(zhǎng)度 一個(gè)數(shù)字是兩個(gè)空格 /// [needSpace] 需不需要添加空格 /// [needEnter] 需不需要回車 String _forEachJson(dynamic data, int spaceCount, {bool needSpace = true, needEnter = true}) { StringBuffer sb = StringBuffer(); int newSpace = spaceCount + 1; if (data is Map) { ///如果它是Map走這里 ///是否需要空格 sb.write(buildSpace(needSpace ? spaceCount : 0)); sb.write(needEnter ? "{\n" : "{"); data.forEach((key, value) { ///打印輸出 key sb.write("${buildSpace(needEnter ? newSpace : 0)}$key: "); ///遞歸調(diào)用看value是什么類型 如果字符長(zhǎng)度少于30就不回車顯示 sb.write(_forEachJson(value, newSpace, needSpace: false, needEnter: !(value is Map ? false : value.toString().length < 50))); ///不是最后一個(gè)就加, if (data.keys.last != key) { sb.write(needEnter ? ",\n" : ","); } }); if (needEnter) { sb.writeln(); } sb.write("${buildSpace(needEnter ? spaceCount : 0)}}"); } else if (data is List) { ///如果他是列表 走這里 sb.write(buildSpace(needSpace ? spaceCount : 0)); sb.write("[${needEnter ? "\n" : ""}"); for (var item in data) { sb.write(_forEachJson(item, newSpace, needEnter: !(item.toString().length < 30))); ///不是最后一個(gè)就加的, if (data.last != item) { sb.write(needEnter ? ",\n" : ","); } } sb.write(needEnter ? "\n" : ""); sb.write("${buildSpace(needSpace?spaceCount:0)}]"); } else if (data is num || data is bool) { ///bool 或者數(shù)組不加雙引號(hào) sb.write(data); } else if (data is String) { ///string 或者其他的打印加雙引號(hào) 如果他是回車就改變他 按回車分行會(huì)錯(cuò)亂 sb.write("\"${data.replaceAll("\n", r"\n")}\""); } else { sb.write("$data"); } return sb.toString(); } ///構(gòu)造空格 String buildSpace(int deep) { String temp = ""; for (int i = 0; i < deep; i++) { temp += " "; } return temp; } }
五. 需要用到的常量
///常量 //log的type enum LogType { V, //VERBOSE E, //ERROR A, //ASSERT W, //WARN I, //INFO D, //DEBUG } int logMaxLength=1024; ///log的type 字符串說(shuō)明 List logTypeStr = ["VERBOSE", "ERROR", "ASSERT", "WARN", "INFO", "DEBUG"]; ///log的type 數(shù)字說(shuō)明(匹配的Android原生,ios暫不清楚) List<int> logTypeNum = [2, 6, 7, 5, 4, 3];
六. 為了控制多個(gè)打印器的設(shè)置做了一個(gè)配置類
class LogConfig { ///是否開(kāi)啟日志 bool _enable = false; ///默認(rèn)的Tag String _globalTag = "LogTag"; ///堆棧顯示的深度 int _stackTraceDepth = 0; ///打印的方式 List<ILogPrint>? _printers; LogConfig({enable, globalTag, stackTraceDepth, printers}) { _enable = enable; _globalTag = globalTag; _stackTraceDepth = stackTraceDepth; _printers?.addAll(printers); } @override String toString() { return 'LogConfig{_enable: $_enable, _globalTag: $_globalTag, _stackTraceDepth: $_stackTraceDepth, _printers: $_printers}'; } get enable => _enable; get globalTag => _globalTag; get stackTraceDepth => _stackTraceDepth; get printers => _printers; }
七. Log的管理類
class LogManager { ///config late LogConfig _config; ///打印器列表 List<ILogPrint> _printers = []; ///單例模式 static LogManager? _instance; factory LogManager() => _instance ??= LogManager._(); LogManager._(); ///初始化 Manager方法 LogManager.init({config, printers}) { _config = config; _printers.addAll(printers); _instance = this; } get printers => _printers; get config => _config; void addPrinter(ILogPrint print) { bool isHave = _printers.any((element) => element == print); if (!isHave) { _printers.add(print); } } void removePrinter(ILogPrint print) { _printers.remove(print); } }
九. 調(diào)用LogUtil
class LogUtil { static const String _ignorePackageName = "log_demo/utils/log"; static void V( {String? tag, dynamic? message, LogConfig? logConfig, StackTrace? stackTrace, Map<String, dynamic>? json}) { _logPrint( type: LogType.V, tag: tag ??= "", logConfig: logConfig, message: message, json: json, stackTrace: stackTrace); } static void E( {String? tag, dynamic? message, LogConfig? logConfig, StackTrace? stackTrace, Map<String, dynamic>? json}) { _logPrint( type: LogType.E, tag: tag ??= "", message: message, logConfig: logConfig, json: json, stackTrace: stackTrace); } static void I( {String? tag, dynamic? message, LogConfig? logConfig, StackTrace? stackTrace, Map<String, dynamic>? json}) { _logPrint( type: LogType.I, tag: tag ??= "", message: message, json: json, stackTrace: stackTrace); } static void D( {String? tag, dynamic? message, LogConfig? logConfig, StackTrace? stackTrace, Map<String, dynamic>? json}) { _logPrint( type: LogType.D, tag: tag ??= "", logConfig: logConfig, message: message, json: json, stackTrace: stackTrace); } static void A( {String? tag, LogConfig? logConfig, dynamic? message, StackTrace? stackTrace, Map<String, dynamic>? json}) { _logPrint( type: LogType.A, tag: tag ??= "", message: message, logConfig: logConfig, json: json, stackTrace: stackTrace); } static void W( {String? tag, dynamic? message, LogConfig? logConfig, StackTrace? stackTrace, Map<String, dynamic>? json}) { _logPrint( type: LogType.W, tag: tag ??= "", message: message, logConfig: logConfig, json: json, stackTrace: stackTrace); } static Future<void> _logPrint({ required LogType type, required String tag, LogConfig? logConfig, dynamic message, StackTrace? stackTrace, Map<String, dynamic>? json, }) async { ///如果logConfig為空那么就用默認(rèn)的 logConfig ??= LogManager().config; if (!logConfig?.enable) { return; } StringBuffer sb = StringBuffer(); ///打印當(dāng)前頁(yè)面 if (message.toString().isNotEmpty) { sb.write(message); } ///如果傳入了棧且 要展示的深度大于0 if (stackTrace != null && logConfig?.stackTraceDepth > 0) { sb.writeln(); String stackTraceStr = StackFormatter().format( StackTraceUtil.getCroppedRealStackTrace( stackTrace: stackTrace, ignorePackage: _ignorePackageName, maxDepth: logConfig?.stackTraceDepth)); sb.write(stackTraceStr); } if (json != null) { sb.writeln(); String body = JsonFormatter().format(json); sb.write(body); } ///獲取有幾個(gè)打印器 List<ILogPrint> prints = logConfig?.printers ?? LogManager().printers; if (prints.isEmpty) { return; } ///遍歷打印器 分別打印數(shù)據(jù) for (ILogPrint print in prints) { print.logPrint(type: type, tag: tag, message: sb.toString()); } } }
十. 定義一個(gè)Flutter 控制臺(tái)打印輸出的方法
class ConsolePrint extends ILogPrint { @override void logPrint( {required LogType type, required String tag, required String message, StackTrace? stackTrace, Map<String, dynamic>? json}) { ///如果要開(kāi)啟顏色顯示 那么就是1000 ///如果不開(kāi)啟顏色顯示 那么就是1023 int _maxCharLength = 1000; //匹配中文字符以及這些中文標(biāo)點(diǎn)符號(hào) 。 ? ! , 、 ; : “ ” ‘ ' ( ) 《 》 〈 〉 【 】 『 』 「 」 ﹃ ﹄ 〔 〕 … — ~ ﹏ ¥ RegExp _chineseRegex = RegExp(r"[\u4e00-\u9fa5|\u3002|\uff1f|\uff01|\uff0c|\u3001|\uff1b|\uff1a|\u201c|\u201d|\u2018|\u2019|\uff08|\uff09|\u300a|\u300b|\u3008|\u3009|\u3010|\u3011|\u300e|\u300f|\u300c|\u300d|\ufe43|\ufe44|\u3014|\u3015|\u2026|\u2014|\uff5e|\ufe4f|\uffe5]"); ///用回車做分割 List<String> strList = message.split("\n"); ///判斷每句的長(zhǎng)度 如果長(zhǎng)度過(guò)長(zhǎng)做切割 for (String str in strList) { ///獲取總長(zhǎng)度 int len = 0; ///獲取當(dāng)前長(zhǎng)度 int current = 0; ///獲取截?cái)帱c(diǎn)數(shù)據(jù) List<int> entry = [0]; ///遍歷文字 查看真實(shí)長(zhǎng)度 for (int i = 0; i < str.length; i++) { //// 一個(gè)漢字再打印區(qū)占三個(gè)長(zhǎng)度,其他的占一個(gè)長(zhǎng)度 len += str[i].contains(_chineseRegex) ? 3 : 1; ///尋找當(dāng)前字符的下一個(gè)字符長(zhǎng)度 int next = (i + 1) < str.length ? str[i + 1].contains(_chineseRegex) ? 3 : 1 : 0; ///當(dāng)前字符累計(jì)長(zhǎng)度 如果達(dá)到了需求就清空 current += str[i].contains(_chineseRegex) ? 3 : 1; if (current < _maxCharLength && (current + next) >= _maxCharLength) { entry.add(i); current = 0; } } ///如果最后一個(gè)階段點(diǎn)不是最后一個(gè)字符就添加上 if (entry.last != str.length - 1) { entry.add(str.length); } ///如果所有的長(zhǎng)度小于1023 那么打印沒(méi)有問(wèn)題 if (len < _maxCharLength) { _logPrint(type, tag, str); } else { ///按照獲取的截?cái)帱c(diǎn)來(lái)打印 for (int i = 0; i < entry.length - 1; i++) { _logPrint(type, tag, str.substring(entry[i], entry[i + 1])); } } } } _logPrint(LogType type, String tag, String message) { ///前面的\u001b[31m用于設(shè)定SGR顏色,后面的\u001b[0m相當(dāng)于一個(gè)封閉標(biāo)簽作為前面SGR顏色的作用范圍的結(jié)束點(diǎn)標(biāo)記。 /// \u001b[3 文字顏色范圍 0-7 標(biāo)準(zhǔn)顏色 0是黑色 1是紅色 2是綠色 3是黃色 4是藍(lán)色 5是紫色 6藍(lán)綠色 是 7是灰色 范圍之外都是黑色 /// \u001b[9 文字顏色范圍 0-7 高強(qiáng)度顏色 0是黑色 1是紅色 2是綠色 3是黃色 4是藍(lán)色 5是紫色 6藍(lán)綠色 是 7是灰色 范圍之外都是黑色 /// 自定義顏色 \u001b[38;2;255;0;0m 表示文字顏色 2是24位 255 0 0 是顏色的RGB 可以自定義顏色 /// \u001b[4 數(shù)字 m 是背景色 /// \u001b[1m 加粗 /// \u001b[3m 斜體 /// \u001b[4m 下劃線 /// \u001b[7m 黑底白字 ///\u001b[9m 刪除線 ///\u001b[0m 結(jié)束符 //////詳情看 https://www.cnblogs.com/zt123123/p/16110475.html String colorHead = ""; String colorEnd = "\u001b[0m"; switch (type) { case LogType.V: // const Color(0xff181818); colorHead = "\u001b[38;2;187;187;187m"; break; case LogType.E: colorHead = "\u001b[38;2;255;0;6m"; break; case LogType.A: colorHead = "\u001b[38;2;143;0;5m"; break; case LogType.W: colorHead = "\u001b[38;2;187;187;35m"; break; case LogType.I: colorHead = "\u001b[38;2;72;187;49m"; break; case LogType.D: colorHead = "\u001b[38;2;0;112;187m"; break; } /// 這里是純Flutter項(xiàng)目所以在控制臺(tái)打印這樣子是可以有顏色的 如果是flutter混編 安卓原生側(cè)打印\u001b 可能是一個(gè)亂碼也沒(méi)有變色效果 /// 如果你不想只在調(diào)試模式打印 你可以把debugPrint換成print debugPrint("$colorHead$message$colorEnd"); /// 如果原生側(cè)有封裝log工具直接 寫一個(gè)methodChannel 傳參數(shù)就好 ,如果沒(méi)有,可以調(diào)用原生的log打印 傳入 level tag 和message /// kDebugMode 用這個(gè)可以判斷是否在debug模式下 /// if(kDebugMode){ /// 在debug模式下打印日志 // bool? result=await CustomChannelUtil.printLog(level:logTypeNum[type.index],tag:tag,message:message); /// } } }
十一. 現(xiàn)在使用前初始化log打印器一次
Widget build(BuildContext context) { LogManager.init( config: LogConfig(enable: true, globalTag: "TAG", stackTraceDepth: 5), printers: [ConsolePrint()]);
使用
///打印堆棧 LogUtil.I(tag: "test", stackTrace: StackTrace.current); ///打印json LogUtil.E(tag: "JSON", json: json); ///打印信息 LogUtil.V(tag: "LogText", message: message);
以上就是Flutter學(xué)習(xí)LogUtil封裝與實(shí)現(xiàn)實(shí)例詳解的詳細(xì)內(nèi)容,更多關(guān)于Flutter LogUtil 封裝實(shí)現(xiàn)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- Flutter在項(xiàng)目中使用動(dòng)畫不使用包實(shí)現(xiàn)詳解
- Flutter繪制3.4邊形及多邊形漸變動(dòng)畫實(shí)現(xiàn)示例
- Flutter添加頁(yè)面過(guò)渡動(dòng)畫實(shí)現(xiàn)步驟
- 封裝flutter狀態(tài)管理工具示例詳解
- flutter封裝單選點(diǎn)擊菜單工具欄組件
- flutter封裝點(diǎn)擊菜單工具欄組件checkBox多選版
- 基于fluttertoast實(shí)現(xiàn)封裝彈框提示工具類
- Flutter封裝組動(dòng)畫混合動(dòng)畫AnimatedGroup示例詳解
相關(guān)文章
Android圓形頭像拍照后“無(wú)法加載此圖片”的問(wèn)題解決方法(適配Android7.0)
這篇文章主要介紹了Android圓形頭像拍照后“無(wú)法加載此圖片”的問(wèn)題解決方法(適配Android7.0) ,需要的朋友可以參考下2017-10-10Android版微信跳一跳小游戲利用技術(shù)手段達(dá)到高分的操作方法
朋友圈到處都是曬微信跳一跳小游戲的,很多朋友能達(dá)到二三百分了。下面小編給大家分享Android版微信跳一跳小游戲利用技術(shù)手段達(dá)到高分的操作方法,需要的朋友一起看看吧2018-01-01android開(kāi)發(fā)去除標(biāo)題欄的方法
這篇文章主要介紹了android開(kāi)發(fā)去除標(biāo)題欄的方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-04-04Android定時(shí)開(kāi)機(jī)的流程詳解
這篇文章給大家分享了Android定時(shí)開(kāi)機(jī)及其實(shí)現(xiàn)流程,對(duì)此知識(shí)點(diǎn)有興趣的朋友,可以學(xué)習(xí)參考下。2018-07-07Kotlin學(xué)習(xí)教程之協(xié)程Coroutine
這篇文章主要給大家介紹了關(guān)于Kotlin學(xué)習(xí)教程之協(xié)程Coroutine的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-05-05Android nativePollOnce函數(shù)解析
這篇文章主要介紹了Android nativePollOnce函數(shù)解析的相關(guān)資料,幫助大家更好的理解和學(xué)習(xí)使用Android,感興趣的朋友可以了解下2021-03-03Android切換前后臺(tái)點(diǎn)擊通知進(jìn)入當(dāng)前頁(yè)面
這篇文章主要介紹了Android切換前后臺(tái)點(diǎn)擊通知進(jìn)入當(dāng)前頁(yè)面,主要講述當(dāng)App退出到后臺(tái)的后,怎么點(diǎn)擊通知回到原來(lái)按下HOME鍵之前的前臺(tái)頁(yè)面,需要的朋友可以參考下2023-03-03Android仿網(wǎng)易客戶端頂部導(dǎo)航欄效果
這篇文章主要為大家詳細(xì)介紹了Android仿網(wǎng)易客戶端頂部導(dǎo)航欄效果,幫助大家制作網(wǎng)易客戶端導(dǎo)航欄特效,感興趣的小伙伴們可以參考一下2016-06-06Android編程自定義菜單實(shí)現(xiàn)方法詳解
這篇文章主要介紹了Android編程自定義菜單實(shí)現(xiàn)方法,結(jié)合實(shí)例形式分析了Android自定義菜單的布局、動(dòng)畫及功能相關(guān)實(shí)現(xiàn)技巧與注意事項(xiàng),需要的朋友可以參考下2017-02-02關(guān)于Android CountDownTimer的使用及注意事項(xiàng)
這篇文章主要介紹了關(guān)于Android CountDownTimer的使用及注意事項(xiàng),具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-11-11