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();對百度地圖資源進行銷燬。

源碼地址

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