項目地址:github.com/AcgnCodeMon…java
提及handler,作安卓開發的同窗應該都很熟悉。handler在咱們平常開發中,主要用於子線中刷新ui,由於一般狀況下,安卓系統是不容許咱們在子線程中直接操做ui的,這點,你們在纔開始學習安卓的時候,想必也必定聽老師講過,甚至有時候一不注意,可能也會在子線程中刷新ui,以致於程序崩潰。至於爲何不能在子線程刷新ui以及handler的通訊原理,這裏不是本文的重點,有興趣的同窗,能夠本身查閱相關資料。git
咱們先來看看傳統的handler的用法,首先是這種:github
private Handler mHandler = new Handler() {
@Override
public void handleMessage (Message msg) {
super.handleMessage(msg);
Log.e("Handler", "Message");
tv.setText("handler改變了控件文本");
iv.setImageBitmap(mBitmap);
}
};
new Thread(new Runnable() {
@Override
public void run () {
try {
Thread.sleep(TimeConfig.SLEEP_TIME);
} catch (InterruptedException e) {
e.printStackTrace();
}
mHandler.sendEmptyMessage(0);
}
}).start();
複製代碼
這種寫法,才學習安卓的同窗們必定常常用到,簡單粗暴。就是直接採用內部類對象的方式實例化一個handler,而後在子線程中經過這個handler發送對應消息進行ui操做。固然,稍有經驗的同窗都知道這種寫法會形成內存泄漏,由於java中內部類(包括匿名內部類)對象會隱式的持有外部對象的引用,這樣,若是耗時操做未完成以前咱們退出了當前activity,就會形成activity由於被handler引用而沒法回收。異步
固然,稍有經驗的同窗們必定看到過別人說過必定要使用靜態內部類寫法,例如:ide
private MyHandler mHandler2;
@Override
protected void onCreate (@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler);
tv = findViewById(R.id.tv);
iv = findViewById(R.id.iv);
mHandler2 = new MyHandler(this);
new Thread(new Runnable() {
@Override
public void run () {
try {
Thread.sleep(TimeConfig.SLEEP_TIME);
} catch (InterruptedException e) {
e.printStackTrace();
}
mHandler2.sendEmptyMessage(0);
}
}).start();
}
private static class MyHandler extends Handler {
private WeakReference<HandlerActivity> mReference;
public MyHandler (HandlerActivity activity) {
mReference = new WeakReference<>(activity);
}
@Override
public void handleMessage (Message msg) {
super.handleMessage(msg);
Log.e("Handler2", "Message");
if (mReference.get() == null) {
return;
}
mReference.get().tv.setText("handler2改變了控件文本");
mReference.get().iv.setImageBitmap(mReference.get().mBitmap);
}
}
複製代碼
那麼這兩種寫法到底效果如何呢?讓咱們來運行一下,並用AS自帶的內存分析,來看下結果。假設activity中有個耗時30秒的異步任務,任務完成後經過handler發送消息進行ui刷新,爲了讓內存變化更明顯,咱們在activity中聲明並實例化一個加載了大圖的bitmap,咱們反覆開啓退出這個界面三次。首先看第一種寫法的內存圖: 學習
能夠看到,隨着咱們反覆開啓關閉這個activity,應用的內存佔用呈階梯式上漲,咱們完成操做後開始不停的用AS的GC進行內存回收,發如今一段時間內內存變化幾乎爲0,在過了一段時間後GC有了效果,內存佔用一樣開始呈階梯式降低,最後迴歸未開啓界面的水平。中間內存沒法回收很好理解,由於activity發生了泄漏,bitmap沒法被虛擬機回收(bitmap沒有作recycle操做),可是爲何後面內存又能成功回收了呢?按照以前的想法,內存應該是一直沒法回收的吧?其實否則,經過上面的代碼咱們能夠看出,咱們的子線程作了一個耗時操做(30秒),在這30秒內,咱們確實是隱式持有這activity,形成其沒法回收,可是,任務執行完畢後,咱們用handler發送完消息,執行完畢後,咱們就已經再也不持有activity了,這時泄漏的activity也就能夠順利回收了,經過下面的時間軸能夠證實咱們的觀點,第一次能夠回收的點,基本上就是第一次打開activity並開啓任務後過了30秒,後面同理,因此纔會出現內存階梯式降低。測試
也就是說,傳統寫法,確實會形成在子線程執行期間activity沒法被回收,那麼下面讓我來看看使用靜態內部類+弱引用的方法,會不會有效呢?ui
驚呆了有木有?童話裏都是騙人的,說好的用靜態內部類不會內存泄漏呢?上圖和一圖一模一樣,並無什麼太大的區別,難道是網上其餘大佬在胡說?this
並非,只是咱們的代碼有問題而已,的確,咱們的handler確實使用了靜態內部類+弱引用,可是咱們忽略了一個問題,上面說過,匿名內部類也會隱式持有外部對象的。而此次咱們的問題就出在thread上,是的,咱們採用了匿名內部類的方式聲明瞭一個子線程。而就是這個線程形成了,activity一直被子線程持有沒法回收,直到子線程執行完畢。spa
意思是說,咱們用子線程也得像handler那樣用靜態內部類+弱引用?感受整我的都很差了,有木有,靜態內部類太麻煩,弱引用用起來更是麻煩,那麼有沒有什麼解決方法呢?
既然子線程和handler都容易形成activity泄漏,沒法被回收,咱們爲何不本身封裝一下,在適當的時候切斷子線程,handler和activity的關係呢?
通過不斷嘗試,首先使用一個自定義類Emitter來持有handler,同時聲明一個自定義類TaskCallable(實現自Callable接口)來實現子線程功能,而後建立一個自定義類Task來協調和調度Emitter及TaskCallable,以此來完成子線程和handler的交互。同時,引入線程池機制,改善處處new thread的low比作法,建立自定義類RxLifeList用於綁定activity的生命週期,這樣就能夠在activity的onDestroy方法被調用時及時的切斷對activity的引用。
說了那麼多,想法很美好,實際效果若是呢?先上圖再說:
這裏的邏輯和上面兩個例子是同樣的,不一樣之處在於使用了本身封裝的類進行的操做,能夠看到,退出界面後GC幾乎就能當即順利的回收掉activitiy了(不要問我爲何仍是呈階梯式降低,我也沒想通,QAQ),代碼以下:
public class TestRunnable extends TaskCallable {
@Override
public boolean run (Emitter emitter) throws Exception {
Thread.sleep(TimeConfig.SLEEP_TIME);
Log.e("TestRunnable","執行完畢!");
return true;
}
}
private RxLifeList mBindLife;
@Override
protected void onCreate (Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mBindLife = new RxLifeList();
iv = findViewById(R.id.iv);
}
@Override
protected void onDestroy () {
if (mBindLife != null) {
mBindLife.onDestroy();
}
super.onDestroy();
}
//子線程任務部分
RxExecutor.getInstance()
.executeTask(new TestRunnable(),
new Task(true) {
@Override
public void bindLife (RxLife rxLife) {
super.bindLife(rxLife);
mBindLife.add(rxLife);
}
@Override
public void onError (Exception e) {
super.onError(e);
toast(e.getMessage() + "\n解綁handler,再也不回調!");
}
@Override
public void onFinished () {
super.onFinished();
tv.setText("handler改變了控件文本");
iv.setImageBitmap(mBitmap);
toast("綁定生命週期的任務執行完畢!");
}
});
複製代碼
不要吐槽代碼風格,和命名。沒錯,我就是不要臉的抄襲rxJava的寫法,-_-
目前,RxTask這個項目還處於測試階段,不過,本人會在近期開源到Jcenter,歡迎不怕踩坑的同窗踊躍嘗試。