leakcanary shark 库解析 二

1.LeakNodeStatus 三个状态,没用到

internal enum class LeakNodeStatus {
  NOT_LEAKING,
  LEAKING,
  UNKNOWN;
}

2.HeapAnalysisException 封装throwable

class HeapAnalysisException(cause: Throwable) : RuntimeException(cause) {
 
  override fun toString(): String {
    val stringWriter = StringWriter()
    cause!!.printStackTrace(PrintWriter(stringWriter))
    return stringWriter.toString()
  }
 
  companion object {
    private const val serialVersionUID: Long = -2522323377375290608
  }
}

3.AppSingletonInspector App范围内的单例,标记为不泄漏

/**
 * Inspector that automatically marks instances of the provided class names as not leaking
 * because they're app wide singletons.
 * 检查器,自动将提供的类名的实例标记为不泄漏,因为它们是应用程序范围的单例。
 *
 */
class AppSingletonInspector(private vararg val singletonClasses: String) : ObjectInspector {
 
    //检查
    override fun inspect(
            reporter: ObjectReporter//todo ObjectReporter是干哈的
    ) {
        if (reporter.heapObject is HeapInstance) {
            reporter.heapObject.instanceClass
                    .classHierarchy//他自己的类+父类
                    .forEach { heapClass ->
                        if (heapClass.name in singletonClasses) {
                            reporter.notLeakingReasons += "${heapClass.name} is an app singleton"
                        }
                    }
        }
    }
}

4.MetadataExtractor 元数据提取器接口

/**
 * Extracts metadata from a hprof to be reported in [HeapAnalysisSuccess.metadata].
 *
 * This is a functional interface with which you can create a [MetadataExtractor] from a lambda.
 * 从 [HeapAnalysisSuccess.metadata] 分析成功的 hprof 中提取元数据。
 * 这是一个功能接口,您可以使用它从 lambda 创建 [MetadataExtractor]。
 */
fun interface MetadataExtractor {
  fun extractMetadata(graph: HeapGraph): Map<String, String>
 
  companion object {
 
    /**
     * A no-op [MetadataExtractor]
     */
    val NO_OP = MetadataExtractor { emptyMap() }
 
    /**
     * Utility function to create a [MetadataExtractor] from the passed in [block] lambda instead of
     * using the anonymous `object : MetadataExtractor` syntax.
     *
     * Usage:
     *
     * ```kotlin
     * val inspector = MetadataExtractor { graph ->
     *
     * }
     * ```
     */
    inline operator fun invoke(crossinline block: (HeapGraph) -> Map<String, String>): MetadataExtractor =
      object : MetadataExtractor {
        override fun extractMetadata(graph: HeapGraph): Map<String, String> = block(graph)
      }
  }
}

5.LeakingObjectFinder查找泄漏的对象id们

/**
 * Finds the objects that are leaking, for which Shark will compute
 * leak traces.
 *
 * This is a functional interface with which you can create a [LeakingObjectFinder] from a lambda.
 * 查找泄漏的对象,Shark 将为其计算泄漏跟踪。 
 * 这是一个函数式接口,您可以使用它从 lambda 创建 [LeakingObjectFinder]。
 */
fun interface LeakingObjectFinder {
 
  /**
   * For a given heap graph, returns a set of object ids for the objects that are leaking.
   * 对于给定的堆图,返回一组泄漏对象的对象 ID。
   */
  fun findLeakingObjectIds(graph: HeapGraph): Set<Long>
 
  companion object {
    /**
     * Utility function to create a [LeakingObjectFinder] from the passed in [block] lambda
     * instead of using the anonymous `object : LeakingObjectFinder` syntax.
     *
     * Usage:
     *
     * ```kotlin
     * val listener = LeakingObjectFinder {
     *
     * }
     * ```
     */
    inline operator fun invoke(crossinline block: (HeapGraph) -> Set<Long>): LeakingObjectFinder =
      object : LeakingObjectFinder {
        override fun findLeakingObjectIds(graph: HeapGraph): Set<Long> = block(graph)
      }
  }
}

6.FilteringLeakingObjectFinder通过扫描堆转储中的所有对象并将决策委托给[FilteringLeakingObjectFinder.LeakingObjectFilter]列表来查找泄漏的对象

