【问题标题】:What is the best way to define log TAG constant in Kotlin?在 Kotlin 中定义 log TAG 常量的最佳方法是什么?
【发布时间】:2024-01-21 09:09:02
【问题描述】:

我正在我的 Android 应用程序中创建我的第一个 Kotlin 类。通常出于记录目的,我有一个名称为TAG 的常量。我会用 Java 做的是:

private static final String TAG = MyClass.class.getSimpleName();

我知道在 Kotlin 类中我可以使用这种方式创建 TAG

private val TAG = MyClass::class.java.simpleName

这对于使用 Java 和 Kotlin 的项目来说是可以的,但如果我开始一个仅使用 Kotlin 的新项目怎么办?我怎样才能在那里定义TAG 常量?有没有更多的 Kotlin 方式让我没有这种奇怪的构造 class.java.simpleName

【问题讨论】:

  • 如果类名被混淆,则使用simpleNamerisky
  • 有风险吗?至少可以说!这样做时,我的应用程序总是崩溃。
  • private val TAG = MainActivity::class.java.simpleName;

标签: java android kotlin constants


【解决方案1】:

这个扩展允许我们在任何类中使用 TAG

val Any.TAG: String
    get() {
        val tag = javaClass.simpleName
        return if (tag.length <= 23) tag else tag.substring(0, 23)
    }

//usage
Log.e(TAG,"some value")

它也被验证为 Android 有效的 Log 标签。

【讨论】:

  • 谢谢,一直在用!
  • 不适用于匿名内部类,否则很好
  • 我是 kotlin 的新手,所以一个诚实的问题:这有什么缺点(除了匿名内部类)吗?这似乎是最好的答案,因为我认为这就是 kotlin 扩展的用途..
  • 这是一个很好的答案,但如果您的 minSDK 为 24 或更高,则不需要进行长度检查,因为不再有标签长度限制。
  • 这个去哪儿了?什么是扩展?
【解决方案2】:

一般常量都是大写的(例如 FOO)并且位于companion object

class MyClass {
    companion object {
        public const val FOO = 1

    }
}

并定义您可以使用的 TAG 字段:

private val TAG = MyClass::class.qualifiedName

【讨论】:

  • MyClass::class.qualifiedName 返回字符串,所以用 const 显示 err ... 怎么办?
  • @EnamulHaque const 值必须是编译时间常数。由于 Kotlin 没有像 nameof 这样的东西,它不能保证该语句总是具有相同的值。此外,正如 Gabriele Mariotti 所说,const 没有在答案中使用。
  • 嗯,根据Android Studio的警告,MyClass::class.qualifiedName需要反射,大大增加了应用的复杂度,需要额外的jar文件。有没有更简单的方法来完成这个非常常见的任务?
  • @ScottBiggs 使用MyClass::class.java.simpleName 怎么样?
  • 关于使用反射如何增加应用程序复杂性的阅读有很好的参考吗? (我在这里什么也没看到:kotlinlang.org/docs/reference/reflection.html
【解决方案3】:

使用companion object 的常见建议方法会生成额外的static final 伴随类实例,因此性能和内存都很差。

最好的方法(恕我直言)

将日志标签定义为*常量,因此只会生成额外的类(MyClassKt),但与companion object相比,将没有static final的实例(也没有任何实例):

private const val TAG = "MyLogTag"

class MyClass {

    fun logMe() {
        Log.w(TAG, "Message")
    }
}

另一种选择

使用普通的val。尽管将日志标记视为不全大写的常量看起来很不寻常,但这不会生成任何类并且开销最小。

class MyClass {

    private val tag = "myLogTag"

    fun logMe() {
        Log.w(tag, "Message")
    }
}

【讨论】:

  • 为什么这不是最受好评的答案?由于我认为您不会在课程外使用标签,因此不需要在同伴课程中。下一个问题是这里发布的两个选项在内存消耗、性能等方面是否同样好?
【解决方案4】:

我喜欢 TAG 作为扩展函数 as suggested by Fredy Mederos

扩展他的答案以支持匿名类:

 /**
 * extension function to provide TAG value
 */
val Any.TAG: String
    get() {
        return if (!javaClass.isAnonymousClass) {
            val name = javaClass.simpleName
            if (name.length <= 23) name else name.substring(0, 23)// first 23 chars
        } else {
            val name = javaClass.name
            if (name.length <= 23) name else name.substring(name.length - 23, name.length)// last 23 chars
        }
    }

【讨论】:

  • 很好的答案,但从 API 24 开始,没有长度限制,因此如果您的应用的最小 SDK 为 24+,您可以取消长度检查。
  • 我喜欢这个解决方案,并在当前项目中尝试过,但由于某些原因,在某些类中它无法解析 Android Studio 中的引用……而其他类则可以。奇怪
  • @szaske 是那些内部类吗?
【解决方案5】:

在 Kotlin 中,您可以创建一个扩展,并将标签作为方法调用来调用。这意味着您永远不必在每个类中定义它,我们可以在每次调用该方法时动态构造它:

inline fun <reified T> T.TAG(): String = T::class.java.simpleName

【讨论】:

  • simpleName 在构建被混淆时不是很好的解决方案,例如MyActivity 可能会变成 M​​,你会在日志中到处看到 M。它在预发布构建等场景中失败,其中构建是发布类型,但我们仍想捕获日志!
【解决方案6】:

只需执行以下操作即可。

private val TAG = this::class.java.simpleName

【讨论】:

  • 这段代码在companion object中使用安全吗?
  • 你需要 kotlin-reflect.jar
【解决方案7】:

最好的记录方式(恕我直言)是使用 Timber: https://github.com/JakeWharton/timber

但是如果你不想使用库那么

TAG 可以定义为内联扩展属性(例如在Extensions.kt 中):

inline val <reified T> T.TAG: String
    get() = T::class.java.simpleName

Log.d(TAG, "") 中不要一直写 TAG 的一些扩展:

inline fun <reified T> T.logv(message: String) = Log.v(TAG, message)
inline fun <reified T> T.logi(message: String) = Log.i(TAG, message)
inline fun <reified T> T.logw(message: String) = Log.w(TAG, message)
inline fun <reified T> T.logd(message: String) = Log.d(TAG, message)
inline fun <reified T> T.loge(message: String) = Log.e(TAG, message)

然后你可以在任何类中使用它们:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    logd("Activity created")
}

