Android 内存优化OOM 秒变大神 内存泄漏_ 性能优化(四)

来源:互联网 发布:p2p网络运营招聘 编辑:程序博客网 时间:2024/06/06 01:17


 

Android 性能优化 (一)APK高效瘦身


http://blog.csdn.net/whb20081815/article/details/70140063

Android 性能优化 (二)数据库优化 秒变大神
http://blog.csdn.net/whb20081815/article/details/70142033
 

Android 性能优化(三)布局优化 秒变大神


http://blog.csdn.net/whb20081815/article/details/70147958

 Android 性能优化(四)内存优化OOM 秒变大神
 http://blog.csdn.net/whb20081815/article/details/70243105
Android 性能优化(五)ANR 秒变大神
http://blog.csdn.net/whb20081815/article/details/70245594
Android 性能优化(六) RelativeLayout和LinearLayout性能比较
http://blog.csdn.net/whb20081815/article/details/74465870
 

Android 性能优化<七>自定义view绘制优化


http://blog.csdn.net/whb20081815/article/details/74474736


内存泄露的危害:

1.程序卡顿,响应速度慢(内存占用高时JVM虚拟机会频繁触发GC)

2.直接崩溃OOM:(OutOfMemoryError)

3.莫名消失(当你的程序所占内存越大,它在后台的时候就越可能被干掉。反之内存占用越小,在后台存在的时间就越长)



程序内存的大小:

Android为每个进程设置Dalvik Heap Size阈值,这个阈值在不同的设备上会因为RAM大小不同而各有差异。如果APP想要分配的内存超过这个阈值,就会发生OOM。程序的可用内存有多大.有限的堆内存,原始只有16M



Java虚拟机对内存的管理(分配和回收)原理:


Android虚拟机的垃圾回收采用的是根搜索算法,还一种是程序计数器算法。GC会从根节点(GC Roots)开始对heap进行遍历。到最后,部分没有直接或者间接引用到GC Roots的就是需要回收的垃圾,会被GC回收掉。而内存泄漏出现的原因就是存在了无效的引用,导致本来需要被GC的对象没有被回收掉。


垃圾回收算法有很多种,这里介绍Java中常见的垃圾回收算法: 
垃圾回收器(GC)把栈上的一些引用所关联的对象作为根节点(GC Root),根据这些引用去搜索与其关联的对象,搜索所经过的节点所组成的路径称为GC链。比如有三个类A,B,C,其中,A持有B的应用,B持有C的引用,

public class A {    public A(B b)    {        this.b = b;    }    private B  b;}public class B {    public B(C c){        this.c = c;    }    private C c;}public class C {}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

当执行:

    C c = new C();    B b  = new B(c);    A a= new A(b);
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

我们就可以通过引用a来找到C的对象,这一条链就可以作为GC链。

  当一个对象从GC Root有路径可达,就说明这个对象正在被引用。GC对于这种对象会“网开一面”。如果有对象没有任何GC Root可达,GC就会对这些对象打上标记,方便后面回收。

1)共享内存

Android系统通过下面几种方式来实现共享内存:

  • Android应用的进程都是从一个叫做Zygote的进程fork出来的。Zygote进程在系统启动,并载入通用的framework的代码与资源之后开始启动。为了启动一个新的程序进程,系统会fork Zygote进程生成一个新的进程,然后在新的进程中加载并运行应用程序的代码。这就使得大多数的RAM pages被用来分配给framework的代码,同时促使RAM资源能够在应用的所有进程之间进行共享。

  • 大多数static的数据被mmapped到一个进程中。这不仅仅让同样的数据能够在进程间进行共享,而且使得它能够在需要的时候被paged out。常见的static数据包括Dalvik Code、app resources、so文件等。

  • 大多数情况下,Android通过显式的分配共享内存区域(例如ashmem或gralloc)来实现动态RAM区域能够在不同进程之间进行共享的机制。比如,Window Surface在App与Screen Compositor之间使用共享的内存,Cursor Buffers在Content Provider与Clients之间共享内存。


2)分配与回收内存

  • 每一个进程的Dalvik Heap都反映了使用内存的占用范围。这就是通常逻辑意义上提到的Dalvik Heap Size,它可以随着需要进行增长,但是增长行为会有一个系统为它设定上限。

  • 逻辑上讲的Heap Size和实际物理意义上使用的内存大小是不对等的,Proportional Set Size(PSS)记录了应用程序自身占用以及与其他进程进行共享的内存。

  • Android系统并不会对Heap中空闲内存区域做碎片整理。系统仅仅会在新的内存分配之前判断Heap的尾端剩余空间是否足够,如果空间不够会触发GC操作,从而腾出更多空闲的内存空间。在Android的高级系统版本里面针对Heap空间有一个Generational Heap Memory的模型,最近分配的对象会存放在Young Generation区域。当这个对象在该区域停留的时间达到一定程度,它会被移动到Old Generation,最后累积一定时间再移动到Permanent Generation区域。系统会根据内存中不同的内存数据类型分别执行不同的GC操作。例如,刚分配到Young Generation区域的对象通常更容易被销毁回收,同时在Young Generation区域的GC操作速度会比Old Generation区域的GC操作速度更快(如图1所示)。


