Day9-OOM

Tips:

  • Lint工具静态分析可能错误jump
  • LeakCanary 检测内存泄漏

第一种 布局的锅

最初学习创建 Fragment, 在 inflate 时, 往往会忘记第三个参数

inflate(int resources,  当前布局D
         ViewGroup root, 根布局G
         boolean attachToRoot 是否依赖根布局G),

而去使用了两个参数的方法, 即第三个参数为true, 当new Fragment时 (多为 viewPager 添加Fragment), 会报 OOM, 没有特殊情况, attachToRoot 置为 false

第二种 Context 和 非静态内部类

什么是 Context

四大组件中 Activity, Service, 甚至 Application 的创建都离不开 Context
Context是一个抽象类, 提供了应用环境全局信息的接口, 它允许获取以应用为特征的资源和类型.
就是说通过它我们可以进行应用程序级别的操作(获取资源, 启动 activity, service, 接收intent),


Day9-OOM_第1张图片

context的子类是实现子类 ContextImpl 和 包装子类 ContextWrapper, 虽然 Activity, Application, service 都是 ContextWrapper 的子类, 但是它们的初始化过程都需要创建 ContextImpl 对象, 由 ContextImpl 对象实现Context中的方法.

一个应用中有几个context

应用中 Context 的数量 = Activity 的数量 + Service 的数量 + 1(Application 的数量),

Context的作用域

由于Context的具体实例都是由 ContextImpl 实现的, 因此在绝大多数场景下, Activity, Service 和 Application的context是可以通用的.
不过也有特殊的情况, 比如 Android 规定 Activity 和 Dialog 不能凭空出现, 一个Activity 的启动必须建立在另一个 Activity 的基础之上, 实现回退栈结构. Dialog 也必须实现在 Activity 之上, System 级的 Dialog 除外

  • 举例
    ApplicationContext 去启动 LaunchMode为 Standard的Actvity, 会报错.
    原因: 非 Activity 级的 context 没有回退栈, 所以用 FLAG_ACTIVITY_NEW_TASK, 创建一个新的任务栈, 此时 Activity 是以 SingleTask 模式启动的

几种context的总结

  • Context.getApplicationContext();
    和整个app的生命周期绑定, 可能因供应商而出问题, 不推荐
  • ContextWrapper.getBaseContext();
    从另一个上下文里获取context, 不推荐
  • Activity.getApplication();
    自建Application类, 提供静态方法, 尽量不使用
  • View.getContext();
    获取当前view的context, 通常是当前运行的activity

几种getResources总结 Link & getResources().getDrawable() 被废弃 Link

  1. 使用drawable资源但不为其设置theme主题

    ResourcesCompat.getDrawable(getResources(), R.drawable.name, null);
    
  2. 使用默认的activity主题

    ContextCompat.getDrawable(getActivity(), R.drawable.name);
    
  3. 使用自定义主题

    ResourcesCompat.getDrawable(getResources(), R.drawable.name, anotherTheme);
    

Context 造成 OOM

a. static方法保存了context

Activity 传入 context 实例 Util 后, 即使 activity 被销毁, 它的引用还是被Util保存, 不会被GC.

public class Util {   
  private Context mContext;  
  private static Util sInstance;  
    private Util(Context context) {  
        this.mContext = context;
    }  
    public static Util getInstance(Context context) {  
        if (sInstance == null) {  
            sInstance = new Util(context);  
        }  
        return sInstance;  
    }  
    //other methods  
}

非静态内部类和匿名内部类是会隐式持有外部类的引用(所以adapter可以作为单独一个包), GC的回收机制是有强引用的对象不会被回收
静态内部类不持有外部类的引用,且静态内部类可持有静态数据, 静态方法, 嵌套静态内部

解决方案:

  • 传入Application.Context(不建议, 可能导致ApplicationContext的泄漏jump
  • 自己将持有引用的量在onDestroy里置为null
  • 弱引用

b. adapter持有context

activity 创建 adapter 时传入 context, adapter 进行了耗时操作, 此时finish 掉 activity, activity被销毁, adapter没有置为null, adapter无法被回收
解决方案:
* 外部Adapter,
根据adapter的getView(int position, View convertView, ViewGroup parent)中的parent.getContext()获取, 如果需要button点击跳转, 添加接口回调.虽然这里的parent.getContext和原先传入的context一样, 但是Adapter不需要将activity保存在成员变量中, adapter就不会持有activity的引用了
* 内部Adapter,
添加static

c. Drawable(3.0前的bug, SDK已修复)

private static Drawable sBackground;
@Override
protected void onCreate(Bundle state) {
super.onCreate(state);

TextView label = new TextView(this);
label.setText("Leaks are bad");

if (sBackground == null) {
sBackground = getDrawable(R.drawable.large_bitmap);
}
label.setBackgroundDrawable(sBackground);

setContentView(label);
}

public void setBackgroundDrawable(Drawable background) {
...
background.setCallback(this);
}

看一下源码

public void setBackgroundDrawable(Drawable background) {
    ...
    background.setCallback(this);
 }

这里, Drawable持有了TextView的引用, TextView持有了Activity的引用, 所以Drawable持有了activity的引用, 3.0后在setCallback里添加了WeakReference, 所以bug被官方修复了

public final void setCallback(Callback cb) {
    mCallback = new WeakReference(cb);
}

总结

  1. 不要在Activity使用非静态内部类, 使用静态内部类, 并且将外部的引用作为弱引用持有(弱引用须判空), 常用于Handler

参考

  • 告诉你ListView的Adapter应该写在Activity外面还是里面
  • Java中的内部类和匿名类
  • Context 都没弄明白,还怎么做 Android 开发?
  • 匿名内部类持外部引用造成内存泄漏问题
  • Android中使用Handler引发的内存泄露
  • 待补充

你可能感兴趣的:(Day9-OOM)