隐式链接隐式链接

    科技2022-07-12  134

    隐式链接隐式链接

    I assume every Android developer knows and uses the logging facilities provided by the Android SDK. For example, when you need to log something for debugging purposes, you’d end up writing something like that:

    我假设每个Android开发人员都知道并使用Android SDK提供的日志记录功能。 例如,当您需要记录某些东西以进行调试时,最终会写出这样的东西:

    Log.d("MainActivity", "Here comes the story of a sleepless night which was spent trying to nail that nasty issue...");

    The first argument is the tag:

    第一个参数是标记:

    * @param tag Used to identify the source of a log message. It usually identifies the class or activity where the log call occurs.

    Sooner or later you may find out that you need more flexibility with your logs — let’s say that you want to send all crash reports into the Firebase Crashlytics service (but only if they are from release builds, since on debug builds you want them to end up in logcat only), you want your debug logs to be sent to the special SQLite table, etc., etc. This is where some developers end up using the library called Timber. The thing that you notice immediately is that you don’t need to specify the tag:

    或早或晚,您可能会发现日志需要更大的灵活性-假设您要将所有崩溃报告发送到Firebase Crashlytics服务中(但仅当它们来自发行版本时,因为在调试版本中,您希望它们结束(仅在logcat中),您希望将调试日志发送到特殊SQLite表等。在这里,某些开发人员最终使用了名为Timber的库。 您立即注意到的是,您无需指定标签:

    Timber.d("Another sleepless night, same issue, but at least we don't need tags...");

    And yet when you look at the logcat, the tag is here:

    但是,当您查看logcat时,标记在这里:

    05-01 21:39:10.763 9350-9350/com.application D/MainActivity: Another sleepless night, same issue, but at least we don't need tags...

    I admit, when I started using Timber, I didn’t stop to think how exactly does it work. It’s time to fill that knowledge gap.

    我承认,当我开始使用Timber时,我没有停止思考它到底是如何工作的。 现在是时候填补这一知识空白。

    一切都以logcat结尾... (It all ends with the logcat…)

    …so this is where we’re going to start looking. I was using the Java version of Timber, so here’s the link if you want to take a look for yourself.

    …所以这就是我们要开始寻找的地方。 我使用的是Java版本的Timber,因此如果您想自己看看,这里是链接 。

    My initial idea was that if the logged lines end up in the logcat, we need to search for some standard log invocations (e.g. Log.d ), and we’ll see how the tags are created. The search raises nothing of the sort, but there is something else (lines 425–428):

    我最初的想法是,如果记录的行最终出现在logcat中,我们需要搜索一些标准的日志调用(例如Log.d ),然后我们将了解如何创建标签。 搜索不会产生任何东西,但是还有其他东西(第425-428行):

    If you scroll further, you’ll see more and more invocations of prepareLog . Ok, that’s something, let’s see what’s going on in this method (lines 530–555):

    如果进一步滚动,将看到越来越多的prepareLog调用。 好的,就是这样,让我们​​看看这种方法的作用(第530-555行):

    Does it look like the tag is created in getTag (lines 401–408)?

    看起来标签是在getTag创建的(第401-408行)吗?

    No, apparently not.

    不,显然不是。

    显式标签? 我以为我们在这里谈论隐式的! (Explicit tags? I thought we’re talking about implicit ones here!)

    All getTag does is it gets the tag from the explicitTag field (which has the type of ThreadLocal ; I’m not going to discuss it in detail here, except for saying that this class provides thread-local variables — each thread that accesses ThreadLocal variable gets its own copy of it, which is inaccessible to other threads). But if it gets the tag from the ThreadLocal container, then it means that someone puts it in here, right? Let’s search for explicitTag.set (we have a single result, lines 135–144):

    getTag所做的只是从“ explicitTag字段(其类型为ThreadLocal ;从这里获取标记)中获取标签,除了说该类提供线程局部变量(每个访问ThreadLocal变量的线程)外,这里不再详细讨论。获取它自己的副本,其他线程无法访问该副本)。 但是,如果它从ThreadLocal容器中获取标签,则意味着有人将其放在这里,对吗? 让我们搜索explicitTag.set (我们有一个结果,第135–144行):

    Ok, I feel like we’re getting to the end of the journey. How does the actual call of the tag method look like?

    好的,我觉得我们已经结束了旅程。 tag方法的实际调用是什么样的?

    Well, there’re no invocations of that method in Timber , so I assume that’s a dead end. Let’s return to the previous step and look at getTag again. This method is defined in the abstract class called Tree , and it is overridden in the class called DebugTree , so let’s take a look at how the overridden version looks like (lines 615–629):

    好吧,在Timber没有该方法的调用,所以我认为那是一个死胡同。 让我们返回上一步,再次查看getTag 。 该方法在名为Tree的抽象类中定义,并且在名为DebugTree的类中被DebugTree ,因此让我们看一下被覆盖的版本的样子(第615–629行):

    Wait, what? Do we create an instance of Throwable only to get a tag from its stack trace?

    等一下 我们是否仅创建Throwable实例以从其堆栈跟踪中获取标签?

    We do (lines 600–613):

    我们这样做(第600–613行):

    StackTraceElement#getClassName does the following:

    StackTraceElement#getClassName执行以下操作 :

    Returns the fully qualified name of the class containing the execution point represented by this stack trace element.

    返回包含此堆栈跟踪元素表示的执行点的类的完全限定名称。

    The mystery is solved. What I thought was achieved through the dark reflection witchery ended up being nothing more than a smart usage* of a Throwable instance.

    谜团解决了。 我认为通过黑暗反射巫术实现的最终结果无非是Throwable实例的明智用法。

    还有一件事 (One more thing)

    By the way, if we take a look at the comment accompanying the createStackTraceElement , we’ll notice the following comment:

    顺便说一下,如果我们看一下createStackTraceElement附带的注释,我们会注意到以下注释:

    Note: This will not be called if a {@linkplain #tag(String) manual tag} was specified.

    Ok, now I get why do we need the tag method that we’ve encountered before and why we didn’t see it being invoked in the Timber source code. This method is meant to be used in cases where we want to override the tag extracted from the stack trace with our own tag for the next logging call. And the most likely reason for storing it in the ThreadLocal container is that we don’t want to end up having a race condition.

    好的,现在我明白了为什么我们需要之前遇到的tag方法以及为什么我们没有在Timber源代码中看到它被调用。 此方法适用于以下情况:我们要使用下一个日志记录调用的标记覆盖从堆栈跟踪中提取的标记。 将其存储在ThreadLocal容器中的最可能的原因是我们不想最终遇到竞争状况。

    For example, Thread A sets an explicit tag to be used for the next logging call, but Thread B gets its chance to access the explicit tag first, effectively using the tag for its intents and purposes (while it shouldn’t). After that, it might get a chance to clean up the explicit tag before Thread A accesses it (which is wrong, because Thread A needs it) or it will not manage to clean it up soon enough (which is also illegal, since Thread A will use the explicit tag and we’ll end up having two log lines with an explicit tag which was meant to be used once). ThreadLocal container solves this issue nicely by making sure each thread has its own copy of an explicit tag, and no other thread can access it.

    例如,线程A设置了一个显式标签以用于下一次日志记录调用,而线程B则有机会首先访问该显式标签,并有效地利用该标签的意图和目的(但不应这样做)。 之后,它可能有机会在线程A访问它之前清除显式标记(这是错误的,因为线程A需要它),否则它将无法尽快清除它(这也是非法的,因为线程A)将使用显式标签,我们最终将获得两个带有显式标签的日志行,该显式标签本应使用一次)。 ThreadLocal容器通过确保每个线程都有自己的显式标记副本,并且没有其他线程可以访问它,很好地解决了此问题。

    Ok, this definitely was an exciting journey, and I hope you’ve learned a trick or two here. Stay tuned for the next articles!

    好的,这绝对是一个令人兴奋的旅程,希望您在这里学到了一两个技巧。 请继续关注下一篇文章!

    Notes

    笔记

    * Another clever (or should I say non-ordinary instead?) usage of exceptions was in some Python project I’ve heard about a couple of years ago. Unfortunately, I don’t remember the project’s name, but I still remember that the exceptions were being used here to pass the information and control between various parts of a program, so the result looked much more like an event bus, than the mechanism to handle the exceptional situations.

    *另一个聪明的(或者我应该说非常规的)异常用法是在几年前我听说的一些Python项目中。 不幸的是,我不记得项目的名称,但是我仍然记得这里使用了异常在程序的各个部分之间传递信息和控制,因此结果看起来更像是事件总线,而不是机制。处理特殊情况。

    翻译自: https://medium.com/swlh/on-timbers-implicit-tags-selection-19687117dbc7

    隐式链接隐式链接

    Processed: 0.023, SQL: 8