内存优化方案:5R:

本文主要通过如下的5R方法来对ANDROID内存进行优化:


1.Reckon(计算)

首先需要知道你的app所消耗内存的情况,知己知彼才能百战不殆

2.Reduce(减少)

消耗更少的资源

3.Reuse(重用)

当第一次使用完以后,尽量给其他的使用

4.Recycle(回收)

返回资源给生产流

5.Review(检查)

回顾检查你的程序,看看设计或代码有什么不合理的地方。



一:内存的计算:Reckon

二.  内存的减少Reduce

1)使用更加轻量的数据结构

使用ArrayMap/SparseArray而不是HashMap等传统数据结构。相比起Android专门为移动操作系统编写的ArrayMap容器,在大多数情况下,都显示效率低下,更占内存。通常的HashMap的实现方式更加消耗内存,因为它需要一个额外的实例对象来记录Mapping操作。另外,SparseArray更加高效,在于他们避免了对key与value的自动装箱(autoboxing),并且避免了装箱后的解箱

2)避免在Android里面使用Enum

Android官方培训课程提到过“Enums often require more than twice as much memory as static constants. You should strictly avoid using enums on Android.”

3)减小Bitmap对象的内存占用

Bitmap是一个极容易消耗内存的大胖子,减小创建出来的Bitmap的内存占用可谓是重中之重,通常来说有以下2个措施:

  • inSampleSize:缩放比例,在把图片载入内存之前,我们需要先计算出一个合适的缩放比例,避免不必要的大图载入。

  • decode format:解码格式,选择ARGB_8888/RBG_565/ARGB_4444/ALPHA_8,存在很大差异。


    4).Services资源,一个Services节省4M

    5)StringBuilder

    在有些时候,代码中会需要使用到大量的字符串拼接的操作,这种时候有必要考虑使用StringBuilder来替代频繁的“+”。

三.Reuse(重用)

1).ListView/GridView等出现大量重复子组件的视图里对ConvertView的复用

2).Bitmap对象的复用

在ListView与GridView等显示大量图片的控件里,需要使用LRU的机制来缓存处理好的Bitmap

3).池(PooL),线程池

4).避免在onDraw方法里面执行对象的创建

5.类似onDraw等频繁调用的方法,一定需要注意避免在这里做创建对象的操作,因为他会迅速增加内存的使用,而且很容易引起频繁的gc,甚至是内存抖动。

四.Recycle(回收)

内存的回收

http://blog.csdn.net/hewence1/article/details/39233085

修改对象引用类型:


1).注意临时Bitmap对象的及时回收


2).引用类型:

引用分为四种级别,这四种级别由高到低依次为:强引用>软引用>弱引用>虚引用。

强引用(strong reference)
如:Object object=new Object(),object就是一个强引用了。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题。

软引用(SoftReference)
只有内存不够时才回收,常用于缓存;当内存达到一个阀值,GC就会去回收它;

弱引用(WeakReference)   

弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它 所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。 


虚引用(PhantomReference)   

"虚引用"顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。  


软引用和弱引用的应用实例:

注意:对于SoftReference(软引用)或者WeakReference(弱引用)的Bitmap缓存方案,现在已经不推荐使用了。自Android2.3版本(API Level 9)开始,垃圾回收器更着重于对软/弱引用的回收,所以下面的内容可以选择忽略。

在Android应用的开发中,为了防止内存溢出,在处理一些占用内存大而且声明周期较长的对象时候,可以尽量应用软引用和弱引用技术。

五.Review(检查)

onLowMemory()与onTrimMemory()






内存泄漏的常见的情况:

http://blog.csdn.net/u010198148/article/details/51649852

http://www.aichengxu.com/java/17381.htm
http://www.cnblogs.com/qianxudetianxia/p/3645106.html


什么是内存泄漏
  定义了的变量没使用,就是内存泄漏了。

      Java内存泄漏指的是进程中某些对象(垃圾对象)已经没有使用价值了,但是它们却可以直接或间接地引用到gc roots导致无法被GC回收。


据统计,94%得OOM异常都是由于内存泄露引发的。所以,解决内存泄露是我们Android程序员必须面对的话题。


要解决MainActivity的内存泄漏问题,只需把非静态的Thread匿名类定义成静态的内部类就行了(静态的内部类不会持有外部类的一个隐式引用): 


内存泄漏1:静态Activities(static Activities)