/**
 * Finds the objects that are leaking by scanning all objects in the heap dump
 * and delegating the decision to a list of [FilteringLeakingObjectFinder.LeakingObjectFilter]
 * 通过扫描堆转储中的所有对象并将决策委托给[FilteringLeakingObjectFinder.LeakingObjectFilter]列表来查找泄漏的对象
 */
class FilteringLeakingObjectFinder(private val filters: List<LeakingObjectFilter>) :
  LeakingObjectFinder {
 
  /**
   * Filter to be passed to the [FilteringLeakingObjectFinder] constructor.
   * 要传递给[FilteringLeakingObjectFinder]构造函数的筛选器。
   */
  interface LeakingObjectFilter {
    /**
     * Returns whether the passed in [heapObject] is leaking. This should only return true
     * when we're 100% sure the passed in [heapObject] should not be in memory anymore.
     * 返回传入的[heapObject]是否泄漏。只有当我们100%确定传入的[heapObject]不应再在内存中时,才会返回true。
     */
    fun isLeakingObject(heapObject: HeapObject): Boolean
  }
 
  override fun findLeakingObjectIds(graph: HeapGraph): Set<Long> {
    return graph.objects
      .filter { heapObject ->
        filters.any { filter ->//any表示至少有一个
          filter.isLeakingObject(heapObject)
        }
      }
      .map { it.objectId }
      .toSet()
  }
}

7.ObjectInspector 对象检查员为 LeakCanary 提供 堆中对象(类、实例和数组)更多的信息。

package shark
 
/**
 * Provides LeakCanary with insights about objects (classes, instances and arrays) found in the
 * heap. [inspect] will be called for each object that LeakCanary wants to know more about.
 * The implementation can then use the provided [ObjectReporter] to provide insights for that
 * object.
 *
 * 对象检查员 为 LeakCanary 提供 堆中对象(类、实例和数组)更多的信息。
 * LeakCanary可以调用[inspect]方法,了解对象更多的信息。
 * 实现类可以使用提供的 [ObjectReporter] 来提供该对象的信息。
 *
 * This is a functional interface with which you can create a [ObjectInspector] from a lambda.
 */
fun interface ObjectInspector {
 
  /**
   * @see [ObjectInspector]
   */
  fun inspect(reporter: ObjectReporter)// ObjectReporter里边保存着label,leakingReasons,notLeakingReasons
 
  companion object {
    /**
     * Utility function to create a [ObjectInspector] from the passed in [block] lambda instead of
     * using the anonymous `object : OnHeapAnalyzedListener` syntax.
     *
     * Usage:
     *
     * ```kotlin
     * val inspector = ObjectInspector { reporter ->
     *
     * }
     * ```
     */
    inline operator fun invoke(crossinline block: (ObjectReporter) -> Unit): ObjectInspector =
      object : ObjectInspector {
        override fun inspect(
          reporter: ObjectReporter
        ) {
          block(reporter)
        }
      }
  }
}

8.OnAnalysisProgressListener将 [HeapAnalyzer] 的进度报告为 [Step] 值。

/**
 * Reports progress from the [HeapAnalyzer] as they occur, as [Step] values.
 * 将 [HeapAnalyzer] 的进度报告为 [Step] 值。
 *
 * This is a functional interface with which you can create a [OnAnalysisProgressListener] from a lambda.
 */
fun interface OnAnalysisProgressListener {
 
  // These steps are defined in the order in which they occur.
  //这些步骤按它们发生的顺序定义。
  enum class Step {
    PARSING_HEAP_DUMP,        //解析dump
    EXTRACTING_METADATA,      //提取metadata
    FINDING_RETAINED_OBJECTS, //retained 保留
    FINDING_PATHS_TO_RETAINED_OBJECTS,//寻找路径到保留对象
    FINDING_DOMINATORS,               //寻找统治者
    INSPECTING_OBJECTS,               //检查对象
    COMPUTING_NATIVE_RETAINED_SIZE,   //计算原生保留大小
    COMPUTING_RETAINED_SIZE,//计算保留大小
    BUILDING_LEAK_TRACES,   //建立泄漏痕迹
    REPORTING_HEAP_ANALYSIS //报告堆分析
  }
 
  fun onAnalysisProgress(step: Step)
 
  companion object {
 
    /**
     * A no-op [OnAnalysisProgressListener]
     */
    val NO_OP = OnAnalysisProgressListener {}
 
    /**
     * Utility function to create a [OnAnalysisProgressListener] from the passed in [block] lambda
     * instead of using the anonymous `object : OnAnalysisProgressListener` syntax.
     *
     * Usage:
     *
     * ```kotlin
     * val listener = OnAnalysisProgressListener {
     *
     * }
     * ```
     */
    inline operator fun invoke(crossinline block: (Step) -> Unit): OnAnalysisProgressListener =
      object : OnAnalysisProgressListener {
        override fun onAnalysisProgress(step: Step) {
          block(step)
        }
      }
  }
}

9.LeakTraceReference封装LeakTraceObject

/**
 * A [LeakTraceReference] represents and origin [LeakTraceObject] and either a reference from that
 * object to the [LeakTraceObject] in the next [LeakTraceReference] in [LeakTrace.referencePath],
 * or to [LeakTrace.leakingObject] if this is the last [LeakTraceReference] in
 * [LeakTrace.referencePath].
 *
 * [LeakTraceReference] 封装LeakTraceObject
 */
data class LeakTraceReference(
  val originObject: LeakTraceObject,
 
  val referenceType: ReferenceType,
 
  val owningClassName: String,
 
  val referenceName: String
 
) : Serializable {
 
  enum class ReferenceType {
    INSTANCE_FIELD,
    STATIC_FIELD,
    LOCAL,
    ARRAY_ENTRY
  }
 
  /**
   * Returns {@link #className} without the package, ie stripped of any string content before the
   * last period (included).
   */
  val owningClassSimpleName: String get() = owningClassName.lastSegment('.')
 
  val referenceDisplayName: String
    get() {
      return when (referenceType) {
        ARRAY_ENTRY -> "[$referenceName]"
        STATIC_FIELD, INSTANCE_FIELD -> referenceName
        LOCAL -> "<Java Local>"
      }
    }
 
  val referenceGenericName: String//Generic通用的
    get() {
      return when (referenceType) {
        // The specific array index in a leak rarely matters, this improves grouping.
        ARRAY_ENTRY -> "[x]"
        STATIC_FIELD, INSTANCE_FIELD -> referenceName
        LOCAL -> "<Java Local>"
      }
    }
 
  companion object {
    private const val serialVersionUID = 1L
  }
}

10.ObjectReporter为ObjectInspector对象检查员提供heapObject相关的信息 , 一个给定的 [ObjectReporter] 只映射到堆中的一个对象,但被许多 [ObjectInspector] 实现共享并积累洞察力。

/**
 * Enables [ObjectInspector] implementations to provide insights on [heapObject], which is
 * an object (class, instance or array) found in the heap.
 *
 * A given [ObjectReporter] only maps to one object in the heap, but is shared to many
 * [ObjectInspector] implementations and accumulates insights.
 *
 * 为ObjectInspector对象检查员提供heapObject相关的信息
 * 一个给定的 [ObjectReporter] 只映射到堆中的一个对象,但被许多 [ObjectInspector] 实现共享并积累洞察力。
 */
class ObjectReporter constructor(val heapObject: HeapObject) {
 
  /**
   * Labels that will be visible on the corresponding [heapObject] in the leak trace.
   * 在泄漏跟踪中相应的 [heapObject] 上可见的标签
   */
  val labels = linkedSetOf<String>()
 
  /**
   * Reasons for which this object is expected to be unreachable (ie it's leaking).
   * 预期此对象无法访问的原因(即它正在泄漏)。
   */
  val leakingReasons = mutableSetOf<String>()
 
  /**
   * Deprecated, use leakingReasons instead.
   */
  @Deprecated(
    "Replace likelyLeakingReasons with leakingReasons",
    replaceWith = ReplaceWith(
      "leakingReasons"
    )
  )
  val likelyLeakingReasons
    get() = leakingReasons
 
  /**
   * Reasons for which this object is expected to be reachable (ie it's not leaking).
   * 预期此对象可达的原因(即它没有泄漏)。
   */
  val notLeakingReasons = mutableSetOf<String>()
 
  /**
   * Runs [block] if [ObjectReporter.heapObject] is an instance of [expectedClass].
   * 如果 [ObjectReporter.heapObject] 是 [expectedClass] 的实例,则运行 [block]。
   */
  fun whenInstanceOf(
    expectedClass: KClass<out Any>,
    block: ObjectReporter.(HeapInstance) -> Unit
  ) {
    whenInstanceOf(expectedClass.java.name, block)
  }
 
  /**
   * Runs [block] if [ObjectReporter.heapObject] is an instance of [expectedClassName].
   * 如果 [ObjectReporter.heapObject] 是 [expectedClass] 的实例,则运行 [block]。
   */
  fun whenInstanceOf(
    expectedClassName: String,
    block: ObjectReporter.(HeapInstance) -> Unit
  ) {
    val heapObject = heapObject
    if (heapObject is HeapInstance && heapObject instanceOf expectedClassName) {
      block(heapObject)
    }
  }
}

11.ReferenceMatcher用于模式匹配堆中已知的引用模式,要么忽略它们([IgnoredReferenceMatcher]), 要么将它们标记为库泄漏([LibraryLeakReferenceMatcher])。

/**
 * Used to pattern match known patterns of references in the heap, either to ignore them
 * ([IgnoredReferenceMatcher]) or to mark them as library leaks ([LibraryLeakReferenceMatcher]).
 * 用于模式匹配堆中已知的引用模式,要么忽略它们([IgnoredReferenceMatcher]),
 * 要么将它们标记为库泄漏([LibraryLeakReferenceMatcher])。
 */
sealed class ReferenceMatcher {
 
  /** The pattern that references will be matched against.
   * 引用将匹配的模式 */
  abstract val pattern: ReferencePattern
}
 
/**
 * [LibraryLeakReferenceMatcher] should be used to match references in library code that are
 * known to create leaks and are beyond your control. The shortest path finder will only go
 * through matching references after it has exhausted references that don't match, prioritizing
 * finding an application leak over a known library leak. Library leaks will be reported as
 * [LibraryLeak] instead of [ApplicationLeak].
 * [LibraryLeakReferenceMatcher] 应该用于匹配库代码中已知会造成泄漏并且超出您控制范围的引用。
 * 最短路径查找器只会在耗尽不匹配的引用后才通过匹配的引用,优先查找应用程序泄漏而不是已知库泄漏。
 * 库泄漏将报告为 [LibraryLeak] 而不是 [ApplicationLeak]。
 */
data class LibraryLeakReferenceMatcher(
  override val pattern: ReferencePattern,
  /**
   * A description that conveys what we know about this library leak.
   */
  val description: String = "",
  /**
   * Whether the identified leak may exist in the provided [HeapGraph]. Defaults to true. If
   * the heap dump comes from a VM that runs a different version of the library that doesn't
   * have the leak, then this should return false.
   * 不同虚拟机可能不用,如果一个虚拟机不可能出现这个问题,则返回false
   */
  val patternApplies: (HeapGraph) -> Boolean = { true }
) : ReferenceMatcher() {
  override fun toString() = "library leak: $pattern"
}
 
/**
 * [IgnoredReferenceMatcher] should be used to match references that cannot ever create leaks. The
 * shortest path finder will never go through matching references.
 * [IgnoredReferenceMatcher] 应该用于匹配永远不会造成泄漏的引用。
 * 最短路径查找器永远不会通过匹配的引用。
 */
class IgnoredReferenceMatcher(override val pattern: ReferencePattern) : ReferenceMatcher() {
  override fun toString() = "ignored ref: $pattern"
}

12.KeyedWeakReferenceFinder查找所有的KeyedWeakReference对应的id

/**
 * Finds all objects tracked by a KeyedWeakReference, ie all objects that were passed to
 * ObjectWatcher.watch.
 * 查找所有的KeyedWeakReference对应的id
 */
object KeyedWeakReferenceFinder : LeakingObjectFinder {
 
  override fun findLeakingObjectIds(graph: HeapGraph): Set<Long> =
    findKeyedWeakReferences(graph)
      .filter { it.hasReferent && it.isRetained }
            //hasReferent对象存在,并且保留了一段时间
            //isRetained保持; 持有; 保留; 继续拥有;
      .map { it.referent.value }
      .toSet()
 
  //获取heap dump的时间
  fun heapDumpUptimeMillis(graph: HeapGraph): Long? {
    return graph.context.getOrPut("heapDumpUptimeMillis") {
      val keyedWeakReferenceClass = graph.findClassByName("leakcanary.KeyedWeakReference")
      val heapDumpUptimeMillis = if (keyedWeakReferenceClass == null) {
        null
      } else {
        keyedWeakReferenceClass["heapDumpUptimeMillis"]?.value?.asLong
      }
      if (heapDumpUptimeMillis == null) {
        SharkLog.d {
          "leakcanary.KeyedWeakReference.heapDumpUptimeMillis field not found"
        }
      }
      heapDumpUptimeMillis
    }
  }
 
