<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Gaoge&#39;s island</title>
    <link>http://ggao.me/tags/android/index.xml</link>
    <description>Recent content on Gaoge&#39;s island</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>zh-CN</language>
    <copyright>All rights reserved - 2016</copyright>
    <atom:link href="http://ggao.me/tags/android/index.xml" rel="self" type="application/rss+xml" />
    
    <item>
      <title>Pokemon Button</title>
      <link>http://ggao.me/post/android/pokemon-button/</link>
      <pubDate>Mon, 19 Dec 2016 00:16:24 +0800</pubDate>
      
      <guid>http://ggao.me/post/android/pokemon-button/</guid>
      <description>&lt;p&gt;&lt;code&gt;PokemonButton&lt;/code&gt;是受&lt;a href=&#34;https://github.com/jd-alexander/LikeButton&#34;&gt;LikeButton&lt;/a&gt;和&lt;a href=&#34;https://github.com/frogermcs/LikeAnimation&#34;&gt;LikeAnimation&lt;/a&gt;的启发，类似&lt;code&gt;Twitter&lt;/code&gt;的&lt;code&gt;Like&lt;/code&gt;动画的一种实现。&lt;/p&gt;

&lt;p&gt;具体效果图如下：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;http://ggao.me/post/android/pokemonbutton.gif&#34; alt=&#34;pokemonbutton&#34; /&gt;&lt;/p&gt;

&lt;p&gt;该动画效果是通过手动绘制和使用&lt;code&gt;ObjectAnimator&lt;/code&gt;实现的，相较帧动画，这样的解决方案更为灵活。这篇文章只是一个简单的概述，如果想要知道更多的细节，可以在我的&lt;code&gt;GitHub&lt;/code&gt;中查看源代码——&lt;a href=&#34;https://github.com/Gaogggg/PokemonButton&#34;&gt;PokemonButton&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;PokemonButton&lt;/code&gt;是一个继承&lt;code&gt;FrameLayout&lt;/code&gt;的&lt;code&gt;View&lt;/code&gt;，该&lt;code&gt;View&lt;/code&gt;托管三个&lt;code&gt;ChildView&lt;/code&gt; - &lt;code&gt;PokeBallView&lt;/code&gt;显示精灵球图形，&lt;code&gt;ImageView&lt;/code&gt;显示精灵的&lt;code&gt;Image&lt;/code&gt;，&lt;code&gt;TypeView&lt;/code&gt;呈现与精灵属性相应的点缀效果图。&lt;/p&gt;

&lt;h5&gt;PokeBallView&lt;/h5&gt;

&lt;p&gt;这个&lt;code&gt;View&lt;/code&gt;负责绘制精灵图标下面的精灵球图案。使用&lt;code&gt;Paint&lt;/code&gt;和&lt;code&gt;Path&lt;/code&gt;在&lt;code&gt;Canvas&lt;/code&gt;上绘制：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    tempCanvas.drawColor(0xffffff, PorterDuff.Mode.CLEAR);

    drawPokeBall(getWidth() / 2, getHeight() / 2, outerCircleRadiusProgress * maxCircleSize,tempCanvas);
    tempCanvas.drawCircle(getWidth() / 2, getHeight() / 2, innerCircleRadiusProgress * maxCircleSize +  paintBlack.getStrokeWidth(), maskPaint);
    canvas.drawBitmap(tempBitmap, 0, 0, null);
}&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;整个&lt;code&gt;View&lt;/code&gt;分为两层，外圈的精灵球图案以及内圈的擦除圆。&lt;/p&gt;

&lt;p&gt;精灵球图案分为上半球、下半球以及轮廓的绘制：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;public void drawPokeBall(float cx, float cy, float radius, Canvas canvas){
    RectF outerBall = new RectF(cx-radius, cy-radius, cx+radius, cy+radius);

    RectF innerBall = new RectF(cx-radius/3,cy-radius/3,cx + radius/3,cy + radius/3);

    pathTopBall.reset();
    pathBlack.reset();
    pathBottomBall.reset();

    pathTopBall.moveTo(cx - radius,cy);
    pathTopBall.lineTo(cx - radius/3,cy);
    pathTopBall.arcTo(new RectF(innerBall),180,180);
    pathTopBall.lineTo(cx + radius,cy);
    pathTopBall.arcTo(new RectF(outerBall),0,-180);
    pathTopBall.close();

    pathBottomBall.moveTo(cx - radius, cy);
    pathBottomBall.lineTo(cx - radius/3, cy);
    pathBottomBall.arcTo(new RectF(innerBall),180,-180);
    pathBottomBall.lineTo(cx + radius,cy);
    pathBottomBall.arcTo(new RectF(outerBall),0,180);
    pathBottomBall.close();

    pathBlack.moveTo(cx - radius, cy);
    pathBlack.lineTo(cx - radius/3,cy);
    pathBlack.moveTo(cx + radius/3, cy);
    pathBlack.lineTo(cx + radius, cy);

    canvas.drawPath(pathTopBall,paintTopBall);
    canvas.drawPath(pathBottomBall,paintBottomBall);
    canvas.drawPath(pathBlack,paintBlack);

    canvas.drawCircle(cx,cy,radius/3,paintBlack);
    canvas.drawCircle(cx,cy,radius,paintBlack);
}&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;每个帧从清除整个画布开始，通过使用&lt;code&gt;CLEAR&lt;/code&gt;模式的画笔。然后根据给定的进度绘制内圈和外圈：&lt;code&gt;maskPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;这就能产生一个透明孔由内向外扩大使精灵球图案消失的效果。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;View&lt;/code&gt;所用的&lt;code&gt;tempCanvas&lt;/code&gt;和&lt;code&gt;tempBitmap&lt;/code&gt;是在这里定义的：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);
    paintBlack.setStrokeWidth(w/20);
    maxCircleSize = w / 2 - (int)paintBlack.getStrokeWidth();
    tempBitmap = Bitmap.createBitmap(getWidth(), getWidth(), Bitmap.Config.ARGB_8888);
    tempCanvas = new Canvas(tempBitmap);
}&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;除此之外，&lt;code&gt;PokeBallView&lt;/code&gt;能根据当前的进展改变其颜色。由&lt;code&gt;ArgbEvaluator&lt;/code&gt;完成：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;private void updateCircleColor() {
    float colorProgress = (float) Utils.clamp(outerCircleRadiusProgress, 0.5, 1);
    colorProgress = (float) Utils.mapValueFromRangeToRange(colorProgress, 0.5f, 1f, 0f, 1f);
    this.paintTopBall.setColor((Integer) argbEvaluator.evaluate(colorProgress, 0, TOP_HALF_BALL_COLOR));
    this.paintBottomBall.setColor((Integer) argbEvaluator.evaluate(colorProgress, 0, BOTTOM_HALF_BALL_COLOR));
    this.paintBlack.setColor((Integer) argbEvaluator.evaluate(colorProgress, 0, BLACK_COLOR));
}&lt;/code&gt;&lt;/pre&gt;