public class MainActivity extends AppCompatActivity {private static MainActivity activity;    TextView saButton;    @Overrideprotected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        saButton = (TextView) findViewById(R.id.text);        saButton.setOnClickListener(new View.OnClickListener() {            @Override public void onClick(View v) {                setStaticActivity();                nextActivity();            }        });    }    void setStaticActivity() {        activity = this;    }    void nextActivity(){        startActivity(new Intent(this,RegisterActivity.class));        SystemClock.sleep(1000);        finish();    }    @Overrideprotected void onDestroy() {        super.onDestroy();        //使用LeakCanary观察是否有内存泄漏        MyApplication.getRefWatcher().watch(this);    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

LeakCanary检

这里写图片描述

静态变量在整个应用的内存里只保存一份,一旦创建就不会释放该变量的内存,直到整个应用都销毁才会释放static静态变量的内存.

为什么? 
在上面代码中,我们声明了一个静态的Activity变量并且在TextView的OnClick事件里引用了当前正在运行的Activity实例,所以如果在activity的生命周期结束之前没有清除这个引用,则会引起内存泄漏。因为声明的activity是静态的,会常驻内存,如果该对象不清除,则垃圾回收器无法回收变量。

怎么解决? 
最简单的方法是在onDestory方法中将静态变量activity置空,这样垃圾回收器就可以将静态变量回收。

@Overrideprotected void onDestroy() {        super.onDestroy();        activity = null;        //使用LeakCanary观察是否有内存泄漏        MyApplication.getRefWatcher().watch(this);    }Context持有导致内存泄漏
  • Activity Context被传递到其他实例中,这可能导致自身被引用而发生泄漏。
  • 解决:对于大部分非必须使用Activity Context的情况(创建Dialog的Context必须是Activity Context),应该使用Application Context。

内存泄漏2:静态View

代码如下: 
MainActivity.java

    ...    private static View view;    TextView saButton;    @Overrideprotected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        saButton = (TextView) findViewById(R.id.text);        saButton.setOnClickListener(new View.OnClickListener() {            @Override public void onClick(View v) {                setStaticView();                nextActivity();            }        });    }    void setStaticView() {        view = findViewById(R.id.sv_view);    }    ...
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

LeakCanary检测到的内存泄漏

这里写图片描述

为什么? 
上面代码看似没有问题,在Activity里声明一个静态变量view,然后初始化,当Activity生命周期结束了内存也释放了,但是LeakCanary却显示出现了内存泄漏,为什么?问题出在这里,View一旦被加载到界面中将会持有一个Context对象的引用,在这个例子中,这个context对象是我们的Activity,声明一个静态变量引用这个View,也就引用了activity,所以当activity生命周期结束了,静态View没有清除掉,还持有activity的引用,因此内存泄漏了。

怎么解决? 
在onDestroy方法里将静态变量置空。

@Overrideprotected void onDestroy() {    super.onDestroy();    view = null;    MyApplication.getRefWatcher().watch(this);} 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

内存泄漏3:非静态内部类内存泄露

代码如下: 
MainActivity.java

private static Object inner;void createInnerClass() {    class InnerClass {    }    inner = new InnerClass();}View icButton = findViewById(R.id.ic_button);icButton.setOnClickListener(new View.OnClickListener() {    @Override public void onClick(View v) {        createInnerClass();        nextActivity();    }});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

使用LeakCanary检测到的内存泄漏:

这里写图片描述

为什么? 
非静态内部类会持有外部类的引用,在上面代码中内部类持有Activity的引用,因此inner会一直持有Activity,如果Activity生命周期结束没有清除这个引用,这样就发生了内存泄漏。

怎么解决? 
因为非静态内部类隐式持有外部类的强引用,所以我们将内部类声明成静态的就可以了。

void createInnerClass() {    static class InnerClass {    }    inner = new InnerClass();}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

内存泄漏4:匿名内部类内存泄漏

void startAsyncTask() {    new AsyncTask<Void, Void, Void>() {        @Override protected Void doInBackground(Void... params) {            while(true);        }    }.execute();}super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);View aicButton = findViewById(R.id.at_button);aicButton.setOnClickListener(new View.OnClickListener() {    @Override public void onClick(View v) {        startAsyncTask();        nextActivity();    }});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

使用LeakCanary检测到的内存泄漏:

这里写图片描述

为什么? 
上面代码在activity中创建了一个匿名类AsyncTask,匿名类和非静态内部类相同,会持有外部类对象,这里也就是activity,因此如果你在Activity里声明且实例化一个匿名的AsyncTask对象,则可能会发生内存泄漏,如果这个线程在Activity销毁后还一直在后台执行,那这个线程会继续持有这个Activity的引用从而不会被GC回收,直到线程执行完成。

怎么解决? 
自定义静态AsyncTask类,并且让AsyncTask的周期和Activity周期保持一致,也就是在Activity生命周期结束时要将AsyncTask cancel掉。

内存泄漏5:Handler

代码如下: 
MainActivity.java

...void createHandler() {    new Handler() {        @Override public void handleMessage(Message message) {            super.handleMessage(message);        }    }.postDelayed(new Runnable() {        @Override public void run() {            while(true);        }    }, 1000);}...View hButton = findViewById(R.id.h_button);hButton.setOnClickListener(new View.OnClickListener() {    @Override public void onClick(View v) {        createHandler();        nextActivity();    }});...
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

