Android 日志打印还能这么玩?
本文作者
作者:唐子玄
链接:
https://juejin.cn/post/7171031370511155231
本文由作者授权发布。
不出意外,下周就不推送了哈,大家春节快乐!
Log.v("test", "duration=${duration}")
但在复杂的业务场景中,会存在各种嵌套的复杂结构,比如 List,Map。断点调试中,可以轻松地点开这些数据结构,查看任何感兴趣的字段,甚至还可以当场计算。在输出日志时有什么好办法能够轻松地输出这些复杂的数据结构吗?
刚开始开发 Android 时,我是这样打印列表的:
for (int i = 0; i < list.size(); i++) {
Log.d("test", "list item="+list.get(i));
}
for (String str:list) {
Log.v("test", "list item="+str);
}
fun <T> Collection<T>.print(mapper: (T) -> String) =
StringBuilder("\n[").also { sb ->
//遍历集合元素将元素转换成感兴趣的字串,并独占一行
this.forEach { e -> sb.append("\n\t${mapper(e)},") }
sb.append("\n]")
}.toString()
data class Person(var name: String, var age: Int)
val persons = listOf(
Person("Peter", 16),
Person("Anna", 28),
Person("Anna", 23),
Person("Sonya", 39)
)
persons.print { "${it.name}_${it.age}" }.let { Log.v("test",it) }
V/test: [
Peter_16,
Anna_28,
Anna_23,
Sonya_39,
]fun <K, V> Map<K, V?>.print(mapper: (V?) -> String): String =
StringBuilder("\n{").also { sb ->
this.iterator().forEach { entry ->
sb.append("\n\t[${entry.key}] = ${mapper(entry.value)}")
}
sb.append("\n}")
}.toString()有些数据类字段比较多,调试时,想把它们通通打印出来,在 Java 中,借助于 AndroidStudio 的 toString功能倒是可以方便地生成可读性很高的字串:
public class Person {
private String name;
private int age;
@Override
public String toString() {
return ”Person{“ +
”name=‘“ + name + ’\” +
”, age=“ + age +
‘}’;
}
}data class Person(var name: String, var age: Int)
Log.v(“test”, “person=${Person("Peter", 16)}”)
//输出如下:
V/test: person=Person(name=Peter, age=16)
fun Any.ofMap() =
// 过滤掉除data class以外的其他类
this::class.takeIf { it.isData }
// 遍历类的所有成员 过滤掉成员方法 只考虑成员属性
?.members?.filterIsInstance<KProperty<Any>>()
// 将成员属性名和值存储在Pair中
?.map { it.name to it.call(this) }
// 将Pair转换成map
?.toMap()
isData是KClass中的一个属性,用于判断该类是不是一个data class。KClass是 Kotlin 中用来描述 类的类型,KClass可以通过对象::class语法获得。 members也是KClass中的一个属性,它以列表的形式返回了类中所有的方法和属性。 filterIsInstance()是Iterable接口的扩展函数,用于过滤出集合中指定的类型。 to是一个infix扩展函数,它的定义如下:
public infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)
带有infix标识的函数只允许带有一个参数,并且在调用时可以省略包裹参数的括号。这种语法叫中缀表达式,它用于简化方法调用。
data class Person(var name: String, var age: Int)
Person("Peter", 16).ofMap()?.print { it.toString() }.let { Log.v("test","$it") }V/test:
{
[age] = 16
[name] = Peter
}
//位置,嵌套在Person类中
data class Location(var x: Int, var y: Int)
data class Person(
var name: String,
var age: Int,
var locaton: Location? = null
)
Person("Peter", 16, Location(20, 30))
.ofMap()
?.print { it.toString() }
.let { Log.v("test", "$it") }
// 打印结果如下
{
[age] = 16
[locaton] = Location(x=20, y=30)
[name] = Peter
}
fun Any.ofMap(): Map<String, Any?>? {
return this::class.takeIf { it.isData }
?.members?.filterIsInstance<KProperty<Any>>()
?.map { member ->
val value = member.call(this)?.let { v->
//'若成员变量是data class,则递归调用ofMap(),将其转化成键值对,否则直接返回值'
if (v::class.isData) v.ofMap()
else v
}
member.name to value
}
?.toMap()
}
/**
* 打印 Map,生成结构化键值对子串
* @param space 行缩进量
*/
fun <K, V> Map<K, V?>.print(space: Int = 0): String {
//'生成当前层次的行缩进,用space个空格表示,当前层次每一行内容都需要带上缩进'
val indent = StringBuilder().apply {
repeat(space) { append(" ") }
}.toString()
return StringBuilder("\n${indent}{").also { sb ->
this.iterator().forEach { entry ->
//'如果值是 Map 类型,则递归调用print()生成其结构化键值对子串,否则返回值本身'
val value = entry.value.let { v ->
(v as? Map<*, *>)?.print("${indent}${entry.key} = ".length) ?: v.toString()
}
sb.append("\n\t${indent}[${entry.key}] = $value,")
}
sb.append("\n${indent}}")
}.toString()
}
//'坐标类,嵌套在Location类中'
data class Coordinate(var x: Int, var y: Int)
//'位置类,嵌套在Person类中'
data class Location(
var country: String,
var city: String,
var coordinate: Coordinate
)
data class Person(
var name: String,
var age: Int,
var locaton: Location? = null
)
Person("Peter", 16, Location("china", "shanghai", Coordinate(10, 20)))
.ofMap()
?.print()
.let { Log.v("test", "$it") }
//'打印效果如下'
{
[age] = 16,
[locaton] =
{
[city] = shanghai,
[coordinate] =
{
[x] = 10,
[y] = 20,
},
[country] = china,
},
[name] = Peter,
}
最后推荐一下我做的网站,玩Android: wanandroid.com ,包含详尽的知识体系、好用的工具,还有本公众号文章合集,欢迎体验和收藏!
推荐阅读:
扫一扫 关注我的公众号
如果你想要跟大家分享你的文章,欢迎投稿~
┏(^0^)┛明天见!