【讨论】:

  • Павел,您想如何使用 Timber?
  • @CoolMind 只需在应用程序onCreate() { if (BuildConfig.DEBUG) Timber.plant(Timber.DebugTree()) } 中设置它,然后在任何类中只需使用Timber.d("msg"),无需显式LOG_TAG
  • 啊,谢谢,我明白了。我找了TAG,已经添加了木材。
  • Kotlin 是否也有旧 java Log 类所具有的标签的字符限制?
  • @BrillPappin afaik,标签长度限制与安卓版本有关,与语言无关。根据documentation,自api24以来没有限制,但我发现问题may appear on versions above 24
【解决方案8】:

您可以通过@JvmField 定义您的TAG,如下所示:

companion object {
    @JvmField val TAG: String = MyClass::class.java.simpleName
}

更多详情可以阅读这篇文章:Kotlin's hidden costs

【讨论】:

  • 您的消息来源是这样说的:“事实上,创建此注解只是出于 Java 兼容性的原因,我绝对不建议您使用晦涩的互操作注解来弄乱您漂亮的 Kotlin 代码”。那你为什么推荐使用@JvmField呢?
【解决方案9】:

我创建了一些日志扩展函数来避免像在 Java 中那样声明日志标记(可能性能较差,但鉴于我们正在讨论日志记录,这应该是可以接受的 IMO)。该方法使用具体类型参数和其他 Kotlin 好东西来检索类的简单名称。这是一个基本示例:

inline fun <reified T> T.logi(message: String) =
   Log.i(T::class.java.simpleName, message)

您可以找到更详细的要点here

【讨论】:

  • 这应该是公认的答案。我不明白为什么要继续模仿 Java 方法
【解决方案10】:

我将常量创建为伴随对象:

companion object {
    val TAG = "SOME_TAG_VALUE"
}

然后,我可以这样使用它:

MyClass.TAG

