Handler 系列一:如何使用

  • Handler 是什麼?
  • 爲何須要 Handler?
  • Handler 如何使用?
  • handler 存在的問題及改進?

Handler:是一個消息分發對象,進行發送和處理消息,而且其 Runnable 對象與一個線程的 MessageQueue 關聯。
做用:調度消息,將一個任務切換到某個指定的線程中去執行。git

爲何須要 Handler?

子線程不容許訪問 UI

倘若子線程容許訪問 UI,則在多線程併發訪問狀況下,會使得 UI 控件處於不可預期的狀態。
傳統解決辦法:加鎖,但會使得UI訪問邏輯變的複雜,其次下降 UI 訪問的效率。github

引入 Handler

採用單線程模型處理 UI 操做,經過 Handler 切換到 UI 線程,解決子線程中沒法訪問 UI 的問題。網絡

Handler 使用

方式一: post(Runnable)

  • 建立一個工做線程,實現 Runnable 接口,實現 run 方法,處理耗時操做
  • 建立一個 handler,經過 handler.post/postDelay,投遞建立的 Runnable,在 run 方法中進行更新 UI 操做。多線程

    new Thread(new Runnable() {
    @Override
    public void run() {併發

    /**
         耗時操做
       */
     handler.post(new Runnable() {
         @Override
         public void run() {
             /**
               更新UI
              */
         }
     });

    }
    }).start();ide

方式二: sendMessage(Message)

  • 建立一個工做線程,繼承 Thread,從新 run 方法,處理耗時操做
  • 建立一個 Message 對象,設置 what 標誌及數據
  • 經過 sendMessage 進行投遞消息
  • 建立一個handler,重寫 handleMessage 方法,根據 msg.what 信息判斷,接收對應的信息,再在這裏更新 UI。post

    private Handler handler = new Handler(){this

    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        switch (msg.what) {      //判斷標誌位
            case 1:
                /**
                  獲取數據,更新UI
                 */
                break;
        }
     }

    };.net

    public class WorkThread extends Thread {線程

    @Override

    public void run() {
       super.run();
    /**
      耗時操做
     */

    Message msg =Message.obtain(); //從全局池中返回一個message實例,避免屢次建立message(如new Message)
    msg.obj = data;
    msg.what=1; //標誌消息的標誌
    handler.sendMessage(msg);
    }

    new WorkThread().start();

Handler 存在的問題

內存方面

Handler 被做爲 Activity 引用,若是爲非靜態內部類,則會引用外部類對象。當 Activity finish 時,Handler可能並未執行完,從而引發 Activity 的內存泄漏。故而在全部調用 Handler 的地方,都用靜態內部類。

異常方面

當 Activity finish 時,在 onDestroy 方法中釋放了一些資源。此時 Handler 執行到 handlerMessage 方法,但相關資源已經被釋放,從而引發空指針的異常
避免

  • 若是是使用 handlerMessage,則在方法中加try catch。
  • 若是是用 post 方法,則在Runnable方法中加try catch。

Handler 的改進

  • 內存方面:使用靜態內部類建立 handler 對象,且對 Activity 持有弱引用
  • 異常方面:不加 try catch,而是在 onDestory 中把消息隊列 MessageQueue 中的消息給 remove 掉。

則使用以下方式建立 handler 對象:

/**
     爲避免handler形成的內存泄漏
     一、使用靜態的handler,對外部類不保持對象的引用
     二、但Handler須要與Activity通訊,因此須要增長一個對Activity的弱引用
    */
      private static class MyHandler extends Handler {
        private final WeakReference<Activity> mActivityReference;    

        MyHandler(Activity activity) {
            this.mActivityReference = new WeakReference<Activity>(activity);
        }

        @Override
            public void handleMessage(Message msg) {
            super.handleMessage(msg);
            MainActivity activity = (MainActivity) mActivityReference.get();  //獲取弱引用隊列中的activity
            switch (msg.what) {    //獲取消息,更新UI
                case 1:
                    byte[] data = (byte[]) msg.obj;
                    activity.threadIv.setImageBitmap(activity.getBitmap(data));
                    break;
            }
        }
    }

並在 onDesotry 中銷燬:

@Override
protected void onDestroy() {
    super.onDestroy();
    //避免activity銷燬時,messageQueue中的消息未處理完;故此時應把對應的message給清除出隊列
    handler.removeCallbacks(postRunnable);   //清除runnable對應的message
    //handler.removeMessage(what)  清除what對應的message
}

Handler 的使用實現

  • 耗時操做採用從網絡加載一張圖片
  • 繼承 Thread 或實現 Runnable 接口的線程,與 UI 線程進行分離,其中 Runnable 與主線程經過回調接口進行通訊,下降耦合,提升代碼複用性。

在 Activity 中建立 handler 對象,調用工做線程執行

public class MainActivity extends AppCompatActivity {

ImageView threadIv;
ImageView runnableIv;
SendThread sendThread;
PostRunnable postRunnable;
private final MyHandler handler = new MyHandler(this);

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    threadIv = (ImageView) findViewById(R.id.thread_iv);
    runnableIv = (ImageView) findViewById(R.id.runnable_iv);

    sendThread = new SendThread(handler);
    sendThread.start();

    postRunnable = new PostRunnable(handler);
    postRunnable.setRefreshUI(new PostRunnable.RefreshUI() {
        @Override
        public void setImage(byte[] data) {
            runnableIv.setImageBitmap(getBitmap(data));
        }
    });
    new Thread(postRunnable).start();
}

/**
  爲避免handler形成的內存泄漏
  一、使用靜態的handler,對外部類不保持對象的引用
  二、但Handler須要與Activity通訊,因此須要增長一個對Activity的弱引用
 /
private static class MyHandler extends Handler {
    private final WeakReference<Activity> mActivityReference;

    MyHandler(Activity activity) {
        this.mActivityReference = new WeakReference<Activity>(activity);
    }

    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        MainActivity activity = (MainActivity) mActivityReference.get();  //獲取弱引用隊列中的activity
        switch (msg.what) {    //獲取消息,更新UI
            case 1:
                byte[] data = (byte[]) msg.obj;
                activity.threadIv.setImageBitmap(activity.getBitmap(data));
                break;
        }
    }
}

private Bitmap getBitmap(byte[] data) {
    return BitmapFactory.decodeByteArray(data, 0, data.length);
}

@Override
protected void onDestroy() {
    super.onDestroy();
    //避免activity銷燬時,messageQueue中的消息未處理完;故此時應把對應的message給清除出隊列
    handler.removeCallbacks(postRunnable);   //清除runnable對應的message
    //handler.removeMessage(what)  清除what對應的message
}
}

方式一:實現 runnable 接口,經過 post(Runnable)通訊,並經過給定的回調接口通知 Activity 更新

public class PostRunnable implements Runnable {

private Handler handler;
private RefreshUI refreshUI;
byte[] data = null;

public PostRunnable(Handler handler) {
    this.handler = handler;
}

@Override
public void run() {
    /**
      耗時操做
     */
    final Bitmap bitmap = null;
    HttpClient httpClient = new DefaultHttpClient();
    HttpGet httpGet = new HttpGet("http://i3.17173cdn.com/2fhnvk/YWxqaGBf/cms3/FNsPLfbkmwgBgpl.jpg");
    HttpResponse httpResponse = null;
    try {
        httpResponse = httpClient.execute(httpGet);
        if (httpResponse.getStatusLine().getStatusCode() == 200) {
            data = EntityUtils.toByteArray(httpResponse.getEntity());
        }
    } catch (IOException e) {
        e.printStackTrace();
    }

    //返回結果給UI線程
    handler.post(new Runnable() {
        @Override
        public void run() {
            refreshUI.setImage(data);
        }
    });
}

