標籤:
有一篇關於android線程講的非常好,大家可以參考下,其中有一句話講的非常好,就拿來做開篇之句:
當一個程式第一次啟動時,Android會同時啟動一個對應的主線程(Main Thread),主線程主要負責處理與UI相關的事件,如使用者的按鍵事件,使用者接觸螢幕的事件以及螢幕繪圖事件,並把相關的事件分發到對應的組件進行處理,所以主線程通常又被叫做UI線程。在開發Android應用時必須遵守單執行緒模式的原則: Android UI操作並不是安全執行緒的並且這些操作必須在UI線程中執行。
咱新手在第一次接觸android線程的背景是這樣的:
1:需求中要進行一次http互動
步驟大概是這樣
1.1:點擊提交按鈕
1.2:http同步擷取結果
1.3:將結果寫入到TextView上。
這樣寫,執行時會報錯,不能在UI線程上發起同步的http網路請求。(耗時可能會導致ANR,application not respond)
2:OK,既然不能在UI線程上發起http請求,那咱新開一個子線程。
於是步驟變成了這樣:
1.1:點擊提交按鈕
1.2:開啟一個子線程
1.3:http同步擷取結果
1.4:將結果寫入到TextView上。
一執行,BOOM,報錯,大概意思是子線程裡不能直接操作UI元素,為什麼呢?請再看看本文開頭的句子(安全執行緒)。
3:好吧,那就在子線程裡通過Handler來操作UI的元素吧
最後,測試通過的代碼大概是這樣
1.1:點擊提交按鈕
1.2:開啟一個子線程
1.3:http同步擷取結果
1.4:將結果作為Message,傳遞給Handler
1.5:在Handler裡將結果寫入到TextView上。
tips:如果你用android studio 的code inspect功能,就會發現,它提示你,這樣的做法可能會導致記憶體泄露,為什麼呢?因為Handler裡持有了UI裡面的元素的引用,當UI結束掉自己時(此時handler還在耐心等待http訪問結果),發現某個元素被Hnadler持有,就不能被GC回收了,這就會造成記憶體泄露。解決辦法很簡單,Handler改為static,消除內部匿名引用,同時,將對象的引用改為WeakReference<>即可。一篇詳細解釋原因的文章,具體代碼參考如下,來源於咱的通訊錄APP
static class ImageDoneHandler extends Handler { WeakReference<ImageView> imageView; WeakReference<Bitmap> bitmap; WeakReference<String> url; ZImage.CacheType cacheType; ImageDoneHandler(Looper looper, ImageView _imageView, Bitmap _bitmap, String url, ZImage.CacheType cacheType) { super(looper); imageView = new WeakReference<>(_imageView); bitmap = new WeakReference<>(_bitmap); this.url = new WeakReference<>(url); this.cacheType = cacheType; } @Override public void handleMessage(Message msg) { if (msg.what != MSG_IMAGE_LOAD_DONE) return; ImageView _imageView = imageView.get(); Bitmap _bitmap = bitmap.get(); String _url = url.get(); if (_imageView == null || _bitmap == null) return; if (_url.equals(_imageView.getTag().toString())) { _imageView.setImageBitmap(_bitmap); if (cacheType == ZImage.CacheType.DiskMemory) ZImage.getInstance().putToMemoryCache(_url, _bitmap); } } }
寫到現在,咱還是不懂,為啥Handler裡面就可以改UI裡面的元素呢?
這時候就需要理解android非同步訊息處理的四大部分了( Message、 Handler、 MessageQueue 和Looper)。
咱才疏學淺,因此下面的知識來源於《第一行代碼》書籍的節選片段,非常的精彩,值得反覆閱讀,大家深呼吸下,系好安全帶,開始咯~
先來一張非同步訊息處理的整個流程圖解,大家對照著圖解看更直觀(圖片來源《第一行代碼》)
1. Message
Message 是線上程之間傳遞的訊息,它可以在內部攜帶少量的資訊,用於在不同線
程之間交換資料。上一小節中我們使用到了 Message 的 what 欄位,除此之外還可以使
用 arg1 和 arg2 欄位來攜帶一些整型資料,使用 obj 欄位攜帶一個 Object 對象。
2. Handler
Handler 顧名思義也就是處理者的意思,它主要是用於發送和處理訊息的。發送消
息一般是使用 Handler 的 sendMessage()方法,而發出的訊息經過一系列地輾轉處理後,
最終會傳遞到 Handler 的 handleMessage()方法中。
3. MessageQueue
MessageQueue 是訊息佇列的意思,它主要用於存放所有通過 Handler 發送的訊息。
這部分訊息會一直存在於訊息佇列中,等待被處理。每個線程中只會有一個 MessageQueue
對象。
4. Looper
Looper 是每個線程中的 MessageQueue 的管家,調用 Looper 的 loop()方法後,就會
進入到一個無限迴圈當中,然後每當發現 MessageQueue 中存在一條訊息,就會將它取
出,並傳遞到 Handler 的 handleMessage()方法中。每個線程中也只會有一個 Looper 對象。
瞭解了 Message、 Handler、 MessageQueue 以及 Looper 的基本概念後我們再來對非同步訊息處理的整個流程梳理一遍。
1:首先需要在主線程當中建立一個 Handler 對象,並重寫handleMessage()方法。
2:然後當子線程中需要進行 UI 操作時,就建立一個 Message 對象,並通過 Handler 將這條訊息發送出去。
3:之後這條訊息會被添加到 MessageQueue 的隊列中等待被處理,
4:而 Looper 則會一直嘗試從 MessageQueue 中取出待處理訊息,最後分發回 Handler的 handleMessage()方法中。
5:由於 Handler 是在主線程中建立的,所以此時 handleMessage()方法中的代碼也會在主線程中運行,於是我們在這裡就可以安心地進行 UI 操作了。
AsyncTask非同步任務類
幸運的是,在一些大部分的場合,android為我們提供了一個AsyncTask非同步任務抽象類別,通過實現他可以非常方便的執行各種耗時操作,而不必擔心UI線程被卡住,同時也避免了原生非同步線程與UI線程互動繁瑣的寫法。
在繼承時,我們可以指定三個泛型參數類型(都是參考型別哦,實值型別的記得也要改成參考型別,比如int ->Integer),它們分別是:
1:參數Param ,傳遞給子線程執行的
2:進度提示Progress,如果需要即時在介面更新非同步處理進度,就可以通過這個參數反饋
3:結果Result,在主線程裡,我們就擷取到了非同步執行的結果。
來個例子吧
/** * http請求使用者是否存在,穿入http的url地址,返回布爾類型是否存在 */ class QueryUserExistTask extends AsyncTask<String,Void,Boolean> { /** * 在非同步請求處理之前 */ @Override protected void onPreExecute() { super.onPreExecute(); } /** * 非同步處理,這裡同樣不能互動UI元素哦 * @param params * @return */ @Override protected Boolean doInBackground(String... params) { return null; } /** * 非同步處理完了,切回到主線程,返回處理結果 * @param aBoolean */ @Override protected void onPostExecute(Boolean aBoolean) { super.onPostExecute(aBoolean); } }
調用例子:
new QueryUserExistTask().execute("http://192.168.1.1/u/kimmy");
總結:
1:保持主線程流暢度很重要,耗費大量資源的工作盡量放到子線程完成。
2:大部分情況下AsyncTask都能勝任非同步重任。
3:高並發的非同步任務、或者非同步任務之間彼此需要調度的情況,需要自己編寫線程池來處理
android線程學習心得