&lt;h5&gt;TypeView&lt;/h5&gt;

&lt;p&gt;这个类是呈现与精灵属性相应的点缀效果图的抽象类，和&lt;code&gt;PokeBallView&lt;/code&gt;类似，在&lt;code&gt;onDraw()&lt;/code&gt;中：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;@Override
protected void onDraw(Canvas canvas) {
    drawOuterTypesFrame(canvas);
    drawInnerTypesFrame(canvas);
}

private void drawOuterTypesFrame(Canvas canvas) {
    for (int i = 0; i &lt; TYPES_COUNT; i++) {
        int cX = (int) (centerX + currentRadius1 * Math.cos(i * OUTER_TYPES_POSITION_ANGLE * Math.PI / 180));
        int cY = (int) (centerY + currentRadius1 * Math.sin(i * OUTER_TYPES_POSITION_ANGLE * Math.PI / 180));
        drawType(cX, cY, currentTypeSize1, canvas);
    }
}

private void drawInnerTypesFrame(Canvas canvas) {
    for (int i = 0; i &lt; TYPES_COUNT; i++) {
        int cX = (int) (centerX + currentRadius2 * Math.cos((i * OUTER_TYPES_POSITION_ANGLE - 10) * Math.PI / 180));
        int cY = (int) (centerY + currentRadius2 * Math.sin((i * OUTER_TYPES_POSITION_ANGLE - 10) * Math.PI / 180));
        drawType(cX, cY, currentTypeSize2, canvas);
    }
}&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;点缀效果是基于当前进度进行绘制的，它们排列在不可见圆上——位置由以下因素决定：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;int cX = (int) (centerX + currentRadius1 * Math.cos(i * OUTER_TYPES_POSITION_ANGLE * Math.PI / 180));
int cY = (int) (centerY + currentRadius1 * Math.sin(i * OUTER_TYPES_POSITION_ANGLE * Math.PI / 180));&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;具体的点缀效果绘制交由继承该类的具体实现来进行。以目前已经实现的四个效果之一的&lt;code&gt;Electric&lt;/code&gt;为例。&lt;code&gt;Electric&lt;/code&gt;需要实现该抽象类的四个抽象方法，分别为&lt;code&gt;init()&lt;/code&gt;——进行绘制所需的初始化，&lt;code&gt;drawType(float cx, float cy, float radius, Canvas canvas)&lt;/code&gt;——实际绘制，&lt;code&gt;updateTypesPaints(float currentProgress)&lt;/code&gt;——根据当前进度调整画笔状态，&lt;code&gt;updateTypesAlpha(int alpha)&lt;/code&gt;——根据当前进度调整画笔透明度。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Electric&lt;/code&gt;的相关实现如下：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;@Override
protected void init() {

    paintElectric = new Paint();
    paintElectric.setAntiAlias(true);
    paintElectric.setStyle((Paint.Style.FILL));
}

@Override
protected void drawType(float cx, float cy, float radius, Canvas canvas) {
    pathElectric.reset();

    pathElectric.moveTo(cx - radius/2,cy - radius/4);
    pathElectric.lineTo(cx + radius,cy - 3*radius/4);
    pathElectric.lineTo(cx + radius/2,cy + radius/4);
    pathElectric.lineTo(cx + 3*radius/4,cy + 3*radius/8);
    pathElectric.lineTo(cx - 3*radius/4,cy + 7*radius/8);
    pathElectric.lineTo(cx - radius/4,cy);
    pathElectric.lineTo(cx - radius/2,cy - radius/4);

    canvas.drawPath(pathElectric,paintElectric);
}

@Override
protected void updateTypesPaints(float currentProgress) {
    if (currentProgress &lt; 0.5f) {
        float progress = (float) Utils.mapValueFromRangeToRange(currentProgress, 0f, 0.5f, 0, 1f);
        paintElectric.setColor((Integer) argbEvaluator.evaluate(progress, COLOR_1, COLOR_2));
    } else {
        float progress = (float) Utils.mapValueFromRangeToRange(currentProgress, 0.5f, 1f, 0, 1f);
        paintElectric.setColor((Integer) argbEvaluator.evaluate(progress, COLOR_2, COLOR_3));

    }
}

@Override
protected void updateTypesAlpha(int alpha) {
    paintElectric.setAlpha(alpha);
}&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;将&lt;code&gt;TypeView&lt;/code&gt;设计为抽象类可以让我们在之后绘制新效果时只用关注于具体图案的绘制，实际上目前由于时间关系我只实现了四个属性效果，分别为&lt;code&gt;Electric&lt;/code&gt;,&lt;code&gt;Fire&lt;/code&gt;,&lt;code&gt;Grass&lt;/code&gt;和&lt;code&gt;Water&lt;/code&gt;，而在&lt;code&gt;PokemonType&lt;/code&gt;中我预设了所有的精灵属性：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;public enum  PokemonType {
    NORMAL,
    FIRE,
    FIGHTING,
    WATER,
    FLYING,
    GRASS,
    POSITION,
    ELECTRIC,
    GROUND,
    PSYCHIC,
    ROCK,
    ICE,
    BUG,
    DRAGON,
    GHOST,
    DARK,
    STEEL,
    FAIRY
}&lt;/code&gt;&lt;/pre&gt;

&lt;h5&gt;PokemonButton&lt;/h5&gt;

&lt;p&gt;最终的&lt;code&gt;ViewGroup&lt;/code&gt;由&lt;code&gt;PokeballView&lt;/code&gt;，&lt;code&gt;ImageView&lt;/code&gt;和动态生成的具体实现的&lt;code&gt;TypeView&lt;/code&gt;组成。&lt;/p&gt;

&lt;p&gt;最终的&lt;code&gt;View&lt;/code&gt;动画由一些较小的动画组成，通过&lt;code&gt;AnimatorSet&lt;/code&gt;一起播放：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;animatorSet = new AnimatorSet();