public interface RefreshUI {
    public void setImage(byte[] data);
}

public void setRefreshUI(RefreshUI refreshUI) {
    this.refreshUI = refreshUI;
}
}

方式二:繼承Thread,經過handler的sendMessage通訊

public class SendThread extends Thread {

private Handler handler;

public SendThread(Handler handler) {
    this.handler = handler;
}

@Override
public void run() {
    super.run();
    /**
      耗時操做
     */
    byte[]data=null;
    HttpClient httpClient = new DefaultHttpClient();
    HttpGet httpGet = new HttpGet("https://d36lyudx79hk0a.cloudfront.net/p0/descr/pc27/3095587d8c4560d8.png");
    HttpResponse httpResponse = null;
    try {
        httpResponse = httpClient.execute(httpGet);
        if(httpResponse.getStatusLine().getStatusCode()==200){
            data= EntityUtils.toByteArray(httpResponse.getEntity());
        }
    } catch (IOException e) {
        e.printStackTrace();
    }

    //返回結果給UI線程
    doTask(data);
}

/**
  經過handler返回消息
  @param data
 */
private void doTask(byte[] data) {
    Message msg =Message.obtain();  //從全局池中返回一個message實例,避免屢次建立message(如new Message)
    msg.obj = data;
    msg.what=1;   //標誌消息的標誌
    handler.sendMessage(msg);
}
}

本文發表於我的博客:http://lavnfan.github.io/,歡迎指教。

相關文章
相關標籤/搜索