Android中常出现的内存泄漏情况分析

Android中常出现的内存泄漏情况分析

@(内存)

  • static变量引用待释放类实例
  • 使用HandlerMessageQueue中添加需要等待的任务
  • 对需要进行注册的对象,进行了注册,但未进行反注册
  • 单例中持有了待释放对象的引用
  • 内部类匿名内部类中有外部类的引用,由于它们被引用导致外部类对象不能被释放
  • 具有销毁方法的资源在使用完后,没有调用相应方法对资源进行销毁。
  • Service执行完任务,不对其立即进行销毁

下面通过实例来对上面可能导致内存泄漏的各种情况进行说明。

有“static”变量引用待释放类实例

示例代码
public class StaticReferenceActivity extends AppCompatActivity {

    private static final String NAME = StaticReferenceActivity.class.getSimpleName();
    private static final String TAG = "sxd";

    public static StaticReferenceActivity sStaticReferenceActivity;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_static_reference);
        sStaticReferenceActivity = this;
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.i(TAG, NAME + "--onDestroy++");
    }

}
内存泄漏分析

  执行一次跳转到StaticReferenceActivity界面并退出。执行onDestroy()方法,则该界面被销毁。
这里写图片描述
  StaticReferenceActivity界面被销毁,则它对应用程序不再有用。它所占用的的资源,应该被释放。但当使用Memory Analyzer工具分析后,发现内存中仍有StaticReferenceActivity实例不能被GC掉。
这里写图片描述
  Memory Analyzer给我们指出了StaticReferenceActivity不能被垃圾回收的原因,有名为sStaticReferenceActivity的变量引用着StaticReferenceActivity的实例。查看程序,我们发现sStaticReferenceActivity是一个静态变量。我们知道静态变量会被存储在静态存储区中,而静态存储区中的数据在应用程序运行时一直存在。
  很多朋友在编写Java程序时,为了方便喜欢使用静态变量,它虽然方便,但使用不当确容易产生内存泄漏,所以本人建议,合理进行程序设计,尽量少用static变量。

使用“Handler”向“MessageQueue”中添加需要等待的任务

示例代码
public class HandlerActivity extends AppCompatActivity {

    private static final String NAME = HandlerActivity.class.getSimpleName();
    private static final String TAG = "sxd";

    private Handler mHandler;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler);
        initData();
    }

    private void initData() {
        mHandler = new Handler();
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                Log.i(TAG, NAME + "--5分钟后执行的任务++");
            }
        }, 5 * 60 * 1000);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.i(TAG, NAME + "--onDestroy++");
    }

}
内存泄漏分析

  执行一次跳转到HandlerActivity 界面并退出。执行onDestroy()方法,则该界面被销毁。
这里写图片描述
  HandlerActivity 界面被销毁,则它对应用程序不再有用。它所占用的的资源,应该被释放。但当使用Memory Analyzer工具分析后,发现内存中仍有HandlerActivity 实例不能被GC掉。
这里写图片描述
  Memory Analyzer给我们指出了HandlerActivity 不能被垃圾回收的原因,有名为mMessageQueue的变量引用着HandlerActivity 的实例。查看示例代码,我们发现我们用Handler向MessageQueue中加入了一个任务,但该任务要延迟5分钟才能执行,所以在改任务未被处理前,它将一直持有HandlerActivity的实例。导致HandlerActivity界面退出后,其占用内存不能被立即释放掉。所以要在退出HandlerActivity界面时,将任务移出MessageQueue

处理内存泄漏的代码
public class HandlerActivity extends AppCompatActivity {

    private static final String NAME = HandlerActivity.class.getSimpleName();
    private static final String TAG = "sxd";

    private Handler mHandler;
    private Runnable mRunnable;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler);
        initData();
    }

    private void initData() {
        mHandler = new Handler();
        mRunnable = new Runnable() {
            @Override
            public void run() {
                Log.i(TAG, NAME + "--5分钟后执行的任务++");
            }
        };
        mHandler.postDelayed(mRunnable, 5 * 60 * 1000);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mHandler.removeCallbacks(mRunnable);
        Log.i(TAG, NAME + "--onDestroy++");
    }

}

  示例程序所表现的问题并不严重,因为等待5分钟后,这部分内存依旧可以释放掉。但如果使用Handler实现了循环定时器,如果在不使用后,忘记将任务从MessageQueue中移出,那这部分内存在程序运行期间将永不会被释放。