ObjectAnimator outerCircleAnimator = ObjectAnimator.ofFloat(pokeBallView, pokeBallView.OUTER_CIRCLE_RADIUS_PROGRESS, 0.1f, 1f);
outerCircleAnimator.setDuration(250);
outerCircleAnimator.setInterpolator(DECCELERATE_INTERPOLATOR);

ObjectAnimator innerCircleAnimator = ObjectAnimator.ofFloat(pokeBallView, pokeBallView.INNER_CIRCLE_RADIUS_PROGRESS, 0.1f, 1f);
innerCircleAnimator.setDuration(200);
innerCircleAnimator.setStartDelay(200);
innerCircleAnimator.setInterpolator(DECCELERATE_INTERPOLATOR);

ObjectAnimator starScaleYAnimator = ObjectAnimator.ofFloat(icon, ImageView.SCALE_Y, 0.2f, 1f);
starScaleYAnimator.setDuration(350);
starScaleYAnimator.setStartDelay(250);
starScaleYAnimator.setInterpolator(OVERSHOOT_INTERPOLATOR);

ObjectAnimator starScaleXAnimator = ObjectAnimator.ofFloat(icon, ImageView.SCALE_X, 0.2f, 1f);
starScaleXAnimator.setDuration(350);
starScaleXAnimator.setStartDelay(250);
starScaleXAnimator.setInterpolator(OVERSHOOT_INTERPOLATOR);

ObjectAnimator dotsAnimator = ObjectAnimator.ofFloat(typeView, typeView.TYPES_PROGRESS, 0, 1f);
dotsAnimator.setDuration(900);
dotsAnimator.setStartDelay(50);
dotsAnimator.setInterpolator(ACCELERATE_DECELERATE_INTERPOLATOR);

animatorSet.playTogether(
        outerCircleAnimator,
        innerCircleAnimator,
        starScaleYAnimator,
        starScaleXAnimator,
        dotsAnimator
);

animatorSet.addListener(new AnimatorListenerAdapter() {
    @Override
    public void onAnimationCancel(Animator animation) {
        pokeBallView.setInnerCircleRadiusProgress(0);
        pokeBallView.setOuterCircleRadiusProgress(0);
        typeView.setCurrentProgress(0);
        icon.setScaleX(1);
        icon.setScaleY(1);
    }
});

animatorSet.start();&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;当然，&lt;code&gt;PokemonButton&lt;/code&gt;需要相应点击事件：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;@Override
public boolean onTouchEvent(MotionEvent event) {
    if(!isEnabled)
        return true;

    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            setPressed(true);
            break;

        case MotionEvent.ACTION_MOVE:
            float x = event.getX();
            float y = event.getY();
            boolean isInside = (x &gt; 0 &amp;&amp; x &lt; getWidth() &amp;&amp; y &gt; 0 &amp;&amp; y &lt; getHeight());
            if (isPressed() != isInside) {
                setPressed(isInside);
            }
            break;

        case MotionEvent.ACTION_UP:
            icon.animate().scaleX(0.7f).scaleY(0.7f).setDuration(150).setInterpolator(DECCELERATE_INTERPOLATOR);
            icon.animate().scaleX(1).scaleY(1).setInterpolator(DECCELERATE_INTERPOLATOR);
            if (isPressed()) {
                performClick();
                setPressed(false);
            }
            break;
    }
    return true;
}&lt;/code&gt;&lt;/pre&gt;
</description>
    </item>
    
    <item>
      <title>Android Memory Leak</title>
      <link>http://ggao.me/post/android/android-memory-leak/</link>
      <pubDate>Wed, 14 Dec 2016 18:16:24 +0800</pubDate>
      
      <guid>http://ggao.me/post/android/android-memory-leak/</guid>
      <description>&lt;p&gt;Android内存泄漏总结&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;h4&gt;基础知识&lt;/h4&gt;
&lt;br&gt;
&lt;h5&gt;1)泄露的对象&lt;/h5&gt;&lt;/p&gt;

&lt;p&gt;首先我们来了解程序运行时，所需内存的分配策略：&lt;/p&gt;

&lt;p&gt;按照编译原理的观点,程序运行时的内存分配有三种策略,分别是静态的,栈式的,和堆式的，对应的，三种存储策略使用的内存空间主要分别是静态存储区（也称方法区）、堆区和栈区。他们的功能不同，对他们使用方式也就不同。&lt;/p&gt;

&lt;p&gt;静态存储区（方法区）：内存在程序编译的时候就已经分配好，这块内存在程序整个运行期间都存在。它主要存放静态数据、全局&lt;code&gt;static&lt;/code&gt;数据和常量。&lt;/p&gt;

&lt;p&gt;栈区：在执行函数时，函数内局部变量的存储单元都可以在栈上创建，函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中，效率很高，但是分配的内存容量有限。&lt;/p&gt;

&lt;p&gt;堆区：亦称动态内存分配。程序在运行的时候用&lt;code&gt;malloc&lt;/code&gt;或&lt;code&gt;new&lt;/code&gt;申请任意大小的内存，程序员自己负责在适当的时候用&lt;code&gt;free&lt;/code&gt;或&lt;code&gt;delete&lt;/code&gt;释放内存（&lt;code&gt;JAVA&lt;/code&gt;则依赖垃圾回收器）。动态内存的生存期可以由我们决定，如果我们不释放内存，程序将在最后才释放掉动态内存。 但是，良好的编程习惯是：如果某动态内存不再使用，需要将其释放掉。&lt;/p&gt;

&lt;p&gt;接下来我们集中说下堆和栈的区别：&lt;/p&gt;

&lt;p&gt;在函数中（说明是局部变量）定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配。当在一段代码块中定义一个变量时，&lt;code&gt;JAVA&lt;/code&gt;就在栈中为这个变量分配内存空间，当超过变量的作用域后，&lt;code&gt;JAVA&lt;/code&gt;会自动释放掉为该变量分配的内存空间，该内存空间可以立刻被另作他用。&lt;/p&gt;