为什么? 
当Android Application启动以后,framework会首先帮助我们完成UI线程的消息循环,也就是在UI线程中,Loop、MessageQueue、Message等等这些实例已经由framework帮我们实现了。所有的Application主要事件,比如Activity的生命周期方法、Button的点击事件都包含在这个Message里面,这些Message都会加入到MessageQueue中去,所以,UI线程的消息循环贯穿于整个Application生命周期,所以当你在UI线程中生成Handler的实例,就会持有Loop以及MessageQueue的引用。并且在Java中非静态内部类和匿名内持有外部类的引用,而静态内部类则不会持有外部类的引用。

  • 在Java中,非静态(匿名)内部类会引用外部类对象。而静态内部类不会引用外部类对象
  • 当Activity Finish后,延时消息会继续存在主线程消息队列中1分钟,然后处理消息。而该消息引用了Activity的Handler对象,然后这个Handler又引用了这个Activity(从以上原因可知道)。这些引用对象会保持到该消息被处理完,这样就导致该Activity对象无法被回收,从而导致了上面说的 Activity泄露。

怎么解决? 
可以由上面的结论看出,产生泄漏的根源在于匿名类持有Activity的引用,

1.因此可以自定义Handler和Runnable类并声明成静态的内部类,来解除和Activity的引用。

2.把消息对象从消息队列移除就行了。

3.用弱引用

内存泄漏6:Thread

Thread的生命周期不一定是和Activity生命周期一致

代码如下: 
MainActivity.java

void spawnThread() {    new Thread() {        @Override public void run() {            while(true);        }    }.start();}View tButton = findViewById(R.id.t_button);tButton.setOnClickListener(new View.OnClickListener() {  @Override public void onClick(View v) {      spawnThread();      nextActivity();  }});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

为什么? 
同AsyncTask一样,这里就不过多赘述。

怎么解决? 

和Handler的处理是一样的

Virtual Machine (DVM)会为所有活跃的threads在运行时系统中保持一个硬引用,这会导致threads一直处于运行状态,垃圾收集器将永远不可能回收它.

那我们自定义Thread并声明成static这样可以吗?其实这样的做法并不推荐,因为Thread位于GC根部,DVM会和所有的活动线程保持hard references关系,所以运行中的Thread绝不会被GC无端回收了,所以正确的解决办法是在自定义静态内部类的基础上给线程加上取消机制,因此我们可以在Activity的onDestroy方法中将thread关闭掉。

内存泄漏7:Timer Tasks

代码如下: 
MainActivity.java

void scheduleTimer() {    new Timer().schedule(new TimerTask() {        @Overridepublic void run() {            while(true);        }    },1000);}View ttButton = findViewById(R.id.tt_button);ttButton.setOnClickListener(new View.OnClickListener() {    @Override public void onClick(View v) {        scheduleTimer();        nextActivity();    }});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

为什么? 
这里内存泄漏在于Timer和TimerTask没有进行Cancel,从而导致Timer和TimerTask一直引用外部类Activity。

怎么解决? 
在适当的时机进行Cancel。


内存泄漏8. 监听注册和反注册:evnbus和事件的监听,广播的监听

.BroadcastReceiver对象

原因:没有取消注册。
直接:getContext().unregisterReceiver(receiver);即可.
注册与反注册:

[java] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. addCallback             <==>     removeCallback  
  2. registerReceiver        <==>     unregisterReceiver  
  3. addObserver             <==>     deleteObserver  
  4. registerContentObserver <==>     unregisterContentObserver  
    来源: http://blog.csdn.net/lvwenbo0107/article/details/51458885

内存泄漏9:数据库Cursor没有关闭


总结:
解决内存泄漏最好的办法:程序的结构
MVC分层做好了 就会有比较少的这些handler和thread的内存泄露
可以把要做的业务逻辑和UI变化都放到 一个任务栈里 ,启一个service来遍历这个栈,这样逻辑上清晰内存泄露的杂事也少些 
1、使用静态内部类/匿名类,不要使用非静态内部类/匿名类.非静态内部类/匿名类会隐式的持有外部类的引用,外部类就有可能发生泄漏。而静态内部类/匿名类不会隐式的持有外部类引用,外部类会以正常的方式回收,如果你想在静态内部类/匿名类中使用外部类的属性或方法时,可以显示的持有一个弱引用。
2、不要以为Java永远会帮你清理回收正在运行的threads.在上面的代码中,我们很容易误以为当Activity结束销毁时会帮我们把正在运行的thread也结束回收掉,但事情永远不是这样的!Java threads会一直存在,只有当线程运行完成或被杀死掉,线程才会被回收。所以我们应该养成为thread设置退出逻辑条件的习惯。
     Dialog对象:使用isFinishing()判断Activity是否退出。才可以showDialog
  • Bitmap没调用recycle()
  • 资源性对象比如(Cursor,File文件等)往往都用了一些缓冲,我们在不使用的时候,应该及时关闭它们
  • 构造Adapter时,没有使用缓存的 convertView
  • 集合容器对象没清理造成的内存泄露(尤其是static的集合,需要clear清理)
  • WebView对象没有销毁。它的destory()函数来销毁它

     解决内存泄漏的工具LeakCanary:

LeakCananry是开源大户Square的一款开源产品,用于检测程序中的内存泄露

在本篇中,楼主用LeakCanary来对内存泄漏进行检测。LeakCanary是非常好用的第三方库用来进行内存泄漏检测,感兴趣的朋友可以去查阅LeakCanary使用方法,使用它来监测App中的内存泄漏。


leakcanary 原理:http://www.jianshu.com/p/5ee6b471970e


Android的内存优化涉及的知识面还有很多:内存管理的细节,垃圾回收的工作原理,如何查找内存泄漏等等都可以展开讲很多。OOM是内存优化当中比较突出的一点,尽量减少OOM的概率对内存优化有着很大的意义。



http://blog.csdn.net/hewence1/article/details/39004301


http://blog.csdn.net/hewence1/article/details/39233085


http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0920/3478.html




内存泄露的危害:

1.程序卡顿,响应速度慢(内存占用高时JVM虚拟机会频繁触发GC)

2.直接崩溃OOM:(OutOfMemoryError)

3.莫名消失(当你的程序所占内存越大,它在后台的时候就越可能被干掉。反之内存占用越小,在后台存在的时间就越长)



程序内存的大小:

Android为每个进程设置Dalvik Heap Size阈值,这个阈值在不同的设备上会因为RAM大小不同而各有差异。如果APP想要分配的内存超过这个阈值,就会发生OOM。程序的可用内存有多大.有限的堆内存,原始只有16M



Java虚拟机对内存的管理(分配和回收)原理:


Android虚拟机的垃圾回收采用的是根搜索算法,还一种是程序计数器算法。GC会从根节点(GC Roots)开始对heap进行遍历。到最后,部分没有直接或者间接引用到GC Roots的就是需要回收的垃圾,会被GC回收掉。而内存泄漏出现的原因就是存在了无效的引用,导致本来需要被GC的对象没有被回收掉。


垃圾回收算法有很多种,这里介绍Java中常见的垃圾回收算法: 
垃圾回收器(GC)把栈上的一些引用所关联的对象作为根节点(GC Root),根据这些引用去搜索与其关联的对象,搜索所经过的节点所组成的路径称为GC链。比如有三个类A,B,C,其中,A持有B的应用,B持有C的引用,

public class A {    public A(B b)    {        this.b = b;    }    private B  b;}public class B {    public B(C c){        this.c = c;    }    private C c;}public class C {}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

当执行:

    C c = new C();    B b  = new B(c);    A a= new A(b);
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

我们就可以通过引用a来找到C的对象,这一条链就可以作为GC链。

  当一个对象从GC Root有路径可达,就说明这个对象正在被引用。GC对于这种对象会“网开一面”。如果有对象没有任何GC Root可达,GC就会对这些对象打上标记,方便后面回收。

1)共享内存

Android系统通过下面几种方式来实现共享内存:

  • Android应用的进程都是从一个叫做Zygote的进程fork出来的。Zygote进程在系统启动,并载入通用的framework的代码与资源之后开始启动。为了启动一个新的程序进程,系统会fork Zygote进程生成一个新的进程,然后在新的进程中加载并运行应用程序的代码。这就使得大多数的RAM pages被用来分配给framework的代码,同时促使RAM资源能够在应用的所有进程之间进行共享。

  • 大多数static的数据被mmapped到一个进程中。这不仅仅让同样的数据能够在进程间进行共享,而且使得它能够在需要的时候被paged out。常见的static数据包括Dalvik Code、app resources、so文件等。

  • 大多数情况下,Android通过显式的分配共享内存区域(例如ashmem或gralloc)来实现动态RAM区域能够在不同进程之间进行共享的机制。比如,Window Surface在App与Screen Compositor之间使用共享的内存,Cursor Buffers在Content Provider与Clients之间共享内存。


2)分配与回收内存

  • 每一个进程的Dalvik Heap都反映了使用内存的占用范围。这就是通常逻辑意义上提到的Dalvik Heap Size,它可以随着需要进行增长,但是增长行为会有一个系统为它设定上限。

  • 逻辑上讲的Heap Size和实际物理意义上使用的内存大小是不对等的,Proportional Set Size(PSS)记录了应用程序自身占用以及与其他进程进行共享的内存。

  • Android系统并不会对Heap中空闲内存区域做碎片整理。系统仅仅会在新的内存分配之前判断Heap的尾端剩余空间是否足够,如果空间不够会触发GC操作,从而腾出更多空闲的内存空间。在Android的高级系统版本里面针对Heap空间有一个Generational Heap Memory的模型,最近分配的对象会存放在Young Generation区域。当这个对象在该区域停留的时间达到一定程度,它会被移动到Old Generation,最后累积一定时间再移动到Permanent Generation区域。系统会根据内存中不同的内存数据类型分别执行不同的GC操作。例如,刚分配到Young Generation区域的对象通常更容易被销毁回收,同时在Young Generation区域的GC操作速度会比Old Generation区域的GC操作速度更快(如图1所示)。


内存优化方案:5R:

本文主要通过如下的5R方法来对ANDROID内存进行优化:


1.Reckon(计算)

首先需要知道你的app所消耗内存的情况,知己知彼才能百战不殆

2.Reduce(减少)

消耗更少的资源

3.Reuse(重用)

当第一次使用完以后,尽量给其他的使用

4.Recycle(回收)

返回资源给生产流

5.Review(检查)

回顾检查你的程序,看看设计或代码有什么不合理的地方。



一:内存的计算:Reckon

二.  内存的减少Reduce

1)使用更加轻量的数据结构

使用ArrayMap/SparseArray而不是HashMap等传统数据结构。相比起Android专门为移动操作系统编写的ArrayMap容器,在大多数情况下,都显示效率低下,更占内存。通常的HashMap的实现方式更加消耗内存,因为它需要一个额外的实例对象来记录Mapping操作。另外,SparseArray更加高效,在于他们避免了对key与value的自动装箱(autoboxing),并且避免了装箱后的解箱

2)避免在Android里面使用Enum

Android官方培训课程提到过“Enums often require more than twice as much memory as static constants. You should strictly avoid using enums on Android.”

3)减小Bitmap对象的内存占用

Bitmap是一个极容易消耗内存的大胖子,减小创建出来的Bitmap的内存占用可谓是重中之重,通常来说有以下2个措施:

  • inSampleSize:缩放比例,在把图片载入内存之前,我们需要先计算出一个合适的缩放比例,避免不必要的大图载入。

  • decode format:解码格式,选择ARGB_8888/RBG_565/ARGB_4444/ALPHA_8,存在很大差异。


    4).Services资源,一个Services节省4M

    5)StringBuilder

    在有些时候,代码中会需要使用到大量的字符串拼接的操作,这种时候有必要考虑使用StringBuilder来替代频繁的“+”。

三.Reuse(重用)

1).ListView/GridView等出现大量重复子组件的视图里对ConvertView的复用

2).Bitmap对象的复用

在ListView与GridView等显示大量图片的控件里,需要使用LRU的机制来缓存处理好的Bitmap

3).池(PooL),线程池

4).避免在onDraw方法里面执行对象的创建

5.类似onDraw等频繁调用的方法,一定需要注意避免在这里做创建对象的操作,因为他会迅速增加内存的使用,而且很容易引起频繁的gc,甚至是内存抖动。

四.Recycle(回收)

内存的回收

http://blog.csdn.net/hewence1/article/details/39233085

修改对象引用类型:


1).注意临时Bitmap对象的及时回收


2).引用类型:

引用分为四种级别,这四种级别由高到低依次为:强引用>软引用>弱引用>虚引用。

强引用(strong reference)
如:Object object=new Object(),object就是一个强引用了。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题。

软引用(SoftReference)
只有内存不够时才回收,常用于缓存;当内存达到一个阀值,GC就会去回收它;

弱引用(WeakReference)   

弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它 所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。 


虚引用(PhantomReference)   

"虚引用"顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。  


软引用和弱引用的应用实例:

注意:对于SoftReference(软引用)或者WeakReference(弱引用)的Bitmap缓存方案,现在已经不推荐使用了。自Android2.3版本(API Level 9)开始,垃圾回收器更着重于对软/弱引用的回收,所以下面的内容可以选择忽略。

在Android应用的开发中,为了防止内存溢出,在处理一些占用内存大而且声明周期较长的对象时候,可以尽量应用软引用和弱引用技术。

五.Review(检查)

onLowMemory()与onTrimMemory()



通过Android Studio的Memory Monitor查看内存中Dalvik Heap的实时变化








内存蟹肉的常见的情况:

http://blog.csdn.net/u010198148/article/details/51649852

http://www.aichengxu.com/java/17381.htm
http://www.cnblogs.com/qianxudetianxia/p/3645106.html


什么是内存泄漏
  定义了的变量没使用,就是内存泄漏了。

      Java内存泄漏指的是进程中某些对象(垃圾对象)已经没有使用价值了,但是它们却可以直接或间接地引用到gc roots导致无法被GC回收。


据统计,94%得OOM异常都是由于内存泄露引发的。所以,解决内存泄露是我们Android程序员必须面对的话题。


要解决MainActivity的内存泄漏问题,只需把非静态的Thread匿名类定义成静态的内部类就行了(静态的内部类不会持有外部类的一个隐式引用): 


内存泄漏1:静态Activities(static Activities)


public class MainActivity extends AppCompatActivity {private static MainActivity activity;    TextView saButton;    @Overrideprotected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        saButton = (TextView) findViewById(R.id.text);        saButton.setOnClickListener(new View.OnClickListener() {            @Override public void onClick(View v) {                setStaticActivity();                nextActivity();            }        });    }    void setStaticActivity() {        activity = this;    }    void nextActivity(){        startActivity(new Intent(this,RegisterActivity.class));        SystemClock.sleep(1000);        finish();    }    @Overrideprotected void onDestroy() {        super.onDestroy();        //使用LeakCanary观察是否有内存泄漏        MyApplication.getRefWatcher().watch(this);    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

LeakCanary检

这里写图片描述

静态变量在整个应用的内存里只保存一份,一旦创建就不会释放该变量的内存,直到整个应用都销毁才会释放static静态变量的内存.

为什么? 
在上面代码中,我们声明了一个静态的Activity变量并且在TextView的OnClick事件里引用了当前正在运行的Activity实例,所以如果在activity的生命周期结束之前没有清除这个引用,则会引起内存泄漏。因为声明的activity是静态的,会常驻内存,如果该对象不清除,则垃圾回收器无法回收变量。

怎么解决? 
最简单的方法是在onDestory方法中将静态变量activity置空,这样垃圾回收器就可以将静态变量回收。

@Overrideprotected void onDestroy() {        super.onDestroy();        activity = null;        //使用LeakCanary观察是否有内存泄漏        MyApplication.getRefWatcher().watch(this);    }Context持有导致内存泄漏
  • Activity Context被传递到其他实例中,这可能导致自身被引用而发生泄漏。
  • 解决:对于大部分非必须使用Activity Context的情况(创建Dialog的Context必须是Activity Context),应该使用Application Context。

内存泄漏2:静态View

代码如下: 
MainActivity.java

    ...    private static View view;    TextView saButton;    @Overrideprotected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        saButton = (TextView) findViewById(R.id.text);        saButton.setOnClickListener(new View.OnClickListener() {            @Override public void onClick(View v) {                setStaticView();                nextActivity();            }        });    }    void setStaticView() {        view = findViewById(R.id.sv_view);    }    ...
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

LeakCanary检测到的内存泄漏

这里写图片描述

为什么? 
上面代码看似没有问题,在Activity里声明一个静态变量view,然后初始化,当Activity生命周期结束了内存也释放了,但是LeakCanary却显示出现了内存泄漏,为什么?问题出在这里,View一旦被加载到界面中将会持有一个Context对象的引用,在这个例子中,这个context对象是我们的Activity,声明一个静态变量引用这个View,也就引用了activity,所以当activity生命周期结束了,静态View没有清除掉,还持有activity的引用,因此内存泄漏了。

怎么解决? 
在onDestroy方法里将静态变量置空。

@Overrideprotected void onDestroy() {    super.onDestroy();    view = null;    MyApplication.getRefWatcher().watch(this);} 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

内存泄漏3:非静态内部类内存泄露

代码如下: 
MainActivity.java

private static Object inner;void createInnerClass() {    class InnerClass {    }    inner = new InnerClass();}View icButton = findViewById(R.id.ic_button);icButton.setOnClickListener(new View.OnClickListener() {    @Override public void onClick(View v) {        createInnerClass();        nextActivity();    }});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

使用LeakCanary检测到的内存泄漏:

这里写图片描述

为什么? 
非静态内部类会持有外部类的引用,在上面代码中内部类持有Activity的引用,因此inner会一直持有Activity,如果Activity生命周期结束没有清除这个引用,这样就发生了内存泄漏。

怎么解决? 
因为非静态内部类隐式持有外部类的强引用,所以我们将内部类声明成静态的就可以了。

void createInnerClass() {    static class InnerClass {    }    inner = new InnerClass();}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

内存泄漏4:匿名内部类内存泄漏

void startAsyncTask() {    new AsyncTask<Void, Void, Void>() {        @Override protected Void doInBackground(Void... params) {            while(true);        }    }.execute();}super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);View aicButton = findViewById(R.id.at_button);aicButton.setOnClickListener(new View.OnClickListener() {    @Override public void onClick(View v) {        startAsyncTask();        nextActivity();    }});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

使用LeakCanary检测到的内存泄漏:

这里写图片描述

为什么? 
上面代码在activity中创建了一个匿名类AsyncTask,匿名类和非静态内部类相同,会持有外部类对象,这里也就是activity,因此如果你在Activity里声明且实例化一个匿名的AsyncTask对象,则可能会发生内存泄漏,如果这个线程在Activity销毁后还一直在后台执行,那这个线程会继续持有这个Activity的引用从而不会被GC回收,直到线程执行完成。

怎么解决? 
自定义静态AsyncTask类,并且让AsyncTask的周期和Activity周期保持一致,也就是在Activity生命周期结束时要将AsyncTask cancel掉。

内存泄漏5:Handler

代码如下: 
MainActivity.java

...void createHandler() {    new Handler() {        @Override public void handleMessage(Message message) {            super.handleMessage(message);        }    }.postDelayed(new Runnable() {        @Override public void run() {            while(true);        }    }, 1000);}...View hButton = findViewById(R.id.h_button);hButton.setOnClickListener(new View.OnClickListener() {    @Override public void onClick(View v) {        createHandler();        nextActivity();    }});...
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

为什么? 
当Android Application启动以后,framework会首先帮助我们完成UI线程的消息循环,也就是在UI线程中,Loop、MessageQueue、Message等等这些实例已经由framework帮我们实现了。所有的Application主要事件,比如Activity的生命周期方法、Button的点击事件都包含在这个Message里面,这些Message都会加入到MessageQueue中去,所以,UI线程的消息循环贯穿于整个Application生命周期,所以当你在UI线程中生成Handler的实例,就会持有Loop以及MessageQueue的引用。并且在Java中非静态内部类和匿名内持有外部类的引用,而静态内部类则不会持有外部类的引用。

  • 在Java中,非静态(匿名)内部类会引用外部类对象。而静态内部类不会引用外部类对象
  • 当Activity Finish后,延时消息会继续存在主线程消息队列中1分钟,然后处理消息。而该消息引用了Activity的Handler对象,然后这个Handler又引用了这个Activity(从以上原因可知道)。这些引用对象会保持到该消息被处理完,这样就导致该Activity对象无法被回收,从而导致了上面说的 Activity泄露。