对需要进行注册的对象,进行了“注册”,但未进行“反注册”

  现在很多项目都使用EventBus进行事件的分发/订阅。所以以它的使用为例,说明对象未进行反注册引发的内存泄漏。

示例代码
public class EventBusActivity extends AppCompatActivity implements View.OnClickListener {

    private static final String NAME = EventBusActivity.class.getSimpleName();
    private static final String TAG = "sxd";

    private TextView mTextView;
    private Button mButton;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_eventbus);
        initView();
    }

    private void initView() {
        mTextView = (TextView) this.findViewById(R.id.textview);
        mButton = (Button) this.findViewById(R.id.button);
        mButton.setOnClickListener(this);

        EventBus.getDefault().register(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.button :
                sendEventBus();
                break;
        }
    }

    private void sendEventBus() {
        Event event = new Event();
        event.setContent("EventBus未进行反注册泄漏");
        EventBus.getDefault().post(event);
    }

    public void onEventMainThread(Event event) {
        mTextView.setText(event.getContent());
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.i(TAG, NAME + "--onDestroy++");
    }

    /**
     * 发送事件
     */
    class Event {

        String content;

        public String getContent() {
            return content;
        }

        public void setContent(String content) {
            this.content = content;
        }
    }

}
内存泄漏分析

  执行一次跳转到EventBusActivity 界面并退出。执行onDestroy()方法,则该界面被销毁。
这里写图片描述
  EventBusActivity 界面被销毁,则它对应用程序不再有用。它所占用的的资源,应该被释放。但当使用Memory Analyzer工具分析后,发现内存中仍有EventBusActivity 实例不能被GC掉。
这里写图片描述
  Memory Analyzer给我们指出了EventBusActivity 不能被垃圾回收的原因,有名为EventBus的对象引用着。查看示例代码,我们发现我们使用方法EventBus.getDefault().register(this);将EventBusActivity的实例注册到了EventBus,但在EventBusActivity退出时却未进行反注册。
  解决该种内存泄漏,只需在onDestroy()方法中,调用EventBus.getDefault().unregister(this);进行反注册。

“单例”中持有了待释放对象的引用

示例代码
public class Instance {

    private static Instance sInstance;

    private Context mContext;

    private Instance(Context context) {
        mContext = context;
    }

    public static synchronized Instance getInstance(Context context) {
        if (sInstance == null) {
            sInstance = new Instance(context);
        }
        return sInstance;
    }

}
public class InstanceActivity extends AppCompatActivity {

    private static final String NAME = InstanceActivity.class.getSimpleName();
    private static final String TAG = "sxd";

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_instance);
        initData();
    }

    private void initData() {
        Instance.getInstance(this);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.i(TAG, NAME + "--onDestroy++");
    }

}
内存泄漏分析

  执行一次跳转到InstanceActivity 界面并退出。执行onDestroy()方法,则该界面被销毁。
这里写图片描述
  InstanceActivity 界面被销毁,则它对应用程序不再有用。它所占用的的资源,应该被释放。但当使用Memory Analyzer工具分析后,发现内存中仍有InstanceActivity 实例不能被GC掉。
这里写图片描述
  Memory Analyzer给我们指出了InstanceActivity 不能被垃圾回收的原因,在Instance实例中,有名为mContext的引用。查看示例代码,我们发现我们用InstanceActivity的实例初始化了Instance单例。我们知道单例对象是在应用程序运行时一直存在的。所以导致InstanceActivity对象不能释放。
  这种内存泄漏的解决方法是,不要用待释放对象去初始化单例对象,单例对象最好使用ApplicationContext去初始化。所以示例程序内存泄漏的解决方法,将Instance.getInstance(this);改为Instance.getInstance(this.getApplicationContext());