&lt;p&gt;堆内存用于存放所有由&lt;code&gt;new&lt;/code&gt;创建的对象（内容包括该对象其中的所有成员变量）和数组。在堆中分配的内存，由&lt;code&gt;JAVA&lt;/code&gt;虚拟机自动垃圾回收器来管理。在堆中产生了一个数组或者对象后，还可以在栈中定义一个特殊的变量，这个变量的取值等于数组或者对象在堆内存中的首地址，在栈中的这个特殊的变量就变成了数组或者对象的引用变量，以后就可以在程序中使用栈内存中的引用变量来访问堆中的数组或者对象，引用变量相当于为数组或者对象起的一个别名，或者代号。&lt;/p&gt;

&lt;p&gt;堆是不连续的内存区域（因为系统是用链表来存储空闲内存地址，自然不是连续的），堆大小受限于计算机系统中有效的虚拟内存（&lt;code&gt;32bit&lt;/code&gt;系统理论上是4G），所以堆的空间比较灵活，比较大。栈是一块连续的内存区域，大小是操作系统预定好的，&lt;code&gt;windows&lt;/code&gt;下栈大小是&lt;code&gt;2M&lt;/code&gt;（也有是&lt;code&gt;1M&lt;/code&gt;，在编译时确定，&lt;code&gt;VC&lt;/code&gt;中可设置）。&lt;/p&gt;

&lt;p&gt;对于堆，频繁的&lt;code&gt;new/delete&lt;/code&gt;会造成大量内存碎片，使程序效率降低。对于栈，它是先进后出的队列，进出一一对应，不产生碎片，运行效率稳定高。&lt;/p&gt;

&lt;h5&gt;结论：&lt;/h5&gt;&lt;br&gt;

&lt;strong&gt;局部变量的基本数据类型和引用存储于栈中，引用的对象实体存储于堆中。&lt;br&gt;——因为它们属于方法中的变量，生命周期随方法而结束。&lt;br&gt;成员变量全部存储与堆中（包括基本数据类型，引用和引用的对象实体）&lt;br&gt;——因为它们属于类，类对象终究是要被&lt;code&gt;new&lt;/code&gt;出来使用的。&lt;br&gt;我们这里说的内存泄露，是针对，也只针对堆内存，他们存放的就是引用指向的对象实体。&lt;/strong&gt;&lt;/font&gt;&lt;br&gt;&lt;br&gt;

&lt;h5&gt;2)泄露的原因&lt;/h5&gt;

&lt;p&gt;为了判断&lt;code&gt;JAVA&lt;/code&gt;中是否有内存泄露，我们首先必须了解&lt;code&gt;JAVA&lt;/code&gt;是如何管理（堆）内存的。&lt;code&gt;JAVA&lt;/code&gt;的内存管理就是对象的分配和释放问题。在&lt;code&gt;JAVA&lt;/code&gt;中，内存的分配是由程序完成的，而内存的释放是由垃圾收集器&lt;code&gt;Garbage Collection,GC&lt;/code&gt;完成的，程序员不需要通过调用函数来释放内存，但它只能回收无用并且不再被其它对象引用的那些对象所占用的空间。&lt;/p&gt;

&lt;p&gt;Java的内存垃圾回收机制是从程序的主要运行对象(如静态对象/寄存器/栈上指向的堆内存对象等)开始检查引用链，当遍历一遍后得到上述这些无法回收的对象和他们所引用的对象链，组成无法回收的对象集合，而其他孤立对象（集）就作为垃圾回收。&lt;code&gt;GC&lt;/code&gt;为了能够正确释放对象，必须监控每一个对象的运行状态，包括对象的申请、引用、被引用、赋值等，&lt;code&gt;GC&lt;/code&gt;都需要进行监控。监视对象状态是为了更加准确地、及时地释放对象，而释放对象的根本原则就是该对象不再被引用。&lt;/p&gt;

&lt;p&gt;在&lt;code&gt;JAVA&lt;/code&gt;中，这些无用的对象都由&lt;code&gt;GC&lt;/code&gt;负责回收，因此程序员不需要考虑这部分的内存泄露。虽然，我们有几个函数可以访问&lt;code&gt;GC&lt;/code&gt;，例如运行&lt;code&gt;GC&lt;/code&gt;的函数&lt;code&gt;System.gc()&lt;/code&gt;，但是根据&lt;code&gt;JAVA&lt;/code&gt;语言规范定义，该函数不保证&lt;code&gt;JVM&lt;/code&gt;的垃圾收集器一定会执行。因为不同的&lt;code&gt;JVM&lt;/code&gt;实现者可能使用不同的算法管理&lt;code&gt;GC&lt;/code&gt;。通常&lt;code&gt;GC&lt;/code&gt;的线程的优先级别较低。&lt;code&gt;JVM&lt;/code&gt;调用&lt;code&gt;GC&lt;/code&gt;的策略也有很多种，有的是内存使用到达一定程度时，&lt;code&gt;GC&lt;/code&gt;才开始工作，也有定时执行的，有的是平缓执行&lt;code&gt;GC&lt;/code&gt;，有的是中断式执行&lt;code&gt;GC&lt;/code&gt;。但通常来说，我们不需要关心这些。&lt;/p&gt;

&lt;p&gt;至此，我们来看看&lt;code&gt;JAVA&lt;/code&gt;中需要被回收的垃圾：
&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;｛
Person p1 = new Person();
……
｝&lt;/code&gt;&lt;/pre&gt;&lt;/p&gt;

&lt;p&gt;引用句柄&lt;code&gt;p1&lt;/code&gt;的作用域是从定义到&lt;code&gt;}&lt;/code&gt;处，执行完这对大括号中的所有代码后，产生的&lt;code&gt;Person&lt;/code&gt;对象就会变成垃圾，因为引用这个对象的句柄&lt;code&gt;p1&lt;/code&gt;已超过其作用域，&lt;code&gt;p1&lt;/code&gt;失效，在栈中被销毁，因此堆上的&lt;code&gt;Person&lt;/code&gt;对象不再被任何句柄引用了。 因此&lt;code&gt;person&lt;/code&gt;变为垃圾，会被回收。&lt;/p&gt;

&lt;p&gt;从上面的例子和解释，可以看到一个很关键的词：引用。&lt;/p&gt;

