問題引發內存泄漏代碼php
public class LoginManager { private static LoginManager mInstance; private Context mContext; private LoginManager(Context context) { this.mContext = context; //修改代碼:this.mContext = context.getApplicationContext(); } public static LoginManager getInstance(Context context) { if (mInstance == null) { synchronized (LoginManager.class) { if (mInstance == null) { mInstance = new LoginManager(context); } } } return mInstance; } public void dealData() {} }
使用場景java
LoginManager.getInstance(this).dealData();
看看報錯截圖android
解決辦法:git
解決Handler內存泄露主要2點github
問題代碼面試
public class MainActivity extends AppCompatActivity { private Handler mHandler = new Handler(); private TextView mTextView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mTextView = (TextView) findViewById(R.id.text); //模擬內存泄露 mHandler.postDelayed(new Runnable() { @Override public void run() { mTextView.setText("yangchong"); } }, 2000); } }
形成內存泄漏緣由分析segmentfault
查看報錯結果以下:設計模式
解決方案markdown
第一種解決辦法網絡
@Override protected void onDestroy() { super.onDestroy(); if(handler!=null){ handler.removeCallbacksAndMessages(null); handler = null; } }
第二種解決方案
//自定義handler public static class HandlerHolder extends Handler { WeakReference<OnReceiveMessageListener> mListenerWeakReference; /** * @param listener 收到消息回調接口 */ HandlerHolder(OnReceiveMessageListener listener) { mListenerWeakReference = new WeakReference<>(listener); } @Override public void handleMessage(Message msg) { if (mListenerWeakReference!=null && mListenerWeakReference.get()!=null){ mListenerWeakReference.get().handlerMessage(msg); } } } //建立handler對象 private HandlerHolder handler = new HandlerHolder(new OnReceiveMessageListener() { @Override public void handlerMessage(Message msg) { switch (msg.what){ case 1: TextView textView1 = (TextView) msg.obj; showBottomInAnimation(textView1); break; case 2: TextView textView2 = (TextView) msg.obj; showBottomOutAnimation(textView2); break; } } }); //發送消息 Message message = new Message(); message.what = 1; message.obj = textView; handler.sendMessageDelayed(message,time); 即推薦使用靜態內部類 + WeakReference 這種方式。每次使用前注意判空。
所以總結來看, 線程產生內存泄露的主要緣由有兩點:
例如以下代碼,在onCreate()方法中啓動一個線程,並用一個靜態變量threadIndex標記當前建立的是第幾個線程
public class ThreadActivity extends AppCompatActivity { private final String TAG = "ThreadActivity"; private static int threadIndex; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_thread); threadIndex++; new Thread(new Runnable() { @Override public void run() { int j = threadIndex; while (true) { Log.e(TAG, "Hi--" + j); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); } }
04-04 08:15:16.373 23731-23911/com.yc.leakdemo E/ThreadActivity: Hi--2 04-04 08:15:16.374 23731-26132/com.yc.leakdemo E/ThreadActivity: Hi--4 04-04 08:15:16.374 23731-23970/com.yc.leakdemo E/ThreadActivity: Hi--3 04-04 08:15:16.374 23731-23820/com.yc.leakdemo E/ThreadActivity: Hi--1 04-04 08:15:16.852 23731-26202/com.yc.leakdemo E/ThreadActivity: Hi--5 04-04 08:15:18.374 23731-23911/com.yc.leakdemo E/ThreadActivity: Hi--2 04-04 08:15:18.374 23731-26132/com.yc.leakdemo E/ThreadActivity: Hi--4 04-04 08:15:18.376 23731-23970/com.yc.leakdemo E/ThreadActivity: Hi--3 04-04 08:15:18.376 23731-23820/com.yc.leakdemo E/ThreadActivity: Hi--1 04-04 08:15:18.852 23731-26202/com.yc.leakdemo E/ThreadActivity: Hi--5 ...
例如,能夠爲 Thread 設置一個布爾變量 threadSwitch 來控制線程的啓動與中止
public class ThreadActivity extends AppCompatActivity { private final String TAG = "ThreadActivity"; private int threadIndex; private boolean threadSwitch = true; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_thread); threadIndex++; new Thread(new Runnable() { @Override public void run() { int j = threadIndex; while (threadSwitch) { Log.e(TAG, "Hi--" + j); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); } @Override protected void onDestroy() { super.onDestroy(); threadSwitch = false; } }
若是想保持Thread繼續運行,能夠按如下步驟來:
public class ThreadActivity extends AppCompatActivity { private static final String TAG = "ThreadActivity"; private static int threadIndex; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_thread); threadIndex++; new MyThread(this).start(); } private static class MyThread extends Thread { private WeakReference<ThreadActivity> activityWeakReference; MyThread(ThreadActivity threadActivity) { activityWeakReference = new WeakReference<>(threadActivity); } @Override public void run() { if (activityWeakReference == null) { return; } if (activityWeakReference.get() != null) { int i = threadIndex; while (true) { Log.e(TAG, "Hi--" + i); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } } } } }
問題代碼
使用場景
DoShareUtil.showFullScreenShareView(PNewsContentActivity.this, title, title, shareurl, logo);
查看報錯
解決辦法
知識延伸
非靜態內部類,靜態實例化 public class MyActivity extends AppCompatActivity { //靜態成員變量 public static InnerClass innerClass = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_my); innerClass = new InnerClass(); } class InnerClass { public void doSomeThing() {} } } 這裏內部類InnerClass隱式的持有外部類MyActivity的引用,而在MyActivity的onCreate方法中調用了。 這樣innerClass就會在MyActivity建立的時候是有了他的引用,而innerClass是靜態類型的不會被垃圾回收, MyActivity在執行onDestory方法的時候因爲被innerClass持有了引用而沒法被回收,因此這樣MyActivity就老是被innerClass持有而沒法回收形成內存泄露。 靜態變量引用不當會致使內存泄漏 靜態變量Activity和View會致使內存泄漏,在下面這段代碼中對Activity的Context和TextView設置爲靜態對象,從而產生內存泄漏。 public class MainActivity extends AppCompatActivity { private static Context context; private static TextView textView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); context = this; textView = new TextView(this); } }
問題代碼
public class MainActivity extends AppCompatActivity { private AsyncTask<Void, Void, Integer> asyncTask; private TextView mTextView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mTextView = (TextView) findViewById(R.id.text); testAsyncTask(); finish(); } private void testAsyncTask() { asyncTask = new AsyncTask<Void, Void, Integer>() { @Override protected Integer doInBackground(Void... params) { int i = 0; //模擬耗時操做 while (!isCancelled()) { i++; if (i > 1000000000) { break; } Log.e("LeakCanary", "asyncTask---->" + i); } return i; } @Override protected void onPostExecute(Integer integer) { super.onPostExecute(integer); mTextView.setText(String.valueOf(integer)); } }; asyncTask.execute(); } }
形成內存泄漏緣由分析
查看報錯結果以下:
解決辦法
private void destroyAsyncTask() { if (asyncTask != null && !asyncTask.isCancelled()) { asyncTask.cancel(true); } asyncTask = null; } @Override protected void onDestroy() { super.onDestroy(); destroyAsyncTask(); }
問題代碼
private static TestResource mResource = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); if(mResource == null){ mResource = new TestResource(); } } class TestResource { //裏面代碼引用上下文,Activity.this會致使內存泄漏 }
解決辦法
分析問題
問題代碼
//add監聽,放到集合裏面 tv.getViewTreeObserver().addOnWindowFocusChangeListener(new ViewTreeObserver.OnWindowFocusChangeListener() { @Override public void onWindowFocusChanged(boolean b) { //監聽view的加載,view加載出來的時候,計算他的寬高等。 } });
解決辦法
//計算完後,必定要移除這個監聽 tv.getViewTreeObserver().removeOnWindowFocusChangeListener(this);
注意事項:
tv.setOnClickListener();//監聽執行完回收對象,不用考慮內存泄漏 tv.getViewTreeObserver().addOnWindowFocusChangeListene,add監聽,放到集合裏面,須要考慮內存泄漏
在註冊觀察則模式的時候,若是不及時取消也會形成內存泄露。好比使用Retrofit+RxJava註冊網絡請求的觀察者回調,一樣做爲匿名內部類持有外部引用,因此須要記得在不用或者銷燬的時候取消註冊。
public class MeAboutActivity extends BaseActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); this.registerReceiver(mReceiver, new IntentFilter()); } private BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { // 接收到廣播須要作的邏輯 } }; @Override protected void onDestroy() { super.onDestroy(); this.unregisterReceiver(mReceiver); } }
先來看看形成內存泄漏的代碼
/** * 吐司工具類 避免點擊屢次致使吐司屢次,最後致使Toast就長時間關閉不掉了 * @param context 注意:這裏若是傳入context會報內存泄漏;傳遞activity..getApplicationContext() * @param content 吐司內容 */ private static Toast toast; @SuppressLint("ShowToast") public static void showToast(Context context, String content) { if (toast == null) { toast = Toast.makeText(context , content, Toast.LENGTH_SHORT); } else { toast.setText(content); } toast.show(); }
解決辦法
問題代碼
public class LeakActivity extends AppCompatActivity { private TextView textView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_leak); textView = (TextView)findViewById(R.id.text_view); ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(textView,"rotation",0,360); objectAnimator.setRepeatCount(ValueAnimator.INFINITE); objectAnimator.start(); } }
解決辦法
@Override protected void onDestroy() { super.onDestroy(); mAnimator.cancel(); }