Android《多執行緒-中》

weixin_34006468發表於2017-10-27

上篇中我們講解了執行緒,程式以及Thread和Runnable之間的區別,那麼這一篇我們來講解下Android應用的訊息處理機制,之後才能夠更深刻的瞭解為什麼多執行緒能夠解決UI縣城阻塞的問題。

Android 的訊息處理機制

說到Android訊息處理機制有的人或許有些概念模糊,那麼Handler、Looper、MessageQueue,大家應該比較面熟吧。

  • UI執行緒
    我們知道在Android應用啟動時,會預設啟動一個(主)UI執行緒,這個執行緒會關聯一個訊息佇列所有的操作都會被封裝成訊息交給主執行緒來處理。為了保證主執行緒不會主動退出,就要將抓取訊息的操作放在一個死迴圈中,這樣我們的程式就不會主動退出並保持執行狀態。
    那麼和Handler 、 Looper 、Message有啥關係?其實Looper負責的就是建立一個MessageQueue,然後進入一個無限迴圈體不斷從該MessageQueue中讀取訊息,而訊息的建立者就是一個或多個Handler 。

上面雖然是原理但是並不是多好理解,我們呢打個比方:
男主:BOY
女主:Girl
當Boy和Girl 結婚後(APP啟動了),那麼Boy就要開始幹活了(APP開啟UI執行緒),這時候Boy就要進入瘋狂工作模式了 (無限迴圈-Looper),而我們的Girl(Handler的一個例項)會把各種型別的賬單記下來(將Message寫入MessageQueue),但凡Boy接收到(Looper獲取)賬單(Message)就進行處理,如果是月初Girl沒有給你霍霍,那麼Boy就會自己攢錢了(阻塞Looper)等到月底賬單來臨再去處理。

舉例講完後我們來看看Handler、Looper、MessageQueue的概念。

  • Handler:
    簡單說Handler用於同一個程式的執行緒間通訊,另外一個作用,就是能統一處理訊息的回撥。這樣一個Handler發出訊息又確保訊息處理也是自己來做,這樣的設計非常的贊。
  • Looper:
    無限迴圈不退出的執行緒,Looper的另外一部分工作就是在迴圈程式碼中會不斷從訊息佇列挨個拿出訊息給主執行緒處理。
  • MessageQueue
    MessageQueue 存在的原因很簡單,就是同一執行緒在同一時間只能處理一個訊息,同一執行緒程式碼執行是不具有併發性,所以需要佇列來儲存訊息和安排每個訊息的處理順序。

這裡就不帶大家去看原始碼了。
下面我峨嵋你通過一個例子來看下訊息處理機制是怎麼解決網路載入時阻塞UI執行緒問題的。

案例中我們讓下載類 sleep 7秒,並且下載前和下載後都要對UI中的Text進行修改。

MainActivity.class

/**
 * Created by 泅渡者
 * Created on 2017/10/27.
 */

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";

    private final Handler mHandler = new MyHandler(this);

    public static TextView tv_download;
    private Message message;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        tv_download = findViewById(R.id.tv_download);

        tv_download.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Download download = new Download();
                Thread thread = new Thread(download,"下載");
                thread.start();
            }
        });
    }


    private static class MyHandler extends Handler {

        private final WeakReference<Activity> mActivity;

        public MyHandler(Activity activity) {
            mActivity = new WeakReference<Activity>(activity);


        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what){
                case 1:
                    if (mActivity.get() == null) {
                        return;
                    }
                    tv_download.setText("下載中。。。");
                    break;
                case 2:
                    if (mActivity.get() == null) {
                        return;
                    }
                    tv_download.setText("下載完成");
                    break;
                    default:
                        return;
            }

        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();

        mHandler.removeCallbacksAndMessages(null);

    }

    class Download implements Runnable{

        @Override
        public void run() {

            try {
                message= mHandler.obtainMessage();
                message.what = 1;
                mHandler.sendMessage(message);

                Thread.sleep(7000);

                message= mHandler.obtainMessage();
                message.what = 2;
                mHandler.sendMessage(message);

            } catch (InterruptedException e) {

                Log.e(TAG,e.toString());

            }
        }
    }
}

佈局檔案

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.bsoft.multithread.MainActivity">

    <TextView
        android:id="@+id/tv_download"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="下載"
       />

</RelativeLayout>

執行效果如下:

4213628-438c150dfe20b5b7.gif
2.gif

我們看下面程式碼:

                message= mHandler.obtainMessage();
                message.what = 1;
                mHandler.sendMessage(message);

                Thread.sleep(7000);

                message= mHandler.obtainMessage();
                message.what = 2;
                mHandler.sendMessage(message);

這裡我們在子執行緒不能操作UI執行緒,這個大家都知道,我們說一下obtainMessage()。

  1. obtainmessage()是從訊息池中拿來一個msg 不需要另開闢空間。
  2. new Message()需要重新申請,效率低。
  3. obtianmessage可以迴圈利用。

這裡還有一個比較重要的話題,就是由Handler導致Activity 的記憶體洩露。

Handler記憶體洩漏解決辦法

Handler也是造成記憶體洩露的一個重要的源頭,主要Handler屬於TLS(Thread Local Storage)變數,生命週期和Activity是不一致的,Handler引用Activity會存在記憶體洩露。
我們一般用法是否是這樣的呢?

  private final Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what){
                case 1:
                    //TODO
                    break;
                default:
                    return;
            }

        }
    };
------------------------------------------------------------------------------
        Message message = mHandler.obtainMessage();
        message.what = 1;
        mHandler.sendMessageDelayed(message,6000);

但是程式會提示

This Handler class should be static or leaks might occur (anonymous android.os.Handler)
意思:此處理程式類應該是靜態的或可能發生洩漏 (匿名 android.os.Handler)

是什麼導致的呢 ?

  • 生命週期
    Handler 的生命週期與Activity 不一致,當Android應用啟動的時候,會先建立一個UI主執行緒的Looper物件,Looper實現了一個簡單的訊息佇列,一個一個的處理裡面的Message物件。主執行緒Looper物件在整個應用生命週期中存在,當在主執行緒中初始化Handler時,該Handler和Looper的訊息佇列關聯(沒有關聯會報錯的)。傳送到訊息佇列的Message會引用傳送該訊息的Handler物件,這樣系統可以呼叫 Handler#handleMessage(Message) 來分發處理該訊息。

  • handler 引用 Activity 阻止了GC對Acivity的回收
    在Java中,非靜態(匿名)內部類會預設隱性引用外部類物件。而靜態內部類不會引用外部類物件。如果外部類是Activity,則會引起Activity洩露 ,當Activity finish後,延時訊息會繼續存在主執行緒訊息佇列中1分鐘,然後處理訊息。而該訊息引用了Activity的Handler物件,然後這個Handler又引用了這個Activity。這些引用物件會保持到該訊息被處理完,這樣就導致該Activity物件無法被回收,從而導致了上面說的 Activity洩露。

  • 如何避免?
    使用顯形的引用:1.靜態內部類 2. 外部類
    使用弱引用 : WeakReference
    還要在程式銷燬時進行remove();

@Override  
public void onDestroy() {  
    mHandler.removeCallbacksAndMessages(null);  
}  

上述案例就是應用弱引用,可能大家覺得寫的程式碼比較多,不怕我們有辦法。我們按照圖的指示來建立自己的Live Templates:

4213628-3b9250efe84eb830.gif
2.gif

OK 今天的就到這裡。

相關文章