&lt;p&gt;通俗的讲，通过&lt;code&gt;A&lt;/code&gt;能调用并访问到&lt;code&gt;B&lt;/code&gt;，那就说明&lt;code&gt;A&lt;/code&gt;持有&lt;code&gt;B&lt;/code&gt;的引用，或&lt;code&gt;A&lt;/code&gt;就是&lt;code&gt;B&lt;/code&gt;的引用，&lt;code&gt;B&lt;/code&gt;的引用计数&lt;code&gt;+1&lt;/code&gt;。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;比如&lt;code&gt;Person p1 = new Person();&lt;/code&gt;通过&lt;code&gt;p1&lt;/code&gt;能操作&lt;code&gt;Person&lt;/code&gt;对象，因此&lt;code&gt;p1&lt;/code&gt;是&lt;code&gt;Person&lt;/code&gt;的引用；&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;比如类&lt;code&gt;O&lt;/code&gt;中有一个成员变量是&lt;code&gt;I&lt;/code&gt;类对象，因此我们可以使用&lt;code&gt;o.i&lt;/code&gt;的方式来访问I类对象的成员，因此&lt;code&gt;o&lt;/code&gt;持有一个&lt;code&gt;i&lt;/code&gt;对象的引用。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;code&gt;GC&lt;/code&gt;过程与对象的引用类型是严重相关的，我们来看看&lt;code&gt;JAVA&lt;/code&gt;对引用的分类&lt;code&gt;Strong reference, SoftReference, WeakReference, PhatomReference&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;http://ggao.me/post/android/reference_table.png&#34; alt=&#34;强弱软虚引用表&#34; /&gt;&lt;/p&gt;

&lt;p&gt;在&lt;code&gt;Android&lt;/code&gt;应用的开发中，为了防止内存溢出，在处理一些占用内存大而且声明周期较长的对象时候，可以尽量应用软引用和弱引用技术。讲多一步，这里的软引用/弱引用一般是做什么的呢？&lt;/p&gt;

&lt;p&gt;软/弱引用可以和一个引用队列&lt;code&gt;ReferenceQueue&lt;/code&gt;联合使用，如果软引用所引用的对象被垃圾回收器回收，&lt;code&gt;Java&lt;/code&gt;虚拟机就会把这个软引用加入到与之关联的引用队列中。利用这个队列可以得知被回收的软/弱引用的对象列表，从而为缓冲器清除已失效的软/弱引用。&lt;/p&gt;

&lt;p&gt;假设我们的应用会用到大量的默认图片，比如应用中有默认的头像，默认游戏图标等等，这些图片很多地方会用到。如果每次都去读取图片，由于读取文件需要硬件操作，速度较慢，会导致性能较低。所以我们考虑将图片缓存起来，需要的时候直接从内存中读取。但是，由于图片占用内存空间比较大，缓存很多图片需要很多的内存，就可能比较容易发生&lt;code&gt;OutOfMemory&lt;/code&gt;异常。这时，我们可以考虑使用软/弱引用技术来避免这个问题发生。以下就是高速缓冲器的雏形:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;public class CacheBySoftRef {
    
    // 首先定义一个HashMap，保存软引用对象。

    private Map&lt;String, SoftReference&lt;Bitmap&gt;&gt;imageCache = new HashMap&lt;String, SoftReference&lt;Bitmap&gt;&gt;();

    // 再来定义一个方法，保存Bitmap的软引用到HashMap。

    public void addBitmapToCache(String path) {

        // 强引用的Bitmap对象

        Bitmap bitmap = BitmapFactory.decodeFile(path);

        // 软引用的Bitmap对象

        SoftReference&lt;Bitmap&gt;softBitmap = new SoftReference&lt;Bitmap&gt;(bitmap);

        // 添加该对象到Map中使其缓存

        imageCache.put(path, softBitmap);

    }

    // 获取的时候，可以通过SoftReference的get()方法得到Bitmap对象。

    public Bitmap getBitmapByPath(String path) {

        // 从缓存中取软引用的Bitmap对象

        SoftReference&lt;Bitmap&gt; softBitmap = imageCache.get(path);

        // 判断是否存在软引用

        if (softBitmap == null) {

            return null;

        }

        // 通过软引用取出Bitmap对象，如果由于内存不足Bitmap被回收，将取得空 ，如果未被回收，则可重复使用，提高速度。

        Bitmap bitmap = softBitmap.get();

        return bitmap;

    }

}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;使用软引用以后，在&lt;code&gt;OutOfMemory&lt;/code&gt;异常发生之前，这些缓存的图片资源的内存空间可以被释放掉的，从而避免内存达到上限，避免&lt;code&gt;Crash&lt;/code&gt;发生。&lt;/p&gt;

&lt;p&gt;如果只是想避免&lt;code&gt;OutOfMemory&lt;/code&gt;异常的发生，则可以使用软引用。如果对于应用的性能更在意，想尽快回收一些占用内存比较大的对象，则可以使用弱引用。&lt;/p&gt;

&lt;p&gt;另外可以根据对象是否经常使用来判断选择软引用还是弱引用。如果该对象可能会经常使用的，就尽量用软引用。如果该对象不被使用的可能性更大些，就可以用弱引用。&lt;/p&gt;

&lt;p&gt;回到我们的问题，为什么内存会泄露？&lt;br&gt;&lt;br&gt;
&lt;strong&gt;堆内存中的长生命周期的对象持有短生命周期对象的强/软引用，尽管短生命周期对象已经不再需要，但是因为长生命周期对象持有它的引用而导致不能被回收，这就是&lt;code&gt;Java&lt;/code&gt;中内存泄露的根本原因。&lt;/strong&gt;
&lt;hr&gt;&lt;/p&gt;

&lt;p&gt;&lt;h4&gt;常见原因及实践&lt;/h4&gt;&lt;br&gt;
&lt;h5&gt;常见原因&lt;/h5&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;集合类&lt;/p&gt;

&lt;p&gt;集合类如果仅仅有添加元素的方法，而没有相应的删除机制，导致内存被占用。如果这个集合类是全局性的变量 (比如类中的静态属性，全局性的 &lt;code&gt;map&lt;/code&gt; 等即有静态引用或 &lt;code&gt;final&lt;/code&gt; 一直指向它)，那么没有相应的删除机制，很可能导致集合所占用的内存只增不减。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;单例模式&lt;/p&gt;

&lt;p&gt;不正确使用单例模式是引起内存泄露的一个常见问题，单例对象在被初始化后将在 &lt;code&gt;JVM&lt;/code&gt; 的整个生命周期中存在(以静态变量的方式)，如果单例对象持有外部对象的引用，那么这个外部对象将不能被 &lt;code&gt;JVM&lt;/code&gt; 正常回收，导致内存泄露。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;ANDROID组件或特殊集合对象的使用&lt;/p&gt;

&lt;p&gt;&lt;code&gt;BraodcastReceiver，ContentObserver，FileObserver，Cursor，Callback&lt;/code&gt;等在 &lt;code&gt;Activity onDestroy&lt;/code&gt; 或者某类生命周期结束之后一定要 &lt;code&gt;unregister&lt;/code&gt; 或者 &lt;code&gt;close&lt;/code&gt; 掉，否则这个 &lt;code&gt;Activity&lt;/code&gt; 类会被 &lt;code&gt;system&lt;/code&gt; 强引用，不会被内存回收。&lt;/p&gt;

&lt;p&gt;不要直接对 &lt;code&gt;Activity&lt;/code&gt; 进行直接引用作为成员变量，如果不得不这么做，请用 &lt;code&gt;private WeakReference mActivity&lt;/code&gt; 来做，相同的，对于&lt;code&gt;Service&lt;/code&gt; 等其他有自己声明周期的对象来说，直接引用都需要谨慎考虑是否会存在内存泄露的可能。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;HANDLER&lt;/p&gt;

&lt;p&gt;要知道，只要 &lt;code&gt;Handler&lt;/code&gt; 发送的 &lt;code&gt;Message&lt;/code&gt; 尚未被处理，则该 &lt;code&gt;Message&lt;/code&gt; 及发送它的 &lt;code&gt;Handler&lt;/code&gt; 对象将被线程 &lt;code&gt;MessageQueue&lt;/code&gt; 一直持有。由于 &lt;code&gt;Handler&lt;/code&gt; 属于 &lt;code&gt;TLS(Thread Local Storage)&lt;/code&gt; 变量, 生命周期和 &lt;code&gt;Activity&lt;/code&gt; 是不一致的。因此这种实现方式一般很难保证跟 &lt;code&gt;View&lt;/code&gt; 或者 &lt;code&gt;Activity&lt;/code&gt; 的生命周期保持一致，故很容易导致无法正确释放。如上所述，&lt;code&gt;Handler&lt;/code&gt; 的使用要尤为小心，否则将很容易导致内存泄露的发生。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;THREAD内存泄露&lt;/p&gt;

&lt;p&gt;线程也是造成内存泄露的一个重要的源头。线程产生内存泄露的主要原因在于线程生命周期的不可控。比如线程是 &lt;code&gt;Activity&lt;/code&gt; 的内部类，则线程对象中保存了 &lt;code&gt;Activity&lt;/code&gt; 的一个引用，当线程的 &lt;code&gt;run&lt;/code&gt; 函数耗时较长没有结束时，线程对象是不会被销毁的，因此它所引用的老的 &lt;code&gt;Activity&lt;/code&gt; 也不会被销毁，因此就出现了内存泄露的问题。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;一些不良代码造成的内存压力&lt;/p&gt;

&lt;p&gt;有些代码并不造成内存泄露，但是它们，或是对没使用的内存没进行有效及时的释放，或是没有有效的利用已有的对象而是频繁的申请新内存。&lt;/p&gt;

&lt;p&gt;6.1 Bitmap 没调用 recycle()。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Bitmap&lt;/code&gt;对象在不使用时,我们应该先调用 &lt;code&gt;recycle()&lt;/code&gt; 释放内存，然后才它设置为 &lt;code&gt;null&lt;/code&gt;。因为加载 &lt;code&gt;Bitmap&lt;/code&gt; 对象的内存空间，一部分是 &lt;code&gt;java&lt;/code&gt; 的，一部分 &lt;code&gt;C&lt;/code&gt; 的（因为 &lt;code&gt;Bitmap&lt;/code&gt; 分配的底层是通过 &lt;code&gt;JNI&lt;/code&gt; 调用的 )。 而这个 &lt;code&gt;recycle()&lt;/code&gt; 就是针对 &lt;code&gt;C&lt;/code&gt; 部分的内存释放。&lt;/p&gt;

&lt;p&gt;6.2 构造 Adapter 时，没有使用缓存的 convertView。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h5&gt;优秀实践&lt;/h5&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;对&lt;code&gt;activity&lt;/code&gt;等组件的引用应该控制在&lt;code&gt;activity&lt;/code&gt;的生命周期之内；如果不能就考虑使用&lt;code&gt;getApplicationContext&lt;/code&gt;或者&lt;code&gt;getApplication&lt;/code&gt;，以避免&lt;code&gt;activity&lt;/code&gt;被外部长生命周期的对象引用而泄露。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;在代码复审的时候关注长生命周期对象：全局性的集合、单例模式的使用、类的&lt;code&gt;static&lt;/code&gt;变量等等。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;尽量不要在静态变量或者静态内部类中使用非静态外部成员变量（包括&lt;code&gt;context&lt;/code&gt;），即使要使用，也要考虑适时把外部成员变量置空；也可以在内部类中使用弱引用来引用外部类的变量。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;code&gt;Handler&lt;/code&gt;的持有的引用对象最好使用弱引用，资源释放时也可以清空&lt;code&gt;Handler&lt;/code&gt;里面的消息。比如在&lt;code&gt;Activity onStop&lt;/code&gt;或者&lt;code&gt;onDestroy&lt;/code&gt;的时候，取消掉该&lt;code&gt;Handler&lt;/code&gt;对象的&lt;code&gt;Message&lt;/code&gt;和&lt;code&gt;Runnable&lt;/code&gt;，&lt;code&gt;removeCallbacks(Runnable r)&lt;/code&gt;或&lt;code&gt;removeMessages(int what)&lt;/code&gt;或&lt;code&gt;removeCallbacksAndMessages（null）&lt;/code&gt;等。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;线程&lt;code&gt;Runnable&lt;/code&gt;执行耗时操作，注意在页面返回时及时取消或者把&lt;code&gt;Runnable&lt;/code&gt;写成静态类:
a) 如果线程类是内部类，改为静态内部类。
b) 线程内如果需要引用外部类对象如&lt;code&gt;context&lt;/code&gt;，需要使用弱引用。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;在&lt;code&gt;Java&lt;/code&gt;的实现过程中，也要考虑其对象释放，最好的方法是在不使用某对象时，显式地将此对象赋空，如清空对图片等资源有直接引用或者间接引用的数组（使用&lt;code&gt;array.clear();array = null&lt;/code&gt;），最好遵循谁创建谁释放的原则。
&lt;hr&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;h4&gt;排查方法以及工具&lt;/h4&gt;&lt;br&gt;
&lt;h5&gt;MAT内存分析&lt;/h5&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;需要的软件&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Android Studio&lt;/code&gt;；&lt;br&gt;
&lt;code&gt;Memory Analyzer&lt;/code&gt;，下载地址：&lt;a href=&#34;http://www.eclipse.org/mat/downloads.php&#34;&gt;http://www.eclipse.org/mat/downloads.php&lt;/a&gt;，下载完成后进行解压，双击&lt;code&gt;MemoryAnalyzer&lt;/code&gt;运行。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;如何使用&lt;/p&gt;

&lt;p&gt;2.1 打开&lt;code&gt;Android Studio&lt;/code&gt;，&lt;code&gt;Run/Debug&lt;/code&gt;要进行分析的工程。&lt;/p&gt;

&lt;p&gt;2.2 启动&lt;code&gt;Android Studio&lt;/code&gt;工具栏上的&lt;code&gt;Android Device Monitor&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;2.3 在&lt;code&gt;Android Device Monitor&lt;/code&gt;中选中我们要进行分析的应用，然后点击&lt;code&gt;update heap&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;2.4 在应用内进行操作后，点击&lt;code&gt;DUMP HPROF file&lt;/code&gt;来保存&lt;code&gt;hprof&lt;/code&gt;文件。（建议保存到&lt;code&gt;sdk\platform-tools&lt;/code&gt;目录下，后面转换比较方便）&lt;/p&gt;

&lt;p&gt;2.5 进入到&lt;code&gt;sdk\platform-tools&lt;/code&gt;目录下，启动命令行，输入如下：&lt;code&gt;./hprof-conv inputName.hprof outputName.hprof&lt;/code&gt;。（由于都在一个目录下，输入输出文件可以不用指定位置）&lt;/p&gt;

&lt;p&gt;2.6 启动&lt;code&gt;Memory Analyzer&lt;/code&gt;，选择&lt;code&gt;File&amp;gt;open heap dump&lt;/code&gt;，打开刚才转换好的文件。&lt;/p&gt;

&lt;p&gt;2.7 点击&lt;code&gt;Leak Suspects&lt;/code&gt;会显示几个内存泄露疑点，在其中怀疑的地方，点击 &lt;code&gt;Details&lt;/code&gt; 就可以看到具体的内存使用情况。&lt;/p&gt;

&lt;p&gt;2.8 点击&lt;code&gt;Histogram&lt;/code&gt;，在出现的菜单中选中一项右键选择 &lt;code&gt;list objects&amp;gt;with incoming references&lt;/code&gt; 将列出该类的实例，如果想查看某个实例没被释放的原因，可以右健 &lt;code&gt;Path to GC Roots&amp;gt;exclude all phantom/weak/soft etc. reference&lt;/code&gt; ，这样可以去掉所有的虚/弱/软引用，以便我们快速查看某个对象的 &lt;code&gt;GC Root&lt;/code&gt;。（表头可以进行排序，在表的第一行可以输入正则表达式来匹配结果）&lt;/p&gt;

&lt;p&gt;2.9 有时候为查找内存泄漏，我们通常需要两个&lt;code&gt;Dump&lt;/code&gt;结果作对比，这时我们可以打开&lt;code&gt;Windows&amp;gt;Navigator History&lt;/code&gt;面板，将两个表的&lt;code&gt;Histogram&lt;/code&gt;结果都添加到&lt;code&gt;Compare Basket&lt;/code&gt;中去 ，然后点击面板右上角的红色叹号，得到对比结果，对比条件也是可设置的。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;一个例子&lt;/p&gt;

&lt;p&gt;一个典型的分析内存泄漏的过程：&lt;/p&gt;

&lt;p&gt;3.1 使用&lt;code&gt;Heap&lt;/code&gt;查看当前堆大小为&lt;code&gt;23.00M&lt;/code&gt;；&lt;/p&gt;

&lt;p&gt;3.2 添加一个页后堆大小变为&lt;code&gt;23.40M&lt;/code&gt;；&lt;/p&gt;

&lt;p&gt;3.3 将添加的一个页删除，堆大小为&lt;code&gt;23.40M&lt;/code&gt;；&lt;/p&gt;

&lt;p&gt;3.4 多次操作，结果仍相似，说明添加/删除页存在内存泄漏 (也应注意排除其它因素的影响)；&lt;/p&gt;

&lt;p&gt;3.5 &lt;code&gt;Dump&lt;/code&gt;出操作前后的&lt;code&gt;hprof&lt;/code&gt;文件 &lt;code&gt;1.hprof,2.hprof&lt;/code&gt;，用&lt;code&gt;mat&lt;/code&gt;打开,并得到&lt;code&gt;histgram&lt;/code&gt;结果；&lt;/p&gt;

&lt;p&gt;3.6 使用&lt;code&gt;HomePage&lt;/code&gt;字段过滤&lt;code&gt;histgram&lt;/code&gt;结果，并列出该类的对象实例列表，看到两个表中的对象集合大小不同，操作后比操作前多出一个&lt;code&gt;HomePage&lt;/code&gt;，说明确实存在泄漏；&lt;/p&gt;

&lt;p&gt;3.7 将两个列表进行对比，找出多出的一个对象，用查找&lt;code&gt;GC Root&lt;/code&gt;的方法找出是谁串起了这条引用线路，定位结束。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h5&gt;LeakCanary内存分析&lt;/h5&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;开始使用&lt;/p&gt;

&lt;p&gt;在 &lt;code&gt;build.gradle&lt;/code&gt; 中加入引用：&lt;/p&gt;

&lt;p&gt;&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;dependencies {
    debugCompile &amp;lsquo;com.squareup.leakcanary:leakcanary-android:1.4-beta2&amp;rsquo;
    releaseCompile &amp;lsquo;com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta2&amp;rsquo;
    testCompile &amp;lsquo;com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta2&amp;rsquo;
}&lt;/code&gt;&lt;/pre&gt;
在&lt;code&gt;Application&lt;/code&gt;中：&lt;/p&gt;

&lt;p&gt;&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;public class VideoApplication extends Application {
    @Override
    public void onCreate() {
            super.onCreate();
            LeakCanary.install(this);
    }
}&lt;/code&gt;&lt;/pre&gt;
在 &lt;code&gt;debug build&lt;/code&gt; 中，如果检测到某个 &lt;code&gt;activity&lt;/code&gt; 有内存泄露，&lt;code&gt;LeakCanary&lt;/code&gt; 就是自动地显示一个通知。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;如何使用&lt;/p&gt;