“内部类”和“匿名内部类”中有外部类的引用,由于它们被引用导致外部类对象不能被释放

示例代码
public interface Listener {

    void listener();

}
public class Instance {

    private static Instance sInstance;

    private Context mContext;
    private Listener mListener;

    private Instance(Context context) {
        mContext = context;
    }

    public static synchronized Instance getInstance(Context context) {
        if (sInstance == null) {
            sInstance = new Instance(context);
        }
        return sInstance;
    }

    public void setListener(Listener listener) {
        mListener = listener;
    }

}
public class InnerClassActivity extends AppCompatActivity {

    private static final String NAME = InnerClassActivity.class.getSimpleName();
    private static final String TAG = "sxd";

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_inner_class);
        initData();
    }

    private void initData() {
        Instance.getInstance(this.getApplicationContext()).setListener(new Listener() {
            @Override
            public void listener() {

            }
        });
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.i(TAG, NAME + "--onDestroy++");
    }

}
public class InnerClassActivity extends AppCompatActivity {

    private static final String NAME = InnerClassActivity.class.getSimpleName();
    private static final String TAG = "sxd";

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_inner_class);
        initData();
    }

    private void initData() {
        Instance.getInstance(this.getApplicationContext()).setListener(new MyListener());
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.i(TAG, NAME + "--onDestroy++");
    }

    class MyListener implements Listener {

        @Override
        public void listener() {

        }
    }

}
内存泄漏分析

  执行一次跳转到InnerClassActivity 界面并退出。执行onDestroy()方法,则该界面被销毁。
这里写图片描述
  InnerClassActivity 界面被销毁,则它对应用程序不再有用。它所占用的的资源,应该被释放。但当使用Memory Analyzer工具分析后,发现内存中仍有InnerClassActivity 实例不能被GC掉。
这里写图片描述
  Memory Analyzer给我们指出了InnerClassActivity 不能被垃圾回收的原因,在Instance实例中,有名为mListener的InnerClassActivity 内部类实例引用着。查看示例代码,我们发现我们将内部类或匿名内部类对象设置到了Instance单例中,导致InnerClassActivity 内部类的实例在程序运行期间始终被引用,不能释放。我们知道内部类和匿名内部类中持有外部类的引用,所以内部类的实例不能释放,将导致外部类的实例不能释放,发生内存泄漏。

具有销毁方法的“资源”在使用完后,没有调用相应方法对资源进行销毁

示例代码
public class BaiduMapActivity extends AppCompatActivity {

    private static final String NAME = BaiduMapActivity.class.getSimpleName();
    private static final String TAG = "sxd";

    private MapView mBaiduMapView;
    private BaiduMap mBaiduMap;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_baidumap);
        initView();
    }

    private void initView() {
        mBaiduMapView = (MapView) this.findViewById(R.id.mapview);
        //地图初始化
        mBaiduMapView.showScaleControl(false);
        mBaiduMapView.showZoomControls(false);
        mBaiduMap = mBaiduMapView.getMap();
        mBaiduMap.setMapType(BaiduMap.MAP_TYPE_NORMAL);//普通地图
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.i(TAG, NAME + "--onDestroy++");
    }

}
内存泄漏分析

  执行一次跳转到BaiduMapActivity 界面并退出。执行onDestroy()方法,则该界面被销毁。
这里写图片描述
  BaiduMapActivity 界面被销毁,则它对应用程序不再有用。它所占用的的资源,应该被释放。但当使用Memory Analyzer工具分析后,发现内存中仍有BaiduMapActivity 实例不能被GC掉。
这里写图片描述
  Memory Analyzer给我们指出了BaiduMapActivity 不能被垃圾回收的原因,百度地图中仍有资源引用着。查看示例代码,我们发现,我们使用了百度地图,却没有在BaiduMapActivity 界面退出时,对使用的百度地图资源进行销毁。
解决该中内存泄漏的方法,在不使用资源时,调用资源的销毁方法,对其进行清理和销毁。该示例程序中,只需在onDestroy()方法中,调用方法mBaiduMap.clear(); mBaiduMapView.onDestroy();对百度地图资源进行销毁。

源码地址

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章