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),
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
getResources().getDrawable()
-
使用drawable资源但不为其设置theme主题
ResourcesCompat.getDrawable(getResources(), R.drawable.name, null);
-
使用默认的activity主题
ContextCompat.getDrawable(getActivity(), R.drawable.name);
-
使用自定义主题
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);
}
总结
- 不要在Activity使用非静态内部类, 使用静态内部类, 并且将外部的引用作为弱引用持有(弱引用须判空), 常用于Handler
参考
- 告诉你ListView的Adapter应该写在Activity外面还是里面
- Java中的内部类和匿名类
- Context 都没弄明白,还怎么做 Android 开发?
- 匿名内部类持外部引用造成内存泄漏问题
- Android中使用Handler引发的内存泄露
- 待补充