&lt;p&gt;使用 &lt;code&gt;RefWatcher&lt;/code&gt; 监控那些本该被回收的对象。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;RefWatcher refWatcher = {...};
// 监控
refWatcher.watch(schrodingerCat);
LeakCanary.install() 
//会返回一个预定义的 RefWatcher，同时也会启用一个 ActivityRefWatcher，用于自动监控调用 Activity.onDestroy() 之后泄露的 activity。
public class VideoApplication extends Application {

public static RefWatcher getRefWatcher(Context context) {
    VideoApplication application = (VideoApplication) context.getApplicationContext();
    return application.refWatcher;
}

private RefWatcher refWatcher;

@Override
public void onCreate() {
        super.onCreate();
        refWatcher = LeakCanary.install(this);
}
}&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;使用 &lt;code&gt;RefWatcher&lt;/code&gt; 监控 &lt;code&gt;Fragment&lt;/code&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;@Override
public void onDestroy() {
    super.onDestroy();
    RefWatcher refWatcher = VideoApplication.getRefWatcher(getActivity());
    refWatcher.watch(this);
}&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;工作机制&lt;/p&gt;

&lt;p&gt;3.1 &lt;code&gt;watch()&lt;/code&gt; 创建一个&lt;code&gt;KeyedWeakReference&lt;/code&gt; 到要被监控的对象。&lt;/p&gt;

&lt;p&gt;3.2 然后在后台线程检查引用是否被清除，如果没有，调用&lt;code&gt;GC&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;3.3 如果引用还是未被清除，把 &lt;code&gt;heap&lt;/code&gt; 内存 &lt;code&gt;dump&lt;/code&gt; 到 &lt;code&gt;APP&lt;/code&gt; 对应的文件系统中的一个&lt;code&gt;.hprof&lt;/code&gt; 文件中。&lt;/p&gt;

&lt;p&gt;3.4 在另外一个进程中的&lt;code&gt;HeapAnalyzerService&lt;/code&gt; 有一个 &lt;code&gt;HeapAnalyzer&lt;/code&gt; 使用&lt;code&gt;HAHA&lt;/code&gt; 解析这个文件。&lt;/p&gt;

&lt;p&gt;3.5 得益于唯一的 &lt;code&gt;reference key&lt;/code&gt;, &lt;code&gt;HeapAnalyzer&lt;/code&gt; 找到&lt;code&gt;KeyedWeakReference&lt;/code&gt;，定位内存泄露。&lt;/p&gt;

&lt;p&gt;3.6 &lt;code&gt;HeapAnalyzer&lt;/code&gt; 计算到 &lt;code&gt;GC roots&lt;/code&gt; 的最短强引用路径，并确定是否是泄露。如果是的话，建立导致泄露的引用链。&lt;/p&gt;

&lt;p&gt;3.7 引用链传递到 &lt;code&gt;APP&lt;/code&gt; 进程中的&lt;code&gt;DisplayLeakService&lt;/code&gt;， 并以通知的形式展示出来。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;如何复制LEAK TRACE&lt;/p&gt;

&lt;p&gt;在 &lt;code&gt;Logcat&lt;/code&gt; 中，你可以看到类似这样的 &lt;code&gt;leak trace&lt;/code&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;In com.example.leakcanary:1.0:1 com.example.leakcanary.MainActivity has leaked:
\* GC ROOT thread java.lang.Thread.&lt;Java Local&gt; (named &#39;AsyncTask #1&#39;)
\* references com.example.leakcanary.MainActivity$3.this$0 (anonymous class extends android.os.AsyncTask)
\* leaks com.example.leakcanary.MainActivity instance
\* Reference Key: e71f3bf5-d786-4145-8539-584afaecad1d
\* Device: Genymotion generic Google Nexus 6 - 5.1.0 - API 22 - 1440x2560 vbox86p
\* Android Version: 5.1 API: 22
\* Durations: watch=5086ms, gc=110ms, heap dump=435ms, analysis=2086ms&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;LEAK TRACE之外&lt;/p&gt;

&lt;p&gt;有时，&lt;code&gt;leak trace&lt;/code&gt; 不够，你需要通过 &lt;code&gt;MAT&lt;/code&gt; 或者 &lt;code&gt;YourKit&lt;/code&gt; 深挖 &lt;code&gt;dump&lt;/code&gt; 文件。&lt;/p&gt;

&lt;p&gt;通过以下方法，你能找到问题所在：&lt;/p&gt;

&lt;p&gt;5.1 查找所有的&lt;code&gt;squareup.leakcanary.KeyedWeakReference&lt;/code&gt; 实例。&lt;/p&gt;

&lt;p&gt;5.2 检查&lt;code&gt;key&lt;/code&gt; 字段。&lt;/p&gt;

&lt;p&gt;5.3 &lt;code&gt;Find the KeyedWeakReference that has a key field equal to the reference key reported by LeakCanary&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;5.4 找到 &lt;code&gt;key&lt;/code&gt; 和 和 &lt;code&gt;logcat&lt;/code&gt; 输出的 &lt;code&gt;key&lt;/code&gt; 值一样的&lt;code&gt;KeyedWeakReference&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;5.5 &lt;code&gt;referent&lt;/code&gt; 字段对应的就是泄露的对象。&lt;/p&gt;

&lt;p&gt;5.6 剩下的，就是动手修复了。最好是检查到 &lt;code&gt;GC root&lt;/code&gt; 的最短强引用路径开始。
&lt;hr&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;h4&gt;参考资料&lt;/h4&gt;&lt;br&gt;
&lt;a href=&#34;https://segmentfault.com/a/1190000003984512&#34;&gt;内存泄露从入门到精通三部曲之基础知识篇&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://segmentfault.com/a/1190000004018386&#34;&gt;内存泄露从入门到精通三部曲之常见原因与用户实践&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://segmentfault.com/a/1190000003991636&#34;&gt;内存泄露从入门到精通三部曲之排查方法篇&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://www.liaohuqiu.net/cn/posts/leak-canary-read-me/&#34;&gt;LeakCanary 中文使用说明&lt;/a&gt;&lt;/p&gt;
</description>
    </item>
    
  </channel>
</rss>