用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>