关于android内存泄露的问题

(1)使用getSystemService()造成的内存泄露
http://www.tuicool.com/articles/iyuErej
Android中有很多服务,比如PowerManager,AlarmManager,NotificationManager等,通常使用起来也很方便,就是使用Context.getSystemService方法来获得。
一次在公司开发项目开发中,突然LeakCanary弹出了一个内存泄漏的通知栏,不好,内存泄漏发生了。原因竟是和getSystemService有关。
为了排除干扰因素,我们使用一个简单的示例代码
public class MainActivity extends AppCompatActivity {
private static PowerManager powerManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
powerManager = (PowerManager)getSystemService(Context.POWER_SERVICE);
}
}

当退出MainActivity时,得到了LeakCanary的内存泄漏报告。如下图。
file:///tmp/ct_tmp/1.png

奇怪了,为什么PowerManager会持有Activity的实例呢,按照理解,PowerManager应该是持有Application的Context对象的。
因此,我们有必要对PowerManager的源码分析一下
1.PowerManager会持有一个Context实例,具体使用Activity还是Application的Context取决于调用者。
final Context mContext;
final IPowerManager mService;
final Handler mHandler;

/**
* {@hide}
*/
public PowerManager(Context context, IPowerManager service, Handler handler) {
mContext = context;
mService = service;
mHandler = handler;
}
2.负责缓存服务的实现在ContextImpl.java文件中
final Object[] mServiceCache = SystemServiceRegistry.createServiceCache
而Activity通过ContextImpl提供的setOuterContext方法设置mOuterContext
final void setOuterContext(Context context) {
mOuterContext = context;
}

因此Activity与ContextImpl的关系如下图
file:///tmp/ct_tmp/2.png

SystemServiceRegistry.java中获取PowerManager的实现。
registerService(Context.POWER_SERVICE, PowerManager.class,
new CachedServiceFetcher<PowerManager>() {
@Override
public PowerManager createService(ContextImpl ctx) {
IBinder b = ServiceManager.getService(Context.POWER_SERVICE);
IPowerManager service = IPowerManager.Stub.asInterface(b);
if (service == null) {
Log.wtf(TAG, "Failed to get power manager service.");
}
return new PowerManager(ctx.getOuterContext(),
service, ctx.mMainThread.getHandler());
}});
创建具体的服务的实现为core/java/android/app/SystemServiceRegistry.java

如何解决

不使用静态持有PowerManager
因为static是一个很容易和内存泄漏产生关联的因素
• static变量与类的生命周期相同
• 类的生命周期等同于类加载器
• 类加载器通常和进程的生命周期一致
所以通过去除static可以保证变量周期和Activity实例相同。这样就不会产生内存泄漏问题。

使用ApplicationContext
除了上面的方法之外,传入Application的Context而不是Activity Context也可以解决问题。
PowerManager powerManager = (PowerManager)getApplicationContext().getSystemService(Context.POWER_SERVICE);


是不是都要使用Application Context?
然而并非如此
以Activity为例,一些和UI相关的服务已经优先进行了处理
@Override
public Object getSystemService(@ServiceName @NonNull String name) {
if (getBaseContext() == null) {
throw new IllegalStateException(
"System services not available to Activities before onCreate()");
}

if (WINDOW_SERVICE.equals(name)) {
return mWindowManager;
} else if (SEARCH_SERVICE.equals(name)) {
ensureSearchManager();
return mSearchManager;
}
return super.getSystemService(name);
}

ContextThemeWrapper也优先处理了LayoutManager服务
@Override
public Object getSystemService(String name) {
if (LAYOUT_INFLATER_SERVICE.equals(name)) {
if (mInflater == null) {
mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this);
}
return mInflater;
}
return getBaseContext().getSystemService(name);
}

那到底改用哪个Context
• 如果服务和UI相关,则用Activity
• 如果是类似ALARM_SERVICE,CONNECTIVITY_SERVICE建议有限选用Application Context
• 如果出现出现了内存泄漏,排除问题,可以考虑使用Application Context

(2)避免android中context引起的内存泄露
Context是我们在编写Android程序经常使用到的对象,意思为上下文对象。 常用的有Activity的Context还是有Application的Context。Activity用来展示活动界面,包含了很多的视图,而视图 又含有图片,文字等资源。在Android中内存泄露很容易出现,而持有很多对象内存占用的Activity更加容易出现内存泄露,开发者需要特别注意这 个问题。
本文讲介绍Android中Context,更具体的说是Activity内存泄露的情况,以及如何避免Activity内存泄露,加速应用性能。

Drawable引起的内存泄露
Drawable引起内存泄露这个问题是比较隐晦,难以察觉的。在阅读了Romain Guy的 Avoiding memory leaks,结合grepcode查看源码才明白了。
在Android系统中,当我们进行了屏幕旋转,默认情况下,会销毁掉当前的Activity,并创建一个新的Activity并保持之前的状态。 在这个过程中,Android系统会重新加载程序的UI视图和资源。假设我们有一个程序用到了一个很大的Bitmap图像,我们不想每次屏幕旋转时都重新 加载这个Bitmap对象,最简单的办法就是将这个Bitmap对象使用static修饰。
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);
}

但是上面的方法在屏幕旋转时有可能引起内存泄露,无论是咋一看还是仔细看这段代码,都很难发现哪里引起了内存泄露。
当一个Drawable绑定到了View上,实际上这个View对象就会成为这个Drawable的一个callback成员变量,上面的例子中静 态的sBackground持有TextView对象lable的引用,而lable只有Activity的引用,而Activity会持有其他更多对象 的引用。sBackground生命周期要长于Activity。当屏幕旋转时,Activity无法被销毁,这样就产生了内存泄露问题。
2.3.7及以下版本Drawable的setCallback方法的实现
public final void setCallback(Callback cb) {
mCallback = cb;
}

好在从4.0.1开始,引入了 弱引用处理这个问题,弱引用在GC回收时,不会阻止GC回收其指向的对象,避免了内存泄露问题。
public final void setCallback(Callback cb) {
mCallback = new WeakReference<Callback>(cb);
}


单例引起的内存泄露
单例是我们比较简单常用的一种设计模式,然而如果单例使用不当也会导致内存泄露。 比如这样一个例子,我们使用饿汉式初始化单例,AppSettings我们需要持有一个Context作为成员变量,如果我们按照下面的实现其实是有问题。
public class AppSettings {
private Context mAppContext;
private static AppSettings sInstance = new AppSettings();

//some other codes
public static AppSettings getInstance() {
return sInstance;
}

public final void setup(Context context) {
mAppContext = context;
}
}

sInstance作为静态对象,其生命周期要长于普通的对象,其中也包含Activity,当我们进行屏幕旋转,默认情况下,系统会销毁当前Activity,然后当前的Activity被一个单例持有,导致垃圾回收器无法进行回收,进而产生了内存泄露。
解决的方法就是不持有Activity的引用,而是持有Application的Context引用。代码如下修改
public final void setup(Context context) {
mAppContext = context.getApplicationContext();
}

访问这里了解更多关于 单例模式的问题

条条方法返回Context
通常我们想要获取Context对象,主要有以下四种方法
• View.getContext,返回当前View对象的Context对象,通常是当前正在展示的Activity对象。
• Activity.getApplicationContext,获取当前Activity所在的(应用)进程的Context对象,通常我们使用Context对象时,要优先考虑这个全局的进程Context。
• ContextWrapper.getBaseContext():用来获取一个ContextWrapper进行装饰之前的Context,可以使用这个方法,这个方法在实际开发中使用并不多,也不建议使用。
• Activity.this 返回当前的Activity实例,如果是UI控件需要使用Activity作为Context对象,但是默认的Toast实际上使用ApplicationContext也可以。

(3)使用handler导致的内存泄露
http://droidyue.com/blog/2014/12/28/in-android-handler-classes-should-be-static-or-leaks-might-occur/
在Android常用编程中,Handler在进行异步操作并处理返回结果时经常被使用。通常我们的代码会这样实现。
public class SampleActivity extends Activity {

private final Handler mLeakyHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// ...
}
}
}

