工具版本
android studio 3.6.1 Eclipse Memory Analyzer Version 1.9.2
模拟内存泄露
Android studio建立工程,把下面的自定义view放入项目Activity中,反复进入退出界面
package com. enjoy. memory. widget;
import android. animation. ValueAnimator;
import android. content. Context;
import android. graphics. Canvas;
import android. graphics. Color;
import android. graphics. Paint;
import android. graphics. Path;
import android. util. AttributeSet;
import android. view. View;
import android. view. animation. LinearInterpolator;
public class IOSStyleLoadingView extends View {
private final Context context;
private double radius;
private double insideRadius;
private float northwestXStart;
private float northwestYStart;
private float northXStart;
private float northYStart;
private float notheastXStart;
private float notheastYStart;
private float eastXStart;
private float eastYStart;
private float southeastXStart;
private float southeastYStart;
private float southXStart;
private float southYStart;
private float southwestXStart;
private float southwestYStart;
private float westXStart;
private float westYStart;
private float northwestXEnd;
private float northwestYEnd;
private float northXEnd;
private float northYEnd;
private float notheastXEnd;
private float notheastYEnd;
private float eastXEnd;
private float eastYEnd;
private float southeastXEnd;
private float southeastYEnd;
private float southXEnd;
private float southYEnd;
private float southwestXEnd;
private float southwestYEnd;
private float westXEnd;
private float westYEnd;
private int currentColor = 7 ;
String color[ ] = new String [ ] {
"#a5a5a5" ,
"#b7b7b7" ,
"#c0c0c0" ,
"#c9c9c9" ,
"#d2d2d2" ,
"#dbdbdb" ,
"#e4e4e4" ,
"#e4e4e4"
} ;
int[ ] colors = new int [ 8 ] ;
public IOSStyleLoadingView ( Context context) {
this ( context, null , 0 ) ;
}
public IOSStyleLoadingView ( Context context, AttributeSet attrs) {
this ( context, null , 0 ) ;
}
public IOSStyleLoadingView ( Context context, AttributeSet attrs, int defStyleAttr) {
super ( context, attrs, defStyleAttr) ;
this . context = context;
radius = UIKits. dip2Px ( context, 9 ) ;
insideRadius = UIKits. dip2Px ( context, 5 ) ;
for ( int i = 0 ; i < color. length; i++ ) {
colors[ i] = Color. parseColor ( color[ i] ) ;
}
paint. setAntiAlias ( true ) ;
paint. setStrokeWidth ( UIKits. dip2Px ( context, 2 ) ) ;
paint. setStyle ( Paint. Style. STROKE ) ;
paint. setStrokeCap ( Paint. Cap. ROUND ) ;
}
Path path = new Path ( ) ;
Paint paint = new Paint ( ) ;
@Override
protected void onDraw ( Canvas canvas) {
super . onDraw ( canvas) ;
path. reset ( ) ;
paint. setColor ( colors[ currentColor++ % 8 ] ) ;
path. moveTo ( northwestXStart, northwestYStart) ;
path. lineTo ( northwestXEnd, northwestYEnd) ;
canvas. drawPath ( path, paint) ;
path. reset ( ) ;
paint. setColor ( colors[ currentColor++ % 8 ] ) ;
path. moveTo ( northXStart, northYStart) ;
path. lineTo ( northXEnd, northYEnd) ;
canvas. drawPath ( path, paint) ;
path. reset ( ) ;
paint. setColor ( colors[ currentColor++ % 8 ] ) ;
path. moveTo ( notheastXStart, notheastYStart) ;
path. lineTo ( notheastXEnd, notheastYEnd) ;
canvas. drawPath ( path, paint) ;
path. reset ( ) ;
paint. setColor ( colors[ currentColor++ % 8 ] ) ;
path. moveTo ( eastXStart, eastYStart) ;
path. lineTo ( eastXEnd, eastYEnd) ;
canvas. drawPath ( path, paint) ;
path. reset ( ) ;
paint. setColor ( colors[ currentColor++ % 8 ] ) ;
path. moveTo ( southeastXStart, southeastYStart) ;
path. lineTo ( southeastXEnd, southeastYEnd) ;
canvas. drawPath ( path, paint) ;
path. reset ( ) ;
paint. setColor ( colors[ currentColor++ % 8 ] ) ;
path. moveTo ( southXStart, southYStart) ;
path. lineTo ( southXEnd, southYEnd) ;
canvas. drawPath ( path, paint) ;
path. reset ( ) ;
paint. setColor ( colors[ currentColor++ % 8 ] ) ;
path. moveTo ( southwestXStart, southwestYStart) ;
path. lineTo ( southwestXEnd, southwestYEnd) ;
canvas. drawPath ( path, paint) ;
path. reset ( ) ;
paint. setColor ( colors[ currentColor++ % 8 ] ) ;
path. moveTo ( westXStart, westYStart) ;
path. lineTo ( westXEnd, westYEnd) ;
canvas. drawPath ( path, paint) ;
}
@Override
protected void onMeasure ( int widthMeasureSpec, int heightMeasureSpec) {
super . onMeasure ( widthMeasureSpec, heightMeasureSpec) ;
double centerX = getMeasuredWidth ( ) / 2 ;
double centerY = getMeasuredHeight ( ) / 2 ;
double leg = radius * Math. cos ( Math. PI / 4 ) ;
double insideLeg = insideRadius * Math. cos ( Math. PI / 4 ) ;
northwestXStart = ( float) ( centerX - leg) ;
northwestYStart = ( float) ( centerY - leg) ;
northXStart = ( float) centerX;
northYStart = ( float) ( centerY - radius) ;
notheastXStart = ( float) ( centerX + leg) ;
notheastYStart = ( float) ( centerY - leg) ;
eastXStart = ( float) ( centerX + radius) ;
eastYStart = ( float) centerY;
southeastXStart = ( float) ( centerX + leg) ;
southeastYStart = ( float) ( centerY + leg) ;
southXStart = ( float) centerX;
southYStart = ( float) ( centerY + radius) ;
southwestXStart = ( float) ( centerX - leg) ;
southwestYStart = ( float) ( centerY + leg) ;
westXStart = ( float) ( centerX - radius) ;
westYStart = ( float) centerY;
northwestXEnd = ( float) ( centerX - insideLeg) ;
northwestYEnd = ( float) ( centerY - insideLeg) ;
northXEnd = ( float) centerX;
northYEnd = ( float) ( centerY - insideRadius) ;
notheastXEnd = ( float) ( centerX + insideLeg) ;
notheastYEnd = ( float) ( centerY - insideLeg) ;
eastXEnd = ( float) ( centerX + insideRadius) ;
eastYEnd = ( float) centerY;
southeastXEnd = ( float) ( centerX + insideLeg) ;
southeastYEnd = ( float) ( centerY + insideLeg) ;
southXEnd = ( float) centerX;
southYEnd = ( float) ( centerY + insideRadius) ;
southwestXEnd = ( float) ( centerX - insideLeg) ;
southwestYEnd = ( float) ( centerY + insideLeg) ;
westXEnd = ( float) ( centerX - insideRadius) ;
westYEnd = ( float) centerY;
}
@Override
protected void onAttachedToWindow ( ) {
super . onAttachedToWindow ( ) ;
startAnimation ( ) ;
}
private ValueAnimator valueAnimator;
public void startAnimation ( ) {
valueAnimator = ValueAnimator. ofInt ( 7 , 0 ) ;
valueAnimator. setDuration ( 400 ) ;
valueAnimator. setRepeatCount ( ValueAnimator. INFINITE ) ;
valueAnimator. setInterpolator ( new LinearInterpolator ( ) ) ;
valueAnimator. addUpdateListener ( new ValueAnimator. AnimatorUpdateListener ( ) {
@Override
public void onAnimationUpdate ( ValueAnimator animation) {
if ( ( int) animation. getAnimatedValue ( ) != currentColor) {
invalidate ( ) ;
currentColor = ( int) animation. getAnimatedValue ( ) ;
}
}
} ) ;
valueAnimator. start ( ) ;
}
}
内存分析
使用profiler工具进入memory界面,反复进入退出有自定义view的activity,点击 按钮进行强制GC(多点几次,防止没来得及回收的类干扰分析),进行垃圾回收,然后点击下图按钮两次(一次开始。一次终止),记录一段时间内的内存情况,勾选Activity/fragment Leaks 选项,就能看到可能存在内存泄露的activity和fragment,如下图: 2.Activity/Fragment leaks may include false positives. (意思是内存泄露可能存在误报,也就是说可能不准)在计算机领域中,“False Positive”通常指误报;“alse Negative”通常指漏报。
使用mat工具进行进一步确认
首先导出.hprof文件 需要将 .hprof 文件从 Android 格式转换为 Java SE hprof 格式。 你可以使用 android_sdk/platform-tools/ 目录中提供的 hprof-conv 工具执行此操作。
cd Library/ Android/ sdk/ platform- tools/
hprof- conv 原始文件名. prof 转换后的文件名(随便起一个). prof
转换成功后,用mat工具打开转换后的文件(mat工具下载地址 ) 点击Histogram,搜索我们没有确定的Activity的名字 继续查找这个activity没有被回收的原因 查看引用关系如下图:
问题与总结
内存泄露的原因 经过上面分析是由于动画监听没有取消,追查代码得知动画监听作为内部类持有IOSStyleLoadingView引用,IOSStyleLoadingView又持有Activity的引用,最终导致无法被GC回收。 解决方法,在IOSStyleLoadingView中取消动画,移除监听。
...
@Override
protected void onDetachedFromWindow ( ) {
super . onDetachedFromWindow ( ) ;
if ( valueAnimator != null ) {
valueAnimator. removeAllUpdateListeners ( ) ;
valueAnimator. cancel ( ) ;
}
}
...