一个对象只具有软引用,在内存不足时
,这个对象才会被 GC 回收。在 GC 时,如果一个对象只存在弱引用,那么它将会被回收
。与 Android 中的差异:在 2.3 以后版本中,即使内存够用,Android 系统会优先将 SoftReference 的对象提前回收掉, 其他和 Java 中是一样的。
因此谷歌官方建议用
LruCache
(least recentlly use 最少最近使用算法)。会将内存控制在一定的大小内, 超出最大值时会自动回收, 这个最大值开发者自己定。
长生命周期的对象
持有 短生命周期对象**强/软引用**
,导致本应该被回收的短生命周期的对象却无法被正常回收。
例如在单例模式中,我们常常在获取单例对象时需要传一个 Context 。单例对象是一个长生命周期的对象(应用程序结束时才终结),而如果我们传递的是某一个 Activity 作为 context,那么这个 Activity 就会因为引用被持有而无法销毁,从而导致内存泄漏。
由于单例模式的静态特性,使得它的生命周期和我们的应用一样长,一不小心让单例无限制的持有 Activity 的强引用就会导致内存泄漏。
public class BaseApplication extends Application{
private static ApplicationContext sContext;
@Override
public void onCreate(){
super.onCreate();
sContext = getApplicationContext();
}
public static Context getApplicationContext(){
return sContext;
}
}
由于 Handler 属于 TLS(Thread Local Storage)变量,导致它的生命周期和 Activity 不一致。因此通过 Handler 来更新 UI 一般很难保证跟 View 或者 Activity 的生命周期一致,故很容易导致无法正确释放。
例如:
public class HandlerBadActivity extends AppCompatActivity {
private final Handler handler = new Handler(){//非静态内部类,持有外部类的强引用
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler_bad);
// 延迟 5min 发送一个消息
handler.postDelayed(new Runnable() {
//内部会将该 Runable 封装为一个 Message 对象,同时将 Message.target 赋值为 handler
@Override
public void run() {
//do something
}
}, 1000 * 60 * 5);
this.finish();
}
}
上面的代码中发送了了一个延时 5 分钟执行的 Message,当该 Activity 退出的时候,延时任务(Message)还在主线程的 MessageQueue 中等待,此时的 Message 持有 Handler 的强引用
(创建时通过 Message.target 进行指定),并且由于 Handler 是 HandlerBadActivity 的非静态内部类
,所以 Handler 会持有一个指向 HandlerBadActivity 的强引用
,所以虽然此时 HandlerBadActivity 调用了 finish 也无法进行内存回收,造成内存泄漏。
将 Handler 声明为静态内部类
,但是要注意 **如果用到 Context 等外部类的 非static 对象,还是应该使用 ApplicationContext 或者通过弱引用来持有这些外部对象**
。
public class HandlerGoodActivity extends AppCompatActivity {
private static final class MyHandler extends Handler{//声明为静态内部类(避免持有外部类的强引用)
private final WeakReference mActivity;
public MyHandler(HandlerGoodActivity activity){
this.mActivity = new WeakReference(activity);//使用弱引用
}
@Override
public void handleMessage(Message msg) {
HandlerGoodActivity activity = mActivity.get();
if (activity == null || activity.isFinishing() || activity.isDestroyed()) {//判断 activity 是否为空,以及是否正在被销毁、或者已经被销毁
removeCallbacksAndMessages(null);
return;
}
// do something
}
}
private final MyHandler myHandler = new MyHandler(this);
}
static 修饰的变量位于内存的方法区,其生命周期与 App 的生命周期一致
。 这必然会导致一系列问题,如果你的 app 进程设计上是长驻内存的,那即使 app 切到后台,这部分内存也不会被释放。
不要在类初始化时初始化静态成员,也就是可以考虑懒加载。架构设计上要思考是否真的有必要这样做,尽量避免。如果架构需要这么设计,那么此对象的生命周期你有责任管理起来。
当然,Application 的 context 不是万能的,所以也不能随便乱用,对于有些地方则必须使用 Activity 的 Context,对于Application,Service,Activity三者的Context的应用场景如下:
功能 | Application | Service | Activity |
---|---|---|---|
Start an Activity | NO1 | NO1 | YES |
Show a Dialog | NO | NO | YES |
Layout Inflation | YES | YES | YES |
Start an Service | YES | YES | YES |
Bind an Service | YES | YES | YES |
Send a Broadcast | YES | YES | YES |
Register BroadcastReceiver | YES | YES | YES |
Load Resource Values | YES | YES | YES |
- NO1 表示 Application 和 Service 可以启动一个 Activity,不过
需要创建一个新的 task 任务队列
。- 对于 Dialog 而言,只有在 Activity 中才能创建。
为了方便我们使用一些常见的系统服务,Activity 做了一些封装。比如说,可以通过 getPackageManager
在 Activtiy 中获取 PackageManagerService
,但是,里面实际上调用了 Activity 对应的 ContextImpl 中的 getPackageManager 方法
ContextWrapper#getPackageManager
@Override
public PackageManager getPackageManager() {
return mBase.getPackageManager();
}
ContextImpl#getPackageManager
@Override
public PackageManager getPackageManager() {
if (mPackageManager != null) {
return mPackageManager;
}
IPackageManager pm = ActivityThread.getPackageManager();
if (pm != null) {
// Doesn't matter if we make more than one instance.
return (mPackageManager = new ApplicationPackageManager(this, pm));//创建 ApplicationPackageManager
}
return null;
}
ApplicationPackageManager#ApplicationPackageManager
ApplicationPackageManager(ContextImpl context,
IPackageManager pm) {
mContext = context;//保存 ContextImpl 的强引用
mPM = pm;
}
private UserManagerService(Context context, PackageManagerService pm,
Object packagesLock, File dataDir) {
mContext = context;//持有外部 Context 引用
mPm = pm;
//代码省略
}
PackageManagerService#PackageManagerService
public class PackageManagerService extends IPackageManager.Stub {
static UserManagerService sUserManager;//持有 UMS 静态引用
public PackageManagerService(Context context, Installer installer,
boolean factoryTest, boolean onlyCore) {
sUserManager = new UserManagerService(context, this, mPackages);//初始化 UMS
}
}
遇到的内存泄漏问题是因为在 Activity 中调用了 getPackageManger 方法获取 PMS ,该方法调用的是 ContextImpl,此时如果ContextImpl 中 PackageManager 为 null,就会创建一个 PackageManger(ContextImpl 会将自己传递进去,而 ContextImpl 的 mOuterContext 为 Activity),创建 PackageManager 实际上会创建 PackageManagerService(简称 PMS),而 PMS 的构造方法中会创建一个 UserManger(UserManger 初始化之后会持有 ContextImpl 的强引用)。
只要 PMS 的 class 未被销毁,那么就会一直引用着 UserManger ,进而导致其关联到的资源无法正常释放。
将getPackageManager()
改为getApplication()#getPackageManager()
。这样引用的就是 Application Context,而非 Activity 了。
因为使用非静态内部类和匿名类都会默认持有外部类的引用,如果生命周期不一致,就会导致内存泄漏。
public class NestedClassLeakActivity extends AppCompatActivity {
class InnerClass {//非静态内部类
}
private static InnerClass sInner;//指向非静态内部类的静态引用
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_nested_class);
if (sInner == null) {
sInner = new InnerClass();//创建非静态内部类的实例
}
}
}
非静态内部类默认会持有外部类的引用,而外部类中又有一个该非静态内部类的静态实例,该静态实例的生命周期和应用的一样长,而静态实例又持有 Activity 的引用,因此导致 Activity 的内存资源不能正常回收。
将该内部类设为静态内部类 也可以将该内部类抽取出来封装成一个单例
我们通常会把一些对象的引用加入到集合容器(比如ArrayList)中,当我们不再需要该对象时(通常会调用 remove 方法),并没有把它的引用从集合中清理掉(其中的一种情况就是 remove 方法没有将不再需要的引用赋值为 null),下面以 ArrayList 的 remove 方法为例
public E remove( int index) {
// 数组越界检查
RangeCheck(index);
modCount++;
// 取出要删除位置的元素,供返回使用
E oldValue = (E) elementData[index];
// 计算数组要复制的数量
int numMoved = size - index - 1;
// 数组复制,就是将index之后的元素往前移动一个位置
if (numMoved > 0)
System. arraycopy(elementData, index+1, elementData, index,
numMoved);
// 将数组最后一个元素置空(因为删除了一个元素,然后index后面的元素都向前移动了,所以最后一个就没用了),好让gc尽快回收
elementData[--size ] = null; // Let gc do its work
return oldValue;
}
WebView 解析网页时会申请 Native堆内存
用于保存页面元素,当页面较复杂时会有很大的内存占用。如果页面包含图片,内存占用会更严重。并且打开新页面时,为了能快速回退, 之前页面占用的内存也不会释放
。有时浏览十几个网页,都会占用几百兆的内存。这样加载网页较多时,会导致系统不堪重负,最终强制关闭应用,也就是出现应用闪退或重启。
由于占用的都是 Native 堆内存
,所以 实际占用的内存大小不会显示在常用的 DDMS Heap 工具中
( DMS Heap 工具看到的只是Java虚拟机分配的内存,即使Native堆内存已经占用了几百兆,这里显示的还只是几兆或十几兆)。只有使用 adb shell 中的一些命令比如 dumpsys meminfo 包名,或者在程序中使用 Debug.getNativeHeapSize()
才能看到 Native 堆内存信息。
据说由于 WebView 的一个 BUG,即使它所在的 Activity(或者Service) 结束也就是 onDestroy() 之后,或者直接调用 WebView.destroy()之后,它所占用这些内存也不会被释放。
把使用了 WebView 的 Activity (或者 Service) 放在单独的进程里。
使用 WebView 的页面(Activity),在生命周期结束页面退出(onDestory)的时候,主动调用WebView.onPause()==以及==WebView.destory()以便让系统释放 WebView 相关资源。
Android 3.0 以下,Bitmap 在不使用的时候没有使用 recycle() 释放内存。
非静态内部类的静态实例
容易造成内存泄漏:即一个类中如果你不能够控制它其中内部类的生命周期(譬如Activity中的一些特殊Handler等),则尽量使用静态类和弱引用来处理(譬如ViewRoot的实现)。
警惕线程未终止造成的内存泄露
;譬如在 Activity 中关联了一个生命周期超过 Activity 的 Thread,在退出 Activity 时切记结束线程。
一个典型的例子就是 HandlerThread 的 run 方法。该方法在这里是一个死循环,它不会自己结束,线程的生命周期超过了 Activity 生命周期,我们必须手动在 Activity 的销毁方法中中调用 thread.getLooper().quit() 才不会泄露。
对象的注册与反注册没有成对出现
造成的内存泄露;譬如注册广播接收器、注册观察者(典型的譬如数据库的监听)等。
创建与关闭没有成对出现造成的泄露
;譬如Cursor资源必须手动关闭,WebView必须手动销毁,流等对象必须手动关闭等。
避免代码设计模式的错误造成内存泄露;譬如循环引用,A 持有 B,B 持有 C,C 持有 A,这样的设计谁都得不到释放。
加群 Android IOC架构设计领取获取往期Android高级架构资料、源码、笔记、视频。高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter全方面的Android进阶实践技术,群内还有技术大牛一起讨论交流解决问题。