但是,其实上面的代码可能导致内存泄露,当你使用Android lint工具的话,会得到这样的警告
In Android, Handler classes should be static or leaks might occur, Messages enqueued on the application thread’s MessageQueue also retain their target Handler. If the Handler is an inner class, its outer class will be retained as well. To avoid leaking the outer class, declare the Handler as a static nested class with a WeakReference to its outer class
看到这里,可能还是有一些搞不清楚,代码中哪里可能导致内存泄露,又是如何导致内存泄露的呢?那我们就慢慢分析一下。
1.当一个Android应用启动的时候,会自动创建一个供应用主线程使用的Looper实例。Looper的主要工作就是一个一个处理消息队列中 的消息对象。在Android中,所有Android框架的事件(比如Activity的生命周期方法调用和按钮点击等)都是放入到消息中,然后加入到 Looper要处理的消息队列中,由Looper负责一条一条地进行处理。主线程中的Looper生命周期和当前应用一样长。
2.当一个Handler在主线程进行了初始化之后,我们发送一个target为这个Handler的消息到Looper处理的消息队列时,实际上 已经发送的消息已经包含了一个Handler实例的引用,只有这样Looper在处理到这条消息时才可以调用 Handler#handleMessage(Message)完成消息的正确处理。
3.在Java中,非静态的内部类和匿名内部类都会隐式地持有其外部类的引用。静态的内部类不会持有外部类的引用。关于这一内容可以查看 细话Java:”失效”的private修饰符
确实上面的代码示例有点难以察觉内存泄露,那么下面的例子就非常明显了
public class SampleActivity extends Activity {

private final Handler mLeakyHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// ...
}
}

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

// Post a message and delay its execution for 10 minutes.
mLeakyHandler.postDelayed(new Runnable() {
@Override
public void run() { /* ... */ }
}, 1000 * 60 * 10);

// Go back to the previous Activity.
finish();
}
}

分析一下上面的代码,当我们执行了Activity的finish方法,被延迟的消息会在被处理之前存在于主线程消息队列中10分钟,而这个消息中 又包含了Handler的引用,而Handler是一个匿名内部类的实例,其持有外面的SampleActivity的引用,所以这导致了 SampleActivity无法回收,进行导致SampleActivity持有的很多资源都无法回收,这就是我们常说的内存泄露。
注意上面的new Runnable这里也是匿名内部类实现的,同样也会持有SampleActivity的引用,也会阻止SampleActivity被回收。
要解决这种问题,思路就是避免使用非静态内部类,继承Handler时,要么是放在单独的类文件中,要么就是使用静态内部类。因为静态的内部类不会持有外部类的引用,所以不会导致外部类实例的内存泄露。当你需要在静态内部类中调用外部的Activity时,我们可以使用 弱引用来处理。另外关于同样也需要将Runnable设置为静态的成员属性。注意:一个静态的匿名内部类实例不会持有外部类的引用。 修改后不会导致内存泄露的代码如下
public class SampleActivity extends Activity {

/**
* Instances of static inner classes do not hold an implicit
* reference to their outer class.
*/
private static class MyHandler extends Handler {
private final WeakReference<SampleActivity> mActivity;

public MyHandler(SampleActivity activity) {
mActivity = new WeakReference<SampleActivity>(activity);
}

@Override
public void handleMessage(Message msg) {
SampleActivity activity = mActivity.get();
if (activity != null) {
// ...
}
}
}

private final MyHandler mHandler = new MyHandler(this);

/**
* Instances of anonymous classes do not hold an implicit
* reference to their outer class when they are "static".
*/
private static final Runnable sRunnable = new Runnable() {
@Override
public void run() { /* ... */ }
};

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

// Post a message and delay its execution for 10 minutes.
mHandler.postDelayed(sRunnable, 1000 * 60 * 10);

// Go back to the previous Activity.
finish();
}
}
其实在Android中很多的内存泄露都是由于在Activity中使用了非静态内部类导致的,就像本文提到的一样,所以当我们使用时要非静态内部 类时要格外注意,如果其实例的持有对象的生命周期大于其外部类对象,那么就有可能导致内存泄露。个人倾向于使用文章的静态类和弱引用的方法解决这种问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值