使用Android studio分析内存泄露

jopen 10年前
   <p> Android使用java作为平台开发,帮助了我们解决了很多底层问题,比如内存管理,平台依赖等等。然而,我们也经常遇到OutOfMemoey问题,垃圾回收到底去哪了?</p>    <p> 接下来是一个Handler Leak的例子,它一般会在编译器中被警告提示。</p>    <h3> 所需要的工具</h3>    <ul>     <li> Android Studio 1.1 or higher </li>     <li> Eclipse MemoryAnalyzer </li>    </ul>    <h3> 示例代码</h3>    <pre class="brush:java; toolbar: true; auto-links: false;">public class MainActivity extends ActionBarActivity {    TextView textView;    public static final String TAG = NonStaticNestedClassLeakActivity.class.getSimpleName();    @Override   protected void onCreate(Bundle savedInstanceState) {     super.onCreate(savedInstanceState);     setContentView(R.layout.activity_non_static_nested_class_leak);     textView = (TextView)findViewById(R.id.textview);     Handler handler = new Handler();      handler.postDelayed(new Runnable() {       @Override public void          textView.setText("Done");       }     }, 800000L);     } }</pre>    <p> 这是一个非常基础的Activity.注意这个匿名的Runnable被送到了Handler中,而且延迟非常的长。现在我们运行这个Activity,反复旋转屏幕,然后导出内存并分析。</p>    <h3> 导入 Memory 到Eclipse MemoryAnalyzer</h3>    <h4> 使用Androidstudio导出 heap dump</h4>    <div href="https://simg.open-open.com/show/5e219764842fae5fad2d3396242ca605.png">     <img src="https://simg.open-open.com/show/5e219764842fae5fad2d3396242ca605.png" alt="Android Studio dump Memory Analyze" height="170.6150341685649" width="700" />     <br />     <div>      Android Studio dump Memory Analyze     </div>    </div>    <ul>     <li> 点击左下角的Android </li>     <li> 选中你的程序的包名 </li>     <li> 点击 initiates garbage collection on selected vm </li>     <li> 点击 dump java heap for selected client </li>    </ul>    <h4> 打开EMA,进行分析</h4>    <p> EMA是对java heap中变量分析的一个工具,它可以用于分析内存泄露。</p>    <ul>     <li> 点击OQL图标 </li>     <li> 在窗口输入select * from instanceof android.app.Activity并按Ctrl + F5或者!按钮 </li>     <li> 奇迹出现了,现在你发现泄露了许多的activity </li>     <li> 这个真是相当的不容乐观,我们来分析一下为什么GC没有回收它 </li>    </ul>    <div href="https://simg.open-open.com/show/1df3a1a9204dfd917339116e7895d533.png">     <img src="https://simg.open-open.com/show/1df3a1a9204dfd917339116e7895d533.png" alt="EMA" height="395.97570235383444" width="700" />     <br />     <div>      EMA     </div>    </div>    <blockquote>     <p> 在OQL(Object Query Language)窗口下输入的查询命令可以获得所有在内存中的Activities,这段查询代码是不是非常简单高效呢? </p>    </blockquote>    <p> 点击一个activity对象,右键选中Path to GC roots</p>    <div href="https://simg.open-open.com/show/379d61be7f069ab239c13aa705d91e1a.png">     <img src="https://simg.open-open.com/show/379d61be7f069ab239c13aa705d91e1a.png" alt="GC root" height="303.73134328358213" width="700" />     <br />     <div>      GC root     </div>    </div>    <div href="https://simg.open-open.com/show/49b649d81ca855785d32e212a0377af8.png">     <img src="https://simg.open-open.com/show/49b649d81ca855785d32e212a0377af8.png" alt="Message in looper hold a reference to Activity" height="203.5143769968051" width="700" />     <br />     <div>      Message in looper hold a reference to Activity     </div>    </div>    <p> 在打开的新窗口中,你可以发现,你的Activity是被this$0所引用的,它实际上是匿名类对当前类的引用。this$0又被callback所引用,接着它又被Message中一串的next所引用,最后到主线程才结束。</p>    <blockquote>     <p> 任何情况下你在class中创建非静态内部类,内部类会(自动)拥有对当前类的一个强引用。 </p>    </blockquote>    <p> 一旦你把Runnable或者Message发送到Handler中,它就会被放入LooperThread的消息队列,并且被保持引用,直到Message被处理。发送postDelayed这样的消息,你输入延迟多少秒,它就会泄露至少多少秒。而发送没有延迟的消息的话,当队列中的消息过多时,也会照成一个临时的泄露。</p>    <h4> 尝试使用static inner class来解决</h4>    <p> 现在把Runnable变成静态的class</p>    <div href="https://simg.open-open.com/show/36ada057f82d7dba527a75f85f238b74.png">     <img src="https://simg.open-open.com/show/36ada057f82d7dba527a75f85f238b74.png" alt="StaticClass" height="547.3548387096774" width="700" />     <br />     <div>      StaticClass     </div>    </div>    <p> 现在,摇一摇手机,导出内存</p>    <div href="https://simg.open-open.com/show/f75dac8cf81bb3ce7df415cde80d39d6.png">     <img src="https://simg.open-open.com/show/f75dac8cf81bb3ce7df415cde80d39d6.png" alt="StaticClass_memory_analyze" width="698" height="451" />     <br />     <div>      StaticClass_memory_analyze     </div>    </div>    <p> 为什么又出现了泄露呢?我们看一看Activities的引用.</p>    <div href="https://simg.open-open.com/show/58d66006d69e8d191ae1bd7b322a66fe.png">     <img src="https://simg.open-open.com/show/58d66006d69e8d191ae1bd7b322a66fe.png" alt="StaticClass_memory_analyze_explained" height="298.8031914893617" width="700" />     <br />     <div>      StaticClass_memory_analyze_explained     </div>    </div>    <p> 看到下面的mContext的引用了吗,它被mTextView引用,这样说明,使用静态内部类还远远不够,我们仍然需要修改。</p>    <h4> 使用弱引用 + static Runnable</h4>    <p> 现在我们把刚刚内存泄露的罪魁祸首 - TextView改成弱引用。</p>    <div href="https://simg.open-open.com/show/01dce77f78e7d44c0d07a66a7fbed968.png">     <img src="https://simg.open-open.com/show/01dce77f78e7d44c0d07a66a7fbed968.png" alt="StaticClassWithWeakRef_code" height="606.140350877193" width="700" />     <br />     <div>      StaticClassWithWeakRef_code     </div>    </div>    <p> 再次注意我们对TextView保持的是<strong>弱引用</strong>,现在让它运行,摇晃手机</p>    <blockquote>     <p> 小心地操作WeakReferences,它们随时可以为空,在使用前要判断是否为空. </p>    </blockquote>    <div href="https://simg.open-open.com/show/85955e12e4d7b0525f6eaf5f3df78a9b.png">     <img src="https://simg.open-open.com/show/85955e12e4d7b0525f6eaf5f3df78a9b.png" alt="StaticClassWithWeakRef_memory_analyze" width="635" height="296" />     <br />     <div>      StaticClassWithWeakRef_memory_analyze     </div>    </div>    <p> 哇!现在只有一个Activity的实例了,这回终于解决了我们的问题。</p>    <p> 所以,我们应该记住:</p>    <ul>     <li> 使用静态内部类 </li>     <li> Handler/Runnable的依赖要使用弱引用。 </li>    </ul>    <p> 如果你把现在的代码与开始的代码相比,你会发现它们大不相同,开始的代码易懂简介,你甚至可以脑补出运行结果。</p>    <p> 而现在的代码更加复杂,有很多的模板代码,当把postDelayed设置为一个短时间,比如50ms的情况下,写这么多代码就有点亏了。其实,还有一个更简单的方法。</p>    <h3> onDestroy中手动控制声明周期</h3>    <p> Handler可以使用removeCallbacksAndMessages(null),它将移除这个Handler所拥有的Runnable与Message。</p>    <pre class="brush:java; toolbar: true; auto-links: false;">//Fixed by manually control lifecycle   @Override protected void onDestroy() {     super.onDestroy();     myHandler.removeCallbacksAndMessages(null);   }</pre>    <p> 现在运行,旋转手机,导出内存</p>    <div href="https://simg.open-open.com/show/c1a73df808c13a6a27b0f5c3837e32dd.png">     <img src="https://simg.open-open.com/show/c1a73df808c13a6a27b0f5c3837e32dd.png" alt="removeCallbacks_memory_analyze" width="621" height="342" />     <br />     <div>      removeCallbacks_memory_analyze     </div>    </div>    <p> Good!只有一个实例。</p>    <p> 这样写可以让你的代码更加简洁与可读。唯一要记住的就是就是要记得在生命周期onDestory的时候手动移除所有的消息。</p>    <h3> 使用WeakHander</h3>    <p> (这个是第三方库,我就不翻译了,大家去<a href="/misc/goto?guid=4959627884934702050" target="_blank">Github</a>上去学习吧)</p>    <h3> 结论</h3>    <p> 在Handler中使用postDelayed需要额外的注意,为了解决问题,我们有三种方法</p>    <ul>     <li> 使用静态内部Handler/Runnable + 弱引用 </li>     <li> 在onDestory的时候,手动清除Message </li>     <li> 使用Badoo开发的第三方的 <a href="/misc/goto?guid=4959627884934702050" target="_blank">WeakHandler</a> </li>    </ul>    <p> 这三种你可以任意选用,第二种看起来更加合理,但是需要额外的工作。第三种方法是我最喜欢的,当然你也要注意WeakHandler不能与外部的强引用共同使用。<br /> <br /> 来自:<a target="_blank" href="/misc/goto?guid=4958867210506314549">http://www.jianshu.com/p/c49f778e7acf</a> </p>