【讨论】:

    【解决方案11】:

    Kotlin 1.2.20更新答案

    class MyClass {
        companion object {
    
            @JvmField
            public val FOO = 1
        }
    }
    

    用途

    MyClass.FOO
    

    【讨论】:

      【解决方案12】:

      val声明TAG变量

      class YourClass {
         companion object {
            //if use java and kotlin both in project
            //private val TAG = MyClass::class.java.simpleName
      
            //if use only kotlin in project
            private val TAG = YourClass::class.simpleName
         }
      }
      

      像这样使用变量

      Log.d(YourClass.TAG, "Your message");
      //or 
      Log.e(TAG, "Your message");
      

      【讨论】:

      • 如果不使用::class.java.simpleName,则必须包含kotlin-reflect.jar
      【解决方案13】:

      我找到了一种更“可复制粘贴”的方法,因为它不需要您输入班级名称:

      package com.*.mypackage
      
      class MyClass
      {
          companion object {
              val TAG = this::class.toString().split(".").last().dropLast(10)
          }
      }
      

      这不是最优雅的解决方案,但它确实有效。

      this::class.toString().split(".").last() 会给你"com.*.mypackage.MyClass$Companion",所以你需要dropLast(10) 来删除$Companion

      您也可以这样做:

      package com.*.mypackage
      
      class MyClass
      {
          val TAG = this::class.simpleName
      }
      

      但是TAG 成员变量不再是“静态的”并且不遵循推荐的命名约定。

      【讨论】:

      • 对于 Kotlin 来说,这是一种减少样板代码的方法!
      【解决方案14】:

      你可以试试这个:

      companion object {
          val TAG = ClearCacheTask::class.java.simpleName as String
      }
      

      【讨论】:

        【解决方案15】:

        AnkoLogger使用接口来定义日志标签。

        interface AnkoLogger {
                    /**
                     * The logger tag used in extension functions for the [AnkoLogger].
                     * Note that the tag length should not be more than 23 symbols.
                     */
                    val loggerTag: String
                        get() = getTag(javaClass)
                }
        private fun getTag(clazz: Class<*>): String {
                val tag = clazz.simpleName
                return if (tag.length <= 23) {
                    tag
                } else {
                    tag.substring(0, 23)
                }
            }
        inline fun AnkoLogger.info(message: () -> Any?) {
            val tag = loggerTag
            if (Log.isLoggable(tag, Log.INFO)) {
                Log.i(tag, message()?.toString() ?: "null")
            }
        }
        

        你可以这样使用它:

        class MyClass : AnkoLogger {
            fun someFun(){
               info("logging info")
            }
        }
        

        也许 AnkoLogger 可以为您提供一些实现自定义日志记录工具的想法。

        【讨论】:

          【解决方案16】:

          在 Android Studio 中,重命名的常用方法是右键单击名称,选择 Refactor->Rename。所以,我觉得做这样的事情就好了,

          class MyClass {
              companion object {
                  private const LOG_TAG = "MyClass"
              }
          }
          

          因为如果您像我描述的那样重命名类 MyClass,那么 IDE 也会建议重命名您的 LOG_TAG 字符串。

          归根结底,使用这种方法与其他方法相比,各有利弊。因为 LOG_TAG 是一个字符串,所以不需要导入 kotlin-reflect.jar,就像将 LOG_TAG 设置为等于 MyClass::class.simpleName 一样。此外,由于该变量使用const 关键字声明为编译时常量,因此生成的字节码更小,因为它不需要生成更多隐藏的getter,如this article 中所述。

          【讨论】:

          • 为什么不简单地命名它只是标签?在任何类中,您通常有任何其他名称为 TAG 的常量吗?
          【解决方案17】:

          这是我在 kotlin 中的扩展功能,只需将其添加到您的扩展文件中即可。

          val Any.TAG: String
          get() {
              return if (!javaClass.isAnonymousClass) {
                  val name = javaClass.simpleName
                  if (name.length <= 23 || Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) name else
                      name.substring(0, 23)// first 23 chars
              } else {
                  val name = javaClass.name
                  if (name.length <= 23 || Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
                      name else name.substring(name.length - 23, name.length)// last 23 chars
              }
          }
          

          然后你可以在任何类中使用 TAG,如下所示:

          Log.d(TAG, "country list")
          

          【讨论】:

            【解决方案18】:

            我已经定义了一个接口,它将 TAG 定义为具有默认 getter 实现的属性。 接口既可以由一个类“实现”,也可以由它的伴随对象“实现”:

            //Prefix allows easier filtering in LogCat
            private const val PREFIX = "somePrefix.";
            
            interface HasLogTag {
                val TAG: String
                    get() {
                        val name = javaClass.canonicalName?.removeSuffix(".Companion")?.substringAfterLast(".")
                        return "$PREFIX${name}"
                    }
            }
            

            接口使用如下:

            import yourPackage.HasLogTag     
            ....
            class MyClass : HasLogTag {
                ...
                //Alternatively: Let the companion object "implement" the interface
                companion object : HasLogTag {
                ...
                Log.e(TAG, "Some Info)
            }
            

            由于每次使用都会调用 getter,因此为伴随对象定义 TAG 没有任何好处。

            注意:在以前的版本中,我使用反射来确定是类本身还是伴随对象定义了接口。

            然而,这似乎大大减慢了我的应用程序的启动速度。

            【讨论】: