Improving perf
diff --git a/leakcanary-android-instrumentation/src/androidTest/java/leakcanary/Api23Test.kt b/leakcanary-android-instrumentation/src/androidTest/java/leakcanary/Api23Test.kt
index a038763..5840d1b 100644
--- a/leakcanary-android-instrumentation/src/androidTest/java/leakcanary/Api23Test.kt
+++ b/leakcanary-android-instrumentation/src/androidTest/java/leakcanary/Api23Test.kt
@@ -4,13 +4,17 @@
 import androidx.test.platform.app.InstrumentationRegistry
 import java.io.File
 import java.io.FileOutputStream
+import leakcanary.internal.friendly.measureDurationMillis
 import org.junit.Test
 import shark.AndroidMetadataExtractor
 import shark.AndroidObjectInspectors
 import shark.AndroidReferenceMatchers
+import shark.DualSourceProvider
+import shark.FileSourceProvider
 import shark.HeapAnalyzer
 import shark.HprofHeapGraph.Companion.openHeapGraph
 import shark.KeyedWeakReferenceFinder
+import shark.RandomAccessFileSourceProvider
 import shark.SharkLog
 
 class Api23Test {
@@ -28,9 +32,22 @@
     context.assets.open("api_23_dump.hprof")
       .copyTo(FileOutputStream(heapDumpFile))
 
+
+    GcTrigger.Default.runGc()
+    val fileChannelDuration = measureDurationMillis {
+      FileSourceProvider(heapDumpFile).runAnalysis()
+    }
+    GcTrigger.Default.runGc()
+    val randomAccessFileDuration = measureDurationMillis {
+      RandomAccessFileSourceProvider(heapDumpFile).runAnalysis()
+    }
+    SharkLog.d { "fileChannelDuration: $fileChannelDuration ms randomAccessFileDuration: $randomAccessFileDuration ms" }
+  }
+
+  private fun DualSourceProvider.runAnalysis() {
     Trace.beginSection("Analysis")
     Trace.beginSection("Open graph ")
-    heapDumpFile.openHeapGraph()
+    openHeapGraph()
       .use { graph ->
         val analyzer = HeapAnalyzer {
           Trace.endSection()
@@ -39,7 +56,7 @@
         }
 
         val result = analyzer.analyze(
-          heapDumpFile = heapDumpFile,
+          heapDumpFile = File("who_cares"),
           graph = graph,
           leakingObjectFinder = KeyedWeakReferenceFinder,
           referenceMatchers = AndroidReferenceMatchers.appDefaults,
diff --git a/shark-hprof/src/main/java/shark/RandomAccessFileSourceProvider.kt b/shark-hprof/src/main/java/shark/RandomAccessFileSourceProvider.kt
new file mode 100644
index 0000000..3c54b15
--- /dev/null
+++ b/shark-hprof/src/main/java/shark/RandomAccessFileSourceProvider.kt
@@ -0,0 +1,54 @@
+package shark
+
+import java.io.File
+import java.io.RandomAccessFile
+import kotlin.math.min
+import okio.Buffer
+import okio.BufferedSource
+import okio.Okio
+
+class RandomAccessFileSourceProvider(private val file: File) : DualSourceProvider {
+  override fun openStreamingSource(): BufferedSource = Okio.buffer(Okio.source(file.inputStream()))
+
+  override fun openRandomAccessSource(): RandomAccessSource {
+
+    val randomAccessFile = RandomAccessFile(file, "r")
+
+    val arrayBuffer = ByteArray(500_000)
+
+    return object : RandomAccessSource {
+      override fun read(
+        sink: Buffer,
+        position: Long,
+        byteCount: Long
+      ): Long {
+        val byteCountInt = byteCount.toInt()
+        randomAccessFile.seek(position)
+        var totalBytesRead = 0
+        val maxRead = arrayBuffer.size
+        var toRead: Int = min(byteCountInt, maxRead)
+        while (totalBytesRead < byteCount) {
+          val bytesRead = randomAccessFile.read(arrayBuffer, 0, toRead)
+          if (bytesRead == -1) {
+            check(totalBytesRead != 0) {
+              "Didn't read nothing"
+            }
+            break
+          }
+          sink.write(arrayBuffer, 0, bytesRead)
+          totalBytesRead += bytesRead
+          toRead = min(byteCountInt - totalBytesRead, maxRead)
+        }
+        return totalBytesRead.toLong()
+      }
+
+      override fun close() {
+        try {
+          randomAccessFile.close()
+        } catch (ignored: Throwable) {
+          SharkLog.d(ignored) { "Failed to close file, ignoring" }
+        }
+      }
+    }
+  }
+}