Android中常出現的內存泄漏情況分析
@(內存)
- 有static變量引用待釋放類實例
- 使用Handler向MessageQueue中添加需要等待的任務
- 對需要進行註冊的對象,進行了註冊,但未進行反註冊
- 單例中持有了待釋放對象的引用
- 內部類和匿名內部類中有外部類的引用,由於它們被引用導致外部類對象不能被釋放
- 具有銷燬方法的資源在使用完後,沒有調用相應方法對資源進行銷燬。
- 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();
對百度地圖資源進行銷燬。