  internal fun findKeyedWeakReferences(graph: HeapGraph): List<KeyedWeakReferenceMirror> {
    return graph.context.getOrPut(KEYED_WEAK_REFERENCE.name) {//todo 这里为什么getOrPut
      val keyedWeakReferenceClass = graph.findClassByName("leakcanary.KeyedWeakReference")
 
      val keyedWeakReferenceClassId = keyedWeakReferenceClass?.objectId ?: 0
      val legacyKeyedWeakReferenceClassId =
        graph.findClassByName("com.squareup.leakcanary.KeyedWeakReference")?.objectId ?: 0
 
      val heapDumpUptimeMillis = heapDumpUptimeMillis(graph)
 
      val addedToContext: List<KeyedWeakReferenceMirror> = graph.instances
        .filter { instance ->
          //过滤所有的instance,找到leakcanary.KeyedWeakReference或者com.squareup.leakcanary.KeyedWeakReference的对象实例
          instance.instanceClassId == keyedWeakReferenceClassId || instance.instanceClassId == legacyKeyedWeakReferenceClassId
        }
        .map {
          //封装为KeyedWeakReferenceMirror
          KeyedWeakReferenceMirror.fromInstance(
            it, heapDumpUptimeMillis
          )
        }
        .toList()
      graph.context[KEYED_WEAK_REFERENCE.name] = addedToContext
      addedToContext
    }
  }
}

13.ReferencePattern将匹配给定 [ReferenceMatcher] 的引用的模式

/**
 * A pattern that will match references for a given [ReferenceMatcher].
 * 将匹配给定 [ReferenceMatcher] 的引用的模式。
 */
sealed class ReferencePattern : Serializable {
 
  /**
   * Matches local references held in the stack of frames of a given thread, identified by its name.
   * 匹配保存在给定线程的帧堆栈中的本地引用,由其名称标识。
   */
  data class JavaLocalPattern(
    val threadName: String
  ) : ReferencePattern() {
    override fun toString() = "local variable on thread $threadName"
 
    companion object {
      private const val serialVersionUID: Long = -8985446122829543654
    }
  }
 
  /**
   * Matches static field references, identified by [className] and [fieldName].
   * 匹配由 [className] 和 [fieldName] 标识的静态字段引用。
   */
  data class StaticFieldPattern(
    val className: String,
    val fieldName: String
  ) : ReferencePattern() {
    override fun toString() = "static field $className#$fieldName"
 
    companion object {
      private const val serialVersionUID: Long = 7656908128775899611
    }
  }
 
  /**
   * Matches instances field references, identified by [className] and [fieldName].
   * 匹配实例字段引用,由 [className] 和 [fieldName] 标识。
   *
   * Note: If [fieldName] is declared in a superclass it will still match for subclasses.
   * This is to support overriding of rules for specific cases. If two [ReferenceMatcher] match for
   * the same [fieldName] but for different [className] in a class hierarchy, then the closest
   * class in the hierarchy wins.
   * 注意:如果 [fieldName] 在超类中声明,它仍然会匹配子类。
   * 这是为了支持在特定情况下覆盖规则。 如果两个 [ReferenceMatcher] 匹配同一个 [fieldName]
   * 但对于类层次结构中的不同 [className],则层次结构中最接近的类获胜。
   */
  data class InstanceFieldPattern(
    val className: String,
    val fieldName: String
  ) : ReferencePattern() {
    override fun toString() = "instance field $className#$fieldName"
 
    companion object {
      private const val serialVersionUID: Long = 6649791455204159802
    }
  }
 
  /**
   * Matches native global variables (also known as jni global gc roots) that reference
   * Java objects. The class name will match against classes, instances and object arrays with
   * a matching class name.
   * 匹配引用 Java 对象的本地全局变量(也称为 jni 全局 gc 根)。
   * 类名将与具有匹配类名的类、实例和对象数组相匹配。
   * todo ??
   */
  data class NativeGlobalVariablePattern(val className: String) : ReferencePattern() {
    override fun toString() = "native global variable referencing $className"
 
    companion object {
      private const val serialVersionUID: Long = -2651328076202244933
    }
  }
 
  companion object {
    private const val serialVersionUID: Long = -5113635523713591133
  }
}

