From a909f8ac05e7c2e4525d0d1ebb5a59c63a89129e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E4=B8=9C?= Date: Thu, 27 Dec 2018 12:05:48 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E5=86=85=E5=AD=98=E5=BF=AB=E7=85=A7?= =?UTF-8?q?=E5=88=86=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 6 +- .../hprofanalyze/ExampleInstrumentedTest.kt | 24 +++ .../java/com/ld/hprofanalyze/TestActivity.kt | 156 ++++++++++++++++++ hprofanalyze/src/main/res/drawable/test.png | Bin 0 -> 955 bytes .../src/main/res/layout/activity_main2.xml | 18 ++ .../src/main/res/layout/activity_test.xml | 68 ++++++++ .../com/ld/hprofanalyze/ExampleUnitTest.kt | 17 ++ settings.gradle | 2 +- 8 files changed, 288 insertions(+), 3 deletions(-) create mode 100644 hprofanalyze/src/androidTest/java/com/ld/hprofanalyze/ExampleInstrumentedTest.kt create mode 100644 hprofanalyze/src/main/java/com/ld/hprofanalyze/TestActivity.kt create mode 100644 hprofanalyze/src/main/res/drawable/test.png create mode 100644 hprofanalyze/src/main/res/layout/activity_main2.xml create mode 100644 hprofanalyze/src/main/res/layout/activity_test.xml create mode 100644 hprofanalyze/src/test/java/com/ld/hprofanalyze/ExampleUnitTest.kt diff --git a/build.gradle b/build.gradle index cdf1d5d..4338523 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,8 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. -buildscript { +buildscript { + ext.kotlin_version = '1.3.11' + repositories { google() jcenter() @@ -11,7 +13,7 @@ buildscript { // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files - } + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } allprojects { diff --git a/hprofanalyze/src/androidTest/java/com/ld/hprofanalyze/ExampleInstrumentedTest.kt b/hprofanalyze/src/androidTest/java/com/ld/hprofanalyze/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..d09dd8e --- /dev/null +++ b/hprofanalyze/src/androidTest/java/com/ld/hprofanalyze/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.ld.hprofanalyze + +import android.support.test.InstrumentationRegistry +import android.support.test.runner.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getTargetContext() + assertEquals("com.ld.hprofanalyze", appContext.packageName) + } +} diff --git a/hprofanalyze/src/main/java/com/ld/hprofanalyze/TestActivity.kt b/hprofanalyze/src/main/java/com/ld/hprofanalyze/TestActivity.kt new file mode 100644 index 0000000..cf701ed --- /dev/null +++ b/hprofanalyze/src/main/java/com/ld/hprofanalyze/TestActivity.kt @@ -0,0 +1,156 @@ +package com.ld.hprofanalyze + +import android.Manifest +import android.content.pm.PackageManager +import android.graphics.BitmapFactory +import android.support.v7.app.AppCompatActivity +import android.os.Bundle +import android.os.Debug +import android.os.Environment +import android.support.v4.app.ActivityCompat +import android.support.v4.content.ContextCompat +import com.squareup.haha.perflib.io.MemoryMappedFileBuffer +import kotlinx.android.synthetic.main.activity_test.* +import java.io.File +import java.util.* +import kotlin.collections.ArrayList +import android.util.Log +import com.squareup.haha.perflib.* +import java.lang.StringBuilder +import java.lang.reflect.Array + + +private const val WRITE_EXTERNAL_STORAGE_REQUEST_CODE = 100 +private const val TAG = "TestActivity" + +class TestActivity : AppCompatActivity() { + private var externalReportPath: File? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_test) + if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { + ActivityCompat.requestPermissions( + this, + arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), + WRITE_EXTERNAL_STORAGE_REQUEST_CODE) + } else { + initExternalReportPath() + afterPermissionGrant() + + } + + } + + override fun onRequestPermissionsResult( + requestCode: Int, permissions: kotlin.Array, grantResults: IntArray) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + initExternalReportPath() + afterPermissionGrant() + } + + private fun initExternalReportPath() { + externalReportPath = File(Environment.getExternalStorageDirectory(), "hprofdir") + if (externalReportPath?.exists() ?: false) { + externalReportPath?.mkdirs() + } + + } + + private fun afterPermissionGrant() { +// 1、构造内存泄漏(重复加载同一张图片) + var bitmap1 = BitmapFactory.decodeResource(resources, R.drawable.test) + image1.setImageBitmap(bitmap1) + var bitmap2 = BitmapFactory.decodeResource(resources, R.drawable.test) + image2.setImageBitmap(bitmap2) + var bitmap3 = BitmapFactory.decodeResource(resources, R.drawable.test) + image3.setImageBitmap(bitmap3) + var bitmap4 = BitmapFactory.decodeResource(resources, R.drawable.test) + image4.setImageBitmap(bitmap4) + dump.setOnClickListener { + Debug.dumpHprofData(externalReportPath?.absolutePath) + } + parse.setOnClickListener { + // 2、Haha库使用,生成内存快照。 + var memoryMappedFileBuffer = MemoryMappedFileBuffer(externalReportPath) + var hprofParser = HprofParser(memoryMappedFileBuffer) + var snapshot = hprofParser.parse() + snapshot.computeDominators() + var heaps = snapshot.heaps + heaps = heaps.filter { + it.name.equals("app") || it.name.equals("default") + } +// 3、获取bitmap相关的对象的内存快照。 + var bitmapClass = snapshot.findClass("android.graphics.Bitmap") + var bitmaplist = arrayListOf() + heaps.forEach { + bitmaplist.addAll(bitmapClass.getHeapInstances(it.id)) + } + var bitmapMapToBuffer = mutableMapOf() +// 4、构建map key:bitmap内存映射对象,value:bitmap Class 的mbuffer字段值 + bitmaplist.forEach { + (it as ClassInstance).values.forEach { buffer -> + if (buffer.field.name.equals("mBuffer")) { + bitmapMapToBuffer[it] = buffer.value as ArrayInstance + } + } + } +// 5、构建map key:步骤4中的mbuffer字段的数组的hashcode,value:拥有相同hashcode的Instance 集合。 + var result = mutableMapOf>() + bitmapMapToBuffer.forEach { instance, byteBuffer -> + var hashCode = Arrays.hashCode(byteBuffer.values) + if (result.containsKey(hashCode)) { + result[hashCode]?.add(instance) + } else { + result[hashCode] = arrayListOf(instance) + } + } +// 6、过滤步骤5中map,如果value(list)的size大于1,就表示有多个instance的mbuffer是一样的,就可以被认为是内存泄漏了。 + var filterResult = result.filter { + it.component2().size > 1 + } + Log.d(TAG, "发现重复bitmap 数量为 ${filterResult.size}") +// 7、获取bitmap的宽高和buffer长度 + filterResult.forEach { hashcode, instances -> + var leak = LeakObjectDetaial() + instances.forEach { + (it as ClassInstance).values.forEach { fieldValue -> + leak.leakCount++ + when (fieldValue.field.name) { + "mWidth" -> leak.width = fieldValue.value as Int + "mHeight" -> leak.height = fieldValue.value as Int + "mBuffer" -> leak.bufferSize = (fieldValue.value as ArrayInstance).values.size + } + } +// 8、获取bitmap的引用链。 + var stackFrame = StringBuilder() + stackFrame.append(it.classObj.className).append("\n") + var nextInstanceToGcRoot = it.nextInstanceToGcRoot + while (nextInstanceToGcRoot != null) { + stackFrame.insert(0, nextInstanceToGcRoot.classObj.className + "\n") + nextInstanceToGcRoot = nextInstanceToGcRoot.nextInstanceToGcRoot + } + leak.stacks = stackFrame.toString() + Log.d(TAG, "width:${leak.width},height:${leak.height},buffersize:${leak.bufferSize},stack:${leak.stacks}") + /*var stackField = it::class.java.superclass.getDeclaredField("mStack") + stackField.isAccessible = true + var stackValue: StackTrace = stackField.get(it) as StackTrace + var stackFrameField = stackValue::class.java.getField("mFrames") + stackFrameField.isAccessible = true + var stackFrameArray = stackFrameField.get(stackValue) + var stackLength = Array.getLength(stackFrameArray) +*/ + } + } + } + } + + class LeakObjectDetaial { + var leakCount: Int = 0 + var stacks: String = "" + var width: Int = 0 + var height: Int = 0 + var bufferSize: Int = 0 + } + +} diff --git a/hprofanalyze/src/main/res/drawable/test.png b/hprofanalyze/src/main/res/drawable/test.png new file mode 100644 index 0000000000000000000000000000000000000000..c6d00dc529466210b732e53969f81f1dfa7f57dc GIT binary patch literal 955 zcmV;s14R6ZP)8S%5Xbjvcnm6Npvk3$8cLo3o*@m2ln5zOB+yA9A&L|{0DZ2ZKv6nA5;!416p_&4 z|KKrutDUuXcg{XLXK9qk_Rh|4=fn1_&Y0@BaqIqrNBeIVPhKo+j$rKW!DqXva_8`) z1*2iuTu{iWnNVl-eU4IF}(SRK-a4u{N_}=j+(tGm$m;G>YWtYGISU&JS z&fyyF!M(i!m%w7IulV8Xe~?%I`C1R$i!m6xR^Sp?h-)Cc^z!s)5@-kOF<4wfZo+Wf>yE=Y zTx%PzGg}HO*$Oe|en!jjDfuTeCc%X33)N4vSAbrtr zhoS^l`aEo&1VaF0rKJWyF0=&8>CzOdY~WYlZMM|U_KZc;(gDTLhArK(A=6S z-D=_XE?zP;q4j{hLYA|~$(81{5oyQ do(3Dk{sp9m#QG<=2o(ST002ovPDHLkV1kEI#RLEV literal 0 HcmV?d00001 diff --git a/hprofanalyze/src/main/res/layout/activity_main2.xml b/hprofanalyze/src/main/res/layout/activity_main2.xml new file mode 100644 index 0000000..e696c9d --- /dev/null +++ b/hprofanalyze/src/main/res/layout/activity_main2.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/hprofanalyze/src/main/res/layout/activity_test.xml b/hprofanalyze/src/main/res/layout/activity_test.xml new file mode 100644 index 0000000..4f01a1e --- /dev/null +++ b/hprofanalyze/src/main/res/layout/activity_test.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + +