上一節(jié)我們介紹了非靜態(tài)內(nèi)部類作為靜態(tài)變量造成的內(nèi)存泄漏情況盹舞,這一節(jié)我們介紹一下Handler的使用造成的內(nèi)存泄漏情況
知識點(diǎn)
非靜態(tài)內(nèi)部類和匿名類內(nèi)部類的實(shí)例都會潛在持有它們所屬的外部類的強(qiáng)引用儡遮,但是靜態(tài)內(nèi)部類卻不會
使用匿名內(nèi)部類
我們來看一段代碼:
public class HandlerAct extends AppCompatActivity {
private TextView textView;
private Handler handler = new Handler();
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.act_handler);
textView = (TextView) findViewById(R.id.tv_value);
handler.postDelayed(new Runnable() {
@Override
public void run() {
textView.setText("Finished");
}
}, 5000000L);
}
}
這個Activity中new Runnable()是一個匿名內(nèi)部類富雅,這個內(nèi)部類持有外部類Activity的強(qiáng)引用,內(nèi)部類被封裝成消息Message被傳遞到Handler的消息隊列MessageQueue中灵再,即消息持有Activity的強(qiáng)引用边翁。在Message消息沒有被Handler處理之前,Activity實(shí)例不會被銷毀了橄妆,于是導(dǎo)致內(nèi)存泄漏衙伶。發(fā)送postDelayed這樣的消息,你輸入延遲多少秒害碾,它就會泄露至少多少秒矢劲。而發(fā)送沒有延遲的消息的話,當(dāng)隊列中的消息過多時慌随,也會照成一個臨時的泄露芬沉。可參考Android內(nèi)存泄漏之匿名內(nèi)部類
使用靜態(tài)內(nèi)部類
public class HandlerAct extends AppCompatActivity {
private TextView textView;
private Handler handler = new Handler();
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.act_handler);
textView = (TextView) findViewById(R.id.tv_value);
handler.postDelayed(new MyRunnable(textView), 5000000L);
}
private static final class MyRunnable implements Runnable {
private final TextView mTextView;
protected MyRunnable(TextView textView) {
mTextView = textView;
}
@Override
public void run() {
mTextView.setText("Finished");
}
}
}
這段代碼中阁猜,我們使用的是靜態(tài)內(nèi)部類丸逸,那么這樣還會內(nèi)存泄漏嗎?
答案:會
上面知識點(diǎn)中我們提到了靜態(tài)內(nèi)部類不會持有外部類的引用剃袍,那么為什么這里還會內(nèi)存泄漏呢黄刚。
因?yàn)門extView持有Activity的強(qiáng)引用,我們都知道View都持有Context的引用民效,這里的Context就是Activity憔维。new MyRunnable(textView)持有TextView的強(qiáng)引用涛救,這樣MyRunnable也就持有Activity的強(qiáng)引用了,所以消息為處理之前埋同,Activity實(shí)例不會被銷毀州叠,于是導(dǎo)致內(nèi)存泄漏。
解決方案1:弱引用+靜態(tài)內(nèi)部類
匿名內(nèi)部類因?yàn)槌钟蠥ctivity的強(qiáng)引用凶赁,所以會導(dǎo)致內(nèi)存泄漏咧栗。
靜態(tài)內(nèi)部類中的TextView持有Activity的強(qiáng)引用,所以也會導(dǎo)致內(nèi)存泄漏
在Android內(nèi)存泄漏和引用的關(guān)系中虱肄,我們有講到弱引用:如果一個對象具有弱引用致板,在GC線程掃描內(nèi)存區(qū)域的過程中,不管當(dāng)前內(nèi)存空間足夠與否咏窿,都會回收內(nèi)存斟或。
代碼如下:
public class HandlerAct extends AppCompatActivity {
private TextView textView;
private Handler handler = new Handler();
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.act_handler);
textView = (TextView) findViewById(R.id.tv_value);
handler.postDelayed(new MyRunnable(textView), 5000000L);
}
private static final class MyRunnable implements Runnable {
private final WeakReference<TextView> wr;
protected MyRunnable(TextView textView) {
wr = new WeakReference<TextView>(textView);
}
@Override
public void run() {
final TextView tv = wr.get();
if (tv != null) {
tv.setText("Finished");
}
}
}
}
這里我們把靜態(tài)內(nèi)部類的TextView改成弱引用了,這樣雖然textView持有activity的強(qiáng)引用集嵌,但是new MyRunnable(textView)持有的是TextView的弱引用萝挤,這樣MyRunnable持有Activity的引用也是弱引用,所以內(nèi)存回收的時候根欧,是可以回收的怜珍,我們可以通過wr.get()是否為空判斷是否已經(jīng)回收。
解決方案2:在onDestory的時候凤粗,手動清除Message
public class HandlerAct extends AppCompatActivity {
private TextView textView;
private Handler handler = new Handler();
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.act_handler);
textView = (TextView) findViewById(R.id.tv_value);
handler.postDelayed(new Runnable() {
@Override
public void run() {
textView.setText("Finished");
}
}, 5000000L);
}
@Override
protected void onDestroy() {
handler.removeCallbacksAndMessages(null);
super.onDestroy();
}
}
如上方法酥泛,我們在ondestory的時候,清除所有未處理的Message嫌拣,就不會有哪個消息持有Activity的強(qiáng)引用了柔袁,這樣也不會導(dǎo)致內(nèi)存泄漏。
解決方案3:使用第三方控件WeakHandler
WeakHandler是一個第三方庫异逐,我們看看他是怎么使用的:
public class HandlerAct extends AppCompatActivity {
private TextView textView;
private WeakHandler handler = new WeakHandler();
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.act_handler);
textView = (TextView) findViewById(R.id.tv_value);
handler.postDelayed(new Runnable() {
@Override
public void run() {
textView.setText("Finished");
}
}, 5000000L);
}
}
它用起來很簡單捶索,不需要考慮弱應(yīng)用的情況,你只需要把以前的Handler替換成WeakHandler就行了应役。
我們看看WeakHandler的源代碼:
private final WeakHandler.ExecHandler mExec;
public final boolean postDelayed(Runnable r, long delayMillis) {
return this.mExec.postDelayed(this.wrapRunnable(r), delayMillis);
}
private WeakHandler.WeakRunnable wrapRunnable(@NonNull Runnable r) {
if(r == null) {
throw new NullPointerException("Runnable can\'t be null");
} else {
WeakHandler.ChainedRef hardRef = new WeakHandler.ChainedRef(this.mLock, r);
this.mRunnables.insertAfter(hardRef);
return hardRef.wrapper;
}
}
static class WeakRunnable implements Runnable {
private final WeakReference<Runnable> mDelegate;
private final WeakReference<WeakHandler.ChainedRef> mReference;
WeakRunnable(WeakReference<Runnable> delegate, WeakReference<WeakHandler.ChainedRef> reference) {
this.mDelegate = delegate;
this.mReference = reference;
}
public void run() {
Runnable delegate = (Runnable)this.mDelegate.get();
WeakHandler.ChainedRef reference = (WeakHandler.ChainedRef)this.mReference.get();
if(reference != null) {
reference.remove();
}
if(delegate != null) {
delegate.run();
}
}
}
private static class ExecHandler extends Handler {
private final WeakReference<Callback> mCallback;
ExecHandler() {
this.mCallback = null;
}
ExecHandler(WeakReference<Callback> callback) {
this.mCallback = callback;
}
ExecHandler(Looper looper) {
super(looper);
this.mCallback = null;
}
ExecHandler(Looper looper, WeakReference<Callback> callback) {
super(looper);
this.mCallback = callback;
}
public void handleMessage(@NonNull Message msg) {
if(this.mCallback != null) {
Callback callback = (Callback)this.mCallback.get();
if(callback != null) {
callback.handleMessage(msg);
}
}
}
}
源代碼中可以清楚的看到它將Handler和Runnable做了弱引用封裝情组,而ExecHandler和WeakRunnable也就是封裝之后的內(nèi)部類。