14.LeakTraceObject 代表泄漏的对象

data class LeakTraceObject(
  val type: ObjectType,
  /**
   * Class name of the object.
   * The class name format is the same as what would be returned by [Class.getName].
   * 对象的类名。类名格式和[Class.getName]返回的一样。
   */
  val className: String,
 
  /**
   * Labels that were computed during analysis. A label provides extra information that helps
   * understand the state of the leak trace object.
   * 在分析期间计算的标签。 标签提供了有助于了解泄漏跟踪对象状态的额外信息。
   */
  val labels: Set<String>,
  val leakingStatus: LeakingStatus,
  val leakingStatusReason: String,
  /**
   * The minimum number of bytes which would be freed if all references to this object were
   * released. Not null only if the retained heap size was computed AND [leakingStatus] is
   * equal to [LeakingStatus.UNKNOWN] or [LeakingStatus.LEAKING].
   * 如果对该对象的所有引用都被释放,则将被释放的最小字节数。
   * 仅当计算保留堆大小且 [leakingStatus] 等于 [LeakingStatus.UNKNOWN] 或 [LeakingStatus.LEAKING] 时才不为 null。
   */
  val retainedHeapByteSize: Int?,
  /**
   * The minimum number of objects which would be unreachable if all references to this object were
   * released. Not null only if the retained heap size was computed AND [leakingStatus] is
   * equal to [LeakingStatus.UNKNOWN] or [LeakingStatus.LEAKING].
   *
   * 如果对该对象的所有引用都被释放,则无法访问的最小对象数。
   * 仅当计算保留堆大小且 [leakingStatus] 等于 [LeakingStatus.UNKNOWN] 或 [LeakingStatus.LEAKING] 时才不为 null。
   */
  val retainedObjectCount: Int?
) : Serializable {
 
  /**
   * Returns {@link #className} without the package, ie stripped of any string content before the
   * last period (included).
   */
  val classSimpleName: String get() = className.lastSegment('.')
 
  val typeName
    get() = type.name.toLowerCase(Locale.US)
 
  override fun toString(): String {
    val firstLinePrefix = ""
    val additionalLinesPrefix = "$ZERO_WIDTH_SPACE  "
    return toString(firstLinePrefix, additionalLinesPrefix, true)
  }
 
  internal fun toString(
    firstLinePrefix: String,
    additionalLinesPrefix: String,
    showLeakingStatus: Boolean,
    typeName: String = this.typeName
  ): String {
    val leakStatus = when (leakingStatus) {
      UNKNOWN -> "UNKNOWN"
      NOT_LEAKING -> "NO ($leakingStatusReason)"
      LEAKING -> "YES ($leakingStatusReason)"
    }
 
    var result = ""
    result += "$firstLinePrefix$className $typeName"
    if (showLeakingStatus) {
      result += "\n${additionalLinesPrefix}Leaking: $leakStatus"
    }
 
    if (retainedHeapByteSize != null) {
      val humanReadableRetainedHeapSize =
        humanReadableByteCount(retainedHeapByteSize.toLong())
      result += "\n${additionalLinesPrefix}Retaining $humanReadableRetainedHeapSize in $retainedObjectCount objects"
    }
    for (label in labels) {
      result += "\n${additionalLinesPrefix}$label"
    }
    return result
  }
 
  enum class ObjectType {
    CLASS,
    ARRAY,
    INSTANCE
  }
 
  enum class LeakingStatus {
    /** The object was needed and therefore expected to be reachable.
     * 该对象是需要的,因此预计是可达的。 */
    NOT_LEAKING,
 
    /** The object was no longer needed and therefore expected to be unreachable.
     * 不再需要该对象,因此预计将无法访问该对象。 */
    LEAKING,
 
    /** No decision can be made about the provided object. */
    UNKNOWN;
  }
 
  companion object {
    private const val serialVersionUID = -3616216391305196341L
 
    // https://stackoverflow.com/a/3758880
    //将bytes long型转为可读的B,kB,MB,GB
    private fun humanReadableByteCount(bytes: Long): String {
      val unit = 1000
      if (bytes < unit) return "$bytes B"
      val exp = (ln(bytes.toDouble()) / ln(unit.toDouble())).toInt()
      val pre = "kMGTPE"[exp - 1]
      return String.format("%.1f %sB", bytes / unit.toDouble().pow(exp.toDouble()), pre)
    }
  }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容