用kotlin打印出漂亮的android日志
zccornsr
8年前
<p><img src="https://simg.open-open.com/show/7abc6a97307f234131309b47cca8af27.png"></p> <p style="text-align:center">code in kotlin.png</p> <p>Kotlin号称是Android版本的swift,距离它1.0正式版本的推出快一年了。它像swift一样,可以写客户端也可以写服务端。由于公司项目比较繁忙,我一直没有时间关注和更进它,只是偶尔花点时间看一下它的语法。</p> <p>元旦放三天假,可以好好陪家人,也可以自己随便写点东西,于是便有了这篇文章。我尝试用kotlin封装了一个日志组件,用于android项目。</p> <p>我们先来看下效果图,看看它是如何打印出日志的</p> <p><img src="https://simg.open-open.com/show/02c9577bd430508e964fba3c4025d7fa.jpg"></p> <p style="text-align:center">打印字符串的日志.jpeg</p> <p><img src="https://simg.open-open.com/show/36f488dbd96ebc0d912c280312063fcf.jpg"></p> <p style="text-align:center">打印json格式的日志.jpeg</p> <p>上面的日志格式是不是很酷?它是用kotlin写出来的哦。</p> <p>talk is cheap, show me the code!</p> <pre> <code class="language-java">import android.util.Log import org.json.JSONArray import org.json.JSONException import org.json.JSONObject /** * Created by Tony Shen on 2017/1/2. */ object L { enum class LogLevel { ERROR { override val value: Int get() = 0 }, WARN { override val value: Int get() = 1 }, INFO { override val value: Int get() = 2 }, DEBUG { override val value: Int get() = 3 }; abstract val value: Int } private var TAG = "SAF_L" var logLevel = LogLevel.DEBUG // 日志的等级,可以进行配置,最好在Application中进行全局的配置 @JvmStatic fun init(clazz: Class<*>) { TAG = clazz.simpleName } /** * 支持用户自己传tag,可扩展性更好 * @param tag */ @JvmStatic fun init(tag: String) { TAG = tag } @JvmStatic fun e(msg: String) { if (LogLevel.ERROR.value <= logLevel.value) { if (msg.isNotBlank()) { val s = getMethodNames() Log.e(TAG, String.format(s, msg)) } } } @JvmStatic fun w(msg: String) { if (LogLevel.WARN.value <= logLevel.value) { if (msg.isNotBlank()) { val s = getMethodNames() Log.e(TAG, String.format(s, msg)) } } } @JvmStatic fun i(msg: String) { if (LogLevel.INFO.value <= logLevel.value) { if (msg.isNotBlank()) { val s = getMethodNames() Log.i(TAG, String.format(s,msg)) } } } @JvmStatic fun d(msg: String) { if (LogLevel.DEBUG.value <= logLevel.value) { if (msg.isNotBlank()) { val s = getMethodNames() Log.d(TAG, String.format(s, msg)) } } } @JvmStatic fun json(json: String) { var json = json if (json.isBlank()) { d("Empty/Null json content") return } try { json = json.trim { it <= ' ' } if (json.startsWith("{")) { val jsonObject = JSONObject(json) var message = jsonObject.toString(LoggerPrinter.JSON_INDENT) message = message.replace("\n".toRegex(), "\n║ ") val s = getMethodNames() println(String.format(s, message)) return } if (json.startsWith("[")) { val jsonArray = JSONArray(json) var message = jsonArray.toString(LoggerPrinter.JSON_INDENT) message = message.replace("\n".toRegex(), "\n║ ") val s = getMethodNames() println(String.format(s, message)) return } e("Invalid Json") } catch (e: JSONException) { e("Invalid Json") } } private fun getMethodNames(): String { val sElements = Thread.currentThread().stackTrace var stackOffset = LoggerPrinter.getStackOffset(sElements) stackOffset++ val builder = StringBuilder() builder.append(LoggerPrinter.TOP_BORDER).append("\r\n") // 添加当前线程名 .append("║ " + "Thread: " + Thread.currentThread().name).append("\r\n") .append(LoggerPrinter.MIDDLE_BORDER).append("\r\n") // 添加类名、方法名、行数 .append("║ ") .append(sElements[stackOffset].className) .append(".") .append(sElements[stackOffset].methodName) .append(" ") .append(" (") .append(sElements[stackOffset].fileName) .append(":") .append(sElements[stackOffset].lineNumber) .append(")") .append("\r\n") .append(LoggerPrinter.MIDDLE_BORDER).append("\r\n") // 添加打印的日志信息 .append("║ ").append("%s").append("\r\n") .append(LoggerPrinter.BOTTOM_BORDER).append("\r\n") return builder.toString() } fun String.isBlank(msg:String):Boolean { return msg==null || msg.length==0; } fun String.isNotBlank(msg:String):Boolean { return !msg.isBlank(); } }</code></pre> <p>这里,对kotlin的语法不做特别详细的解释,就解释一下@JvmStatic和最后两个方法。</p> <p>kotlin中在方法名前标注@JvmStatic,就表示该方法是静态的。</p> <p>例如:</p> <pre> <code class="language-java">@JvmStatic fun i(msg: String)</code></pre> <p>相当于java的</p> <pre> <code class="language-java">public static void i(String msg)</code></pre> <p>最后两个方法,就更加厉害了,使用了kotlin的extension function的特性。(即扩展类的函数, 可以在已有类中添加新的方法, 比继承更加简洁和优雅。)</p> <pre> <code class="language-java">fun String.isBlank(msg:String):Boolean { return msg==null || msg.length==0; } fun String.isNotBlank(msg:String):Boolean { return !msg.isBlank(); }</code></pre> <p>他们扩展了String类,在String类中增加两个函数isBlank()、isNotBlank()。</p> <p>使用方法:</p> <pre> <code class="language-java">msg.isNotBlank()</code></pre> <p>如果对这两个扩展类感兴趣,可以看看kotlin bytecode:</p> <p><img src="https://simg.open-open.com/show/d85708cf75395f45d944f79dd3858b4f.jpg"></p> <p style="text-align:center">kotlin bytecode.jpeg</p> <p>对了,还漏了一个LoggerPrinter类。同样附上源码:</p> <pre> <code class="language-java">/** * Created by Tony Shen on 2017/1/2. */ object LoggerPrinter { private val MIN_STACK_OFFSET = 3 /** * Drawing toolbox */ private val TOP_LEFT_CORNER = '╔' private val BOTTOM_LEFT_CORNER = '╚' private val MIDDLE_CORNER = '╟' private val HORIZONTAL_DOUBLE_LINE = '║' private val DOUBLE_DIVIDER = "════════════════════════════════════════════" private val SINGLE_DIVIDER = "────────────────────────────────────────────" val TOP_BORDER = TOP_LEFT_CORNER + DOUBLE_DIVIDER + DOUBLE_DIVIDER val BOTTOM_BORDER = BOTTOM_LEFT_CORNER + DOUBLE_DIVIDER + DOUBLE_DIVIDER val MIDDLE_BORDER = MIDDLE_CORNER + SINGLE_DIVIDER + SINGLE_DIVIDER /** * It is used for json pretty print */ val JSON_INDENT = 2 fun getStackOffset(trace: Array<StackTraceElement>): Int { var i = MIN_STACK_OFFSET while (i < trace.size) { val e = trace[i] val name = e.className if (name != LoggerPrinter::class.java.name && name != L::class.java.name) { return --i } i++ } return -1 } }</code></pre> <p>这个日志组件就这么两个类,支持跟java混编。</p> <p>举个栗子吧:</p> <pre> <code class="language-java">import android.app.Activity import android.content.Intent import android.os.Bundle import android.widget.Button import cn.kotlintest.saf.log.L import org.jetbrains.anko.find /** * Created by Tony Shen on 2016/12/28. */ class MainActivity : Activity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.main_activity) L.init(this.javaClass) initViews() initData() } fun initViews():Unit{ val button = find<Button>(R.id.button) button.setOnClickListener({ view -> var intent = Intent(this,SecondActivity::class.java) startActivity(intent) }) } fun initData():Unit{ var s = "{\"firstName\":\"Brett\",\"lastName\":\"McLaughlin\",\"email\":\"aaaa\"}" L.json(s) } }</code></pre> <p>在initData()中会打印一个json字符串,其运行效果已在最开始的图中。</p> <p>再举一个跟java混编的例子吧</p> <pre> <code class="language-java">import android.app.Activity; import android.os.Bundle; import cn.kotlintest.saf.log.L; /** * Created by Tony Shen on 2017/1/3. */ public class SecondActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); L.init(this.getClass()); L.i("this is second activity"); } }</code></pre> <p><img src="https://simg.open-open.com/show/9475af4ad356e5fa0a93fbd81bf894e3.jpg"></p> <p style="text-align:center">混编的效果.jpeg</p> <h2>写在最后</h2> <p>kotlin是开发android不错的选择,虽然我不会很激进地完全使用kotlin来替换原先的java代码,但是一些常用的工具类可能会有它来写,或者用它来逐步替换原先的工具类。</p> <p>这个日志组件要是看得不过瘾,可以看看我Android框架 SAF 里包含的日志组件,功能更加丰富。</p> <p> </p> <p>来自:http://www.jianshu.com/p/87da67f09c1c</p> <p> </p>