1.内存泄漏
一个长生命周期的对象持有一个短生命周期对象的引用,通俗讲就是该回收的对象,因为引用问题没有被回收,最终会产生OOM。
1.1 非业务需要不要把activity的上下文做参数传递,可以传递application的上下文
- 因为Application的生命周期等于整个应用的生命周期,非必须的情况下用Application的上下文可以避免发生内存泄露
1.2 和Activity有关联的对象不要写成static
- static修饰的成员变量的生命周期等于应用程序的生命周期,不要使用static修饰符Context或者ui控件等等
1.3 非静态内部类和匿名内部类会持有activity引用
- 非静态内部类和匿名内部类默认持有外部类的引用,经常会引起内存泄漏的情况有三种:
1.3.1 非静态内部类的实例的引用被设置为静态
非静态内部类所创建的实例为静态(其生命周期等于应用的生命周期),会因非静态内部类默认持有外部类的引用而导致外部类无法释放,最终造成内存泄露,如下
public class TestActivity extends AppCompatActivity {
// 非静态内部类的实例的引用设置为静态
public static InnerClass innerClass = null;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 保证非静态内部类的实例只有1个
if (innerClass == null)
innerClass = new InnerClass();
}
// 非静态内部类的定义
private class InnerClass {
//...
}
}
解决方法
- 将非静态内部类设置为静态内部类(静态内部类默认不持有外部类的引用)
- 尽量新建一个文件定义类
- 避免非静态内部类所创建的实例为静态
1.3.2 使用非静态内部类和匿名内部类的方式实现多线程,例如Thread、AsyncTask、Timer
正如使用内部类一样,只要不跨越生命周期,内部类是完全没问题的。但是,这些类是用于产生后台线程的,这些Java线程是全局的,而且持有创建者的引用(即匿名类的引用),而匿名类又持有外部类的引用。线程是可能长时间运行的,所以一直持有Activity的引用导致当销毁时无法回收。如下所示
void startAsyncTask() {
new AsyncTask<Void, Void, Void>() {
@Override protected Void doInBackground(Void... params) {
while(true);
}
}.execute();
}
void scheduleTimer() {
new Timer().schedule(new TimerTask() {
@Override
public void run() {
while(true);
}
}, Long.MAX_VALUE >> 1);
}
解决方法
- 使用静态内部类
private static class NimbleTask extends AsyncTask<Void, Void, Void> {
@Override protected Void doInBackground(Void... params) {
while(true);
}
}
void startAsyncTask() {
new NimbleTask().execute();
}
private static class NimbleTimerTask extends TimerTask {
@Override public void run() {
while(true);
}
}
void scheduleTimer() {
new Timer().schedule(new NimbleTimerTask(), Long.MAX_VALUE >> 1);
}
- 在生命周期结束时中断线程
private Thread thread;
@Override
public void onDestroy() {
super.onDestroy();
if (thread != null) {
thread.interrupt();
}
}
void spawnThread() {
thread = new Thread() {
@Override public void run() {
while (!isInterrupted()) {
}
}
}
thread.start();
}
1.3.3 Handle的使用问题
Handler的两种用法 内部类和匿名内部类默认持有外部类引用,在Handler消息队列还有未处理的消息或正在处理消息时,此时若需销毁外部类Activity,但由于消息队列中的Message持有Handler实例的引用,垃圾回收器(GC)无法回收Activity,从而造成内存泄漏
/**
* 方式1:新建Handle子类(内部类)
*/
class FHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 1:
break;
}
}
/**
* 方式2:匿名Handle内部类
*/
Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 1:
break;
}
}
};
解决方法
- 使用静态内部类,同时还可以使用WeakReference弱引用持有Activity实例,断开Message消息 -> Handler实例 -> 外部类 的引用关系
private static class FHandler extends Handler{
// 定义 弱引用实例
private WeakReference<Activity> reference;
// 在构造方法中传入需持有的Activity实例
public FHandler(Activity activity) {
// 使用WeakReference弱引用持有Activity实例
reference = new WeakReference<Activity>(activity);
}
// 通过复写handlerMessage() 从而确定更新UI的操作
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 1:
break;
}
}
}
- 当外部类结束生命周期时,清空Handler内消息队列
@Override
protected void onDestroy() {
super.onDestroy();
mHandler.removeCallbacksAndMessages(null);
}
1.4 单例模式持有activity引用
单例模式由于其静态特性,其生命周期的长度等于应用程序的生命周期。若一个对象已不需再使用而单例对象还持有该对象的引用,那么该对象将不能被正常回收从而导致内存泄漏,例如:
//在使用单例setCallback的地方,callback持有外部类引用
public class Singleton {
private static Singleton singleton;
private Callback callback;
public static Singleton getInstance(){
if(singleton==null){
singleton=new Singleton();
}
return singleton;
}
private Singleton () { }
public void setCallback(Callback callback){
this.callback=callback;
}
public Callback getCallback(){
return callback.get();
}
public interface Callback{
void callback();
}
}
//若传入的是Activity的Context,此时单例 则持有该Activity的引用
public class Singleton {
private static Singleton instance;
private Context mContext;
private Singleton (Context context) {
this.mContext = context; // 传递的是Activity的context
}
public Singleton getInstance(Context context) {
if (instance == null) {
instance = new Singleton(context);
}
return instance;
}
}
解决方法
- 单例中成员变量使用弱引用,例如 private WeakReference callback
- 需要Context时传递Application的Context,因Application的生命周期等于整个应用的生命周期
1.5 WebView内存泄漏
我们都知道,对应 WebView 来说,其 网络延时、引擎 Session 管理、Cookies 管理、引擎内核线程、HTML5 调用系统声音、视频播放组件等产生的引用链条无法及时打断,造成的内存问题基本上可以用”无解“来形容。
解决方案是我们可以 把 WebView 装入另一个进程,使用AIDL与应用的主进程进行通信。具体为在 AndroidManifes 中对当前的 Activity 设置 android:process 属性即可,最后,在 Activity 的 onDestory 中退出进程,这样即可基本上终结 WebView 造成的泄漏。
1.6 资源性对象未关闭
对于资源性对象不再使用时,应该立即调用它的close()函数,将其关闭,然后再置为null。例如Bitmap等资源未关闭会造成内存泄漏,此时我们应该在Activity销毁时及时关闭。
1.7 注册对象未注销
例如BraodcastReceiver、EventBus未注销造成的内存泄漏,我们应该在Activity销毁时及时注销。
2.内存抖动
什么是内存抖动呢?
Android里内存抖动是指内存频繁地分配和回收,而频繁的gc会导致卡顿,严重时还会导致OOM。一个很经典的案例是string拼接创建大量小的对象(比如在一些频繁调用的地方打字符串拼接的log的时候)
而内存抖动为什么会引起OOM呢?
主要原因还是有因为大量小的对象频繁创建,导致内存碎片,从而当需要分配内存时,虽然总体上还是有剩余内存可分配,而由于这些内存不连续,导致无法分配,系统直接就返回OOM了。
2.1 字符串少用加号拼接
- 使用StringBuilder和StringBuffer代替字符串加号拼接
2.2 内存重复申请的问题
- 不要在频繁调用的方法中new对象,例如递归函数 ,回调函数,流的循环读取,自定义View的方法等。
- 自定义View不要在onMeause() onLayout() onDraw() 中去刷新UI(requestLayout)
2.3 避免GC回收将来要复用的对象
对于能够复用的对象,可以使用对象池+LRU算法将它们缓存起来。
public abstract class ObjectPool<T> {
//空闲池,用户从这个里面拿对象
private SparseArray<T> freePool;
//正在使用池,用户正在使用的对象放在这个池记录
private SparseArray<T> lentPool;
//池的最大值
private int maxCapacity;
public ObjectPool(int initialCapacity, int maxCapacity) {
//初始化对象池
initalize(initialCapacity);
this.maxCapacity=maxCapacity;
}
private void initalize(int initialCapacity) {
lentPool=new SparseArray<>();
freePool=new SparseArray<>();
for(int i=0;i<initialCapacity;i++){
freePool.put(i,create());
}
}
/**
* 申请对象
* @return
*/
public T acquire() throws Exception {
T t=null;
synchronized (freePool){
int freeSize=freePool.size();
for(int i=0;i<freeSize;i++){
int key=freePool.keyAt(i);
t=freePool.get(key);
if(t!=null){
this.lentPool.put(key,t);
this.freePool.remove(key);
return t;
}
}
//如果没对象可取了
if(t==null && lentPool.size()+freeSize<maxCapacity){
//这里可以自己处理,超过大小
if(lentPool.size()+freeSize==maxCapacity){
throw new Exception();
}
t=create();
lentPool.put(lentPool.size()+freeSize,t);
}
}
return t;
}
/**
* 回收对象
* @return
*/
public void release(T t){
if(t==null){
return;
}
int key=lentPool.indexOfValue(t);
//释放前可以把这个对象交给用户处理
restore(t);
this.freePool.put(key,t);
this.lentPool.remove(key);
}
protected void restore(T t){
};
protected abstract T create();
public ObjectPool(int maxCapacity) {
this(maxCapacity/2,maxCapacity);
}
}
3.优化内存的良好习惯
3.1 static和static final
static String strVal = "Hello, world!";
编译器会在类首次被使用到的时候,使用初始化方法来初始化上面的值,之后访问的时候会需要先到它那里查找,然后才返回数据。我们可以使用static final来提升性能:
static final String strVal = "Hello, world!";
这时再也不需要上面的那个方法来做多余的查找动作了。所以,请尽可能的为常量声明为static final类型的。
3.2 数据类型选择
不要使用比需求更占空间的基本数据类型
- 条件允许下,尽量避免使用float类型,Android系统中float类型的数据存取速度是int类型的一半,尽量优先采用int类型。
3.3 循环用foreach少用iterator
3.4 使用SparseArray和Arraymap代替HashMap
利用Android Framework里面优化过的容器类,例如SparseArray, SparseBooleanArray, 与 LongSparseArray。 通常的HashMap的实现方式更加消耗内存,因为它需要一个额外的实例对象来记录Mapping操作。另外,SparseArray更加高效在于他们避免了对key与value的autobox自动装箱,并且避免了装箱后的解箱。
所以数据在千级以内
- 如果key的类型已经确定为int类型,那么使用SparseArray,因为它避免了自动装箱的过程,如果key为long类型,它还提供了一个LongSparseArray来确保key为long类型时的使用
- 如果key类型为其它的类型,则使用ArrayMap
3.5 尽量少使用枚举
每一个枚举值都是一个单例对象,在使用它时会增加额外的内存消耗,所以枚举相比与 Integer 和 String 会占用更多的内存,较多的使用 Enum 会增加 DEX 文件的大小,会造成运行时更多的IO开销,使我们的应用需要更多的空间,特别是分dex多的大型APP,枚举的初始化很容易导致ANR
可以使用自定义注解实现类似效果,例如
public class SHAPE {
public static final int RECTANGLE=0;
public static final int TRIANGLE=1;
public static final int SQUARE=2;
public static final int CIRCLE=3;
@IntDef(flag=true,value={RECTANGLE,TRIANGLE,SQUARE,CIRCLE})
@Target({ElementType.PARAMETER,ElementType.METHOD,ElementType.FIELD})
@Retention(RetentionPolicy.SOURCE)
public @interface Model{
}
private @Model int value=RECTANGLE;
public void setShape(@Model int value){
this.value=value;
}
@Model
public int getShape(){
return this.value;
}
}
3.6 尽量使用IntentService而不是Service
如果应用程序当中需要使用Service来执行后台任务的话,请一定要注意只有当任务正在执行的时候才应该让Service运行起来。另外,当任务执行完之后去停止Service的时候,要小心Service停止失败导致内存泄漏的情况。
当我们启动一个Service时,系统会倾向于将这个Service所依赖的进程进行保留,这样就会导致这个进程变得非常消耗内存。并且,系统可以在LRU cache当中缓存的进程数量也会减少,导致切换应用程序的时候耗费更多性能。严重的话,甚至有可能会导致崩溃,因为系统在内存非常吃紧的时候可能已无法维护所有正在运行的Service所依赖的进程了。
为了能够控制Service的生命周期,Android官方推荐的最佳解决方案就是使用IntentService,这种Service的最大特点就是当后台任务执行结束后会自动停止,从而极大程度上避免了Service内存泄漏的可能性。
3.7 在合适的时候适当采用软引用和弱引用。
4.Bitmap中优化
可以参考另一篇文章:
Bitmap内存优化