@linux1s1s
2017-01-22T16:20:28.000000Z
字数 8486
阅读 4631
AndroidMemory
2015-05
阅读这篇博文请阅读 Android 内存泄露 (一)、Android 内存泄露 (二)两篇相关的博文。此篇博文主要给出一些内存泄露的Demo,目的是让内存泄露案例更直观易懂,以期在工作中更好的防范内存泄露。
先看一下代码
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);
}
上面的代码片段会有 memory-leaks 情况发生吗?
为了回答问题,我们直接追踪setBackgroundDrawable(...)
方法。
public void setBackgroundDrawable(Drawable background) {
// ... ...
background.setCallback(this);
// ... ...
}
注意到这里Drawable
类有个设置回调的方法,为了说明问题,我们从比较旧的SDK版本说起,然后再对比新的SDK版本,看看这么一个设置回调方法有神马变动。
Froyo
& GingerBread
版本setCallback方法
public final void setCallback(Callback cb) {
mCallback = cb;
}
之后的版本是这样的
public final void setCallback(Callback cb) {
mCallback = new WeakReference<Callback>(cb);
}
看到上面两处代码,是不是感觉Google也有马失前蹄的时候,Google的失误从另外一个侧面给我们提供了一个解决内存泄露的方案---对于长生命周期的实例请使用软引用防止内存泄露。所以上面的问题,需要分开来说,对于GingerBread
版本之前是存在内存泄露的,而对于该版本之后,已经修复了该泄漏问题。
我们先来看一下Context
的实现类ContextImpl
部分实现代码
class ContextImpl extends Context {
private final static String TAG = "ContextImpl";
private final static boolean DEBUG = false;
...
static {
registerService(CONNECTIVITY_SERVICE, new ServiceFetcher() {
public Object createService(ContextImpl ctx) {
IBinder b = ServiceManager.getService(CONNECTIVITY_SERVICE);
return new ConnectivityManager(IConnectivityManager.Stub.asInterface(b));
}});
...
}
...
}
上面代码片段是ContextImpl
类其中的部分实现,这里重点突出了ConnectivityManager
的注册部分,除此之外,系统还有其他服务,这些系统服务在ContextImpl实例初始化的时候,就为此Context注册了一些列系统服务,当我们在App中需要监听网络状况时可以方便的注册Receiver就可以了,就想下面这样。
private class ConnectionReiver extends BroadcastReceiver
{
@Override
public void onReceive(Context context, Intent intent)
{
//首先通过Context实例得到上面注册的系统网络服务
final ConnectivityManager cm = (ConnectivityManager) context
.getSystemService(Context.CONNECTIVITY_SERVICE);
final NetworkInfo networkInfo = cm.getActiveNetworkInfo();
//通过networkInfo实例可以判断当前是否处于连接网络状态
if ((networkInfo != null) && (networkInfo.isConnected()))
{
if (mHasShowDisconnection)
{
//如果当前网络处于连接状态,结束原先通过该MainActivity启动的DisconnectInternetActivity,
finishActivity(DisconnectInternetActivity.REQUESTCODE_DISSCONNECTINTERNET);
mHasShowDisconnection = false;
}
}
else
{
//如果当前网络处于断网状态,那么启动DisconnectInternetActivity,这里有另外一个知识点,MainActivity通过设置request标志启动DisconnectInternetActivity,也可以通过这个request标志结束DisconnectInternetActivity
Intent startIntent = new Intent(MainActivity.this,
DisconnectInternetActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivityForResult(
startIntent,
DisconnectInternetActivity.REQUESTCODE_DISSCONNECTINTERNET);
mHasShowDisconnection = true;
}
}
}
接下来看看注册的过程
public class MainActivity extends Activity
{
private ConnectionReiver mConnectionReiver;
@Override
protected void onCreate(Bundle savedInstanceState)
{
//直接注册ConnectionReiver,通过Filter标志位 ConnectivityManager.CONNECTIVITY_ACTION监听系统网络状态
registerReceiver(mConnectionReiver = new ConnectionReiver(),
new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
}
}
如果代码写到这里,请问上面有木有啥问题?
讨论这个问题前,先说一下生命周期问题,系统服务生命周期大于App生命周期这点毫无疑问,而上面代码长生命周期持有短生命周期的引用,会导致短生命周期实例所持有的资源释放不了,由此引发内存泄露。
为了避免此类泄露问题,我们应该在每次调用类似registerReceiver
方法的时候,顺手加上注销unregisterReceiver
方法。
@Override
protected void onDestroy()
{
if (mConnectionReiver != null)
{
unregisterReceiver(mConnectionReiver);
mConnectionReiver = null;
}
}
但是有许多时候,如果我们加上面的注销方法反而会抛出 java.lang.IllegalArgumentException: Receiver not registered ...
这个让我们丈二和尚摸不着头脑,这里说一下原因:
receiver对象没有registerReceiver()成功(没有调用到),于是unregister的时候提示出错:
所以我们对unregisterReceiver包装一下,确保不会引起此类异常:
private void unregisterReceiverSafe(BroadcastReceiver receiver) {
try {
getContext().unregisterReceiver(receiver);
} catch (IllegalArgumentException e) {
// ignore
}
}
由于在Android开发中Handler的使用很频繁,一不小心就会导致内存泄露,关于这方面的典型例子可以参考 Android 内存泄露 (一),不过这里小结一下
造成Handler内存泄露无非有两种情况
内部类
内部类持有外部类Activity的引用,当Handler对象有Message在排队,则无法释放,进而导致Activity对象不能释放。
如果是声明为static,则该内部类不持有外部Acitivity的引用,则不会阻塞Activity对象的释放。
如果声明为static后,可在其内部声明一个弱引用(WeakReference)引用外部类。
public class MainActivity extends Activity {
private CustomHandler mHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mHandler = new CustomHandler(this);
}
static class CustomHandlerextends Handler {
// 内部声明一个弱引用,引用外部类
private WeakReference<MainActivity > activityWeakReference;
public MyHandler(MyActivity activity) {
activityWeakReference= new WeakReference<MainActivity >(activity);
}
// ... ...
}
}
Handler生命周期和Activity不同步
// 一切都是为了不要让mHandler拖泥带水
@Override
public void onDestroy() {
mHandler.removeMessages(MESSAGE_1);
mHandler.removeMessages(MESSAGE_2);
mHandler.removeMessages(MESSAGE_3);
mHandler.removeMessages(MESSAGE_4);
// ... ...
mHandler.removeCallbacks(mRunnable);
// ... ...
}
或者干脆点,移除所有的runable和message
@Override
public void onDestroy() {
/**
* Remove any pending posts of callbacks and sent messages whose
* <var>obj</var> is <var>token</var>. If <var>token</var> is null,
* all callbacks and messages will be removed.
*/
mHandler.removeCallbacksAndMessages(null);
}
这部分可以参考 Android 内存泄露 (二)
我们先来看看SDK抛出的一大推英文说明
/**
*AsyncTask enables proper and easy use of the UI thread. This class allows to
* perform background operations and publish results on the UI thread without
* having to manipulate threads and/or handlers.
*
*AsyncTask is designed to be a helper class around {@link Thread} and {@link Handler}
* and does not constitute a generic threading framework. AsyncTasks should ideally be
* used for short operations (a few seconds at the most.) If you need to keep threads
* running for long periods of time, it is highly recommended you use the various APIs
* provided by thejava.util.concurrent
pacakge such as {@link Executor},
* {@link ThreadPoolExecutor} and {@link FutureTask}.
上面大概意思说一下:AsyncTask适用于短耗时操作,最多几秒钟。如果你想长时间耗时操作,请使用其他java.util.concurrent包下的API,比如Executor, ThreadPoolExecutor 和 FutureTask.
所以AsyncTask具有一定的使用范围,如果在长时间耗时操作使用AsyncTask时,尤其要注意和Activity保持同步,针对这点,你可以对所谓的AsyncTask进行包装改良,详情请看 AsyncTask 改良
TimerTask对象在和Timer的schedule()方法配合使用的时候极容易造成内存泄露。
```java
private void startTimer(){
if (mTimer == null) {
mTimer = new Timer();
}
if (mTimerTask == null) {
mTimerTask = new TimerTask() {
@Override
public void run() {
// todo
}
};
}
if(mTimer != null && mTimerTask != null )
mTimer.schedule(mTimerTask, 1000, 1000);
}
java
泄露的点是,忘记cancel掉Timer和TimerTask实例。cancel的时机同cursor篇说的,在合适的时候cancel。
private void cancelTimer(){
if (mTimer != null) {
mTimer.cancel();
mTimer = null;
}
}
java
##Observer对象
Observer对象的泄露,也是一种常见、易发现、易解决的泄露类型。
// 其实也非常简单,只不过ContentObserver是系统的例子,有必要单独拿出来提示一下大家,不可掉以轻心
private final ContentObserver mSettingsObserver = new ContentObserver(new Handler()) {
@Override
public void onChange(boolean selfChange, Uri uri) {
// todo
}
};
@Override
public void onStart() {
super.onStart();
// register the observer
getContentResolver().registerContentObserver(Settings.Global.getUriFor(
xxx), false, mSettingsObserver);
}
@Override
public void onStop() {
super.onStop();
// unregister it when stoping
getContentResolver().unregisterContentObserver(mSettingsObserver);
}
java
看完示例,我们来看看病例:
private final class SettingsObserver implements Observer {
public void update(Observable o, Object arg) {
// todo ...
}
}
mContentQueryMap = new ContentQueryMap(mCursor, Settings.System.XXX, true, null);
mContentQueryMap.addObserver(new SettingsObserver());
```
这里把匿名内部类当做参数传过去相当不合适,所以,有些懒是不能偷的,有些语法糖是不能吃的。解决方案就是,在不需要或退出的时候delete这个Observer。
private Observer mSettingsObserver;
@Override
public void onResume() {
super.onResume();
if (mSettingsObserver == null) {
mSettingsObserver = new SettingsObserver();
}
mContentQueryMap.addObserver(mSettingsObserver);
}
@Override
public void onStop() {
super.onStop();
if (mSettingsObserver != null) {
mContentQueryMap.deleteObserver(mSettingsObserver);
}
mContentQueryMap.close();
}
注意一点,不同的注册方法,不同的反注册方法。
/*
addCallback <==> removeCallback
registerReceiver <==> unregisterReceiver
addObserver <==> deleteObserver
registerContentObserver <==> unregisterContentObserver
... ...
*/
先看一下异常信息
android.view.WindowManager$BadTokenException: Unable to add window -- token android.os.BinderProxy@438afa60 is not valid; is your activity running?
一般发生于Handler的MESSAGE在排队,Activity已退出,然后Handler才开始处理Dialog相关事情。
关键点就是,怎么判断Activity是退出了,有人说,在onDestroy中设置一个FLAG。我很遗憾的告诉你,这个错误很有可能还会出来。
解决方案是:使用isFinishing()判断Activity是否退出。
Handler handler = new Handler() {
public void handleMessage(Message msg) {
switch (msg.what) {
case MESSAGE_1:
// isFinishing == true, 则不处理,尽快结束
if (!isFinishing()) {
// 不退出
// removeDialog()
// showDialog()
}
break;
default:
break;
}
super.handleMessage(msg);
}
};
上面的内存泄露,只是冰山一角,这些泄露大部分原因都是相同的,几乎90%的原因是 长生命周期持有短生命周期的引用,导致GC无法有效回收内存。
以上是内存泄露的情况,另外如下想进一步了解如何查看内存泄露,可以查看博客 Android 内存分析(一)以及(二)
参考:http://www.cnblogs.com/qianxudetianxia/p/3645106.html 适当做了修改 特此说明