怎么解决? 
可以由上面的结论看出,产生泄漏的根源在于匿名类持有Activity的引用,

1.因此可以自定义Handler和Runnable类并声明成静态的内部类,来解除和Activity的引用。

2.把消息对象从消息队列移除就行了。

3.用弱引用

内存泄漏6:Thread

Thread的生命周期不一定是和Activity生命周期一致

代码如下: 
MainActivity.java

void spawnThread() {    new Thread() {        @Override public void run() {            while(true);        }    }.start();}View tButton = findViewById(R.id.t_button);tButton.setOnClickListener(new View.OnClickListener() {  @Override public void onClick(View v) {      spawnThread();      nextActivity();  }});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

为什么? 
同AsyncTask一样,这里就不过多赘述。

怎么解决? 

和Handler的处理是一样的

Virtual Machine (DVM)会为所有活跃的threads在运行时系统中保持一个硬引用,这会导致threads一直处于运行状态,垃圾收集器将永远不可能回收它.

那我们自定义Thread并声明成static这样可以吗?其实这样的做法并不推荐,因为Thread位于GC根部,DVM会和所有的活动线程保持hard references关系,所以运行中的Thread绝不会被GC无端回收了,所以正确的解决办法是在自定义静态内部类的基础上给线程加上取消机制,因此我们可以在Activity的onDestroy方法中将thread关闭掉。

内存泄漏7:Timer Tasks

代码如下: 
MainActivity.java

void scheduleTimer() {    new Timer().schedule(new TimerTask() {        @Overridepublic void run() {            while(true);        }    },1000);}View ttButton = findViewById(R.id.tt_button);ttButton.setOnClickListener(new View.OnClickListener() {    @Override public void onClick(View v) {        scheduleTimer();        nextActivity();    }});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

为什么? 
这里内存泄漏在于Timer和TimerTask没有进行Cancel,从而导致Timer和TimerTask一直引用外部类Activity。

怎么解决? 
在适当的时机进行Cancel。


内存泄漏8. 监听注册和反注册:evnbus和事件的监听,广播的监听

.BroadcastReceiver对象

原因:没有取消注册。
直接:getContext().unregisterReceiver(receiver);即可.
注册与反注册:

[java] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. addCallback             <==>     removeCallback  
  2. registerReceiver        <==>     unregisterReceiver  
  3. addObserver             <==>     deleteObserver  
  4. registerContentObserver <==>     unregisterContentObserver  
    来源: http://blog.csdn.net/lvwenbo0107/article/details/51458885

内存泄漏9:数据库Cursor没有关闭


总结:
解决内存泄漏最好的办法:程序的结构
MVC分层做好了 就会有比较少的这些handler和thread的内存泄露
可以把要做的业务逻辑和UI变化都放到 一个任务栈里 ,启一个service来遍历这个栈,这样逻辑上清晰内存泄露的杂事也少些 
1、使用静态内部类/匿名类,不要使用非静态内部类/匿名类.非静态内部类/匿名类会隐式的持有外部类的引用,外部类就有可能发生泄漏。而静态内部类/匿名类不会隐式的持有外部类引用,外部类会以正常的方式回收,如果你想在静态内部类/匿名类中使用外部类的属性或方法时,可以显示的持有一个弱引用。
2、不要以为Java永远会帮你清理回收正在运行的threads.在上面的代码中,我们很容易误以为当Activity结束销毁时会帮我们把正在运行的thread也结束回收掉,但事情永远不是这样的!Java threads会一直存在,只有当线程运行完成或被杀死掉,线程才会被回收。所以我们应该养成为thread设置退出逻辑条件的习惯。
     Dialog对象:使用isFinishing()判断Activity是否退出。才可以showDialog
  • Bitmap没调用recycle()
  • 资源性对象比如(Cursor,File文件等)往往都用了一些缓冲,我们在不使用的时候,应该及时关闭它们
  • 构造Adapter时,没有使用缓存的 convertView
  • 集合容器对象没清理造成的内存泄露(尤其是static的集合,需要clear清理)
  • WebView对象没有销毁。它的destory()函数来销毁它

     解决内存泄漏的工具LeakCanary:

LeakCananry是开源大户Square的一款开源产品,用于检测程序中的内存泄露

在本篇中,楼主用LeakCanary来对内存泄漏进行检测。LeakCanary是非常好用的第三方库用来进行内存泄漏检测,感兴趣的朋友可以去查阅LeakCanary使用方法,使用它来监测App中的内存泄漏。


leakcanary 原理:http://www.jianshu.com/p/5ee6b471970e


Android的内存优化涉及的知识面还有很多:内存管理的细节,垃圾回收的工作原理,如何查找内存泄漏等等都可以展开讲很多。OOM是内存优化当中比较突出的一点,尽量减少OOM的概率对内存优化有着很大的意义。



http://blog.csdn.net/hewence1/article/details/39004301


http://blog.csdn.net/hewence1/article/details/39233085


http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0920/3478.html

0 0
原创粉丝点击