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);
最后感谢你宝贵的时间阅读,如果你喜欢的话可以点赞收藏,也可以关注我的账号,大家一起交流技术。