Handler雜談【二】Handler的用法及解析

原文地址:https://www.jianshu.com/p/7cdf2c2ed354
https://blog.csdn.net/qq_37321098/article/details/81535449#2.常用api

一、handler作用:

  • 傳遞消息Message
//2種創建消息方法
//1.通過handler實例獲取
Handler handler = new Handler();
Message message=handler.obtainMessage();
 
//2.通過Message獲取
Message message=Message.obtain();
 
 
//源碼中第一種獲取方式其實也是內部調用了第二種:
public final Message obtainMessage(){
    return Message.obtain(this);
}

不建議直接new Message,Message內部保存了一個緩存的消息池,我們可以用obtain從緩存池獲得一個消息,Message使用完後系統會調用recycle回收,如果自己new很多Message,每次使用完後系統放入緩存池,會佔用很多內存的。

//傳遞的數據
Bundle bundle = new Bundle();
bundle.putString("msg", "傳遞我這個消息");
//發送數據
Message message = Message.obtain();
message.setData(bundle);   //message.obj=bundle  傳值也行
message.what = 0x11;
handler.sendMessage(message);
 
 
 
//數據的接收
final Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if (msg.what == 0x11) {
                Bundle bundle = msg.getData();
                String date = bundle.getString("msg");
            }
        }
};

  • 子線程通知主線程更新ui
        //創建handler
        final Handler handler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                if (msg.what == 0x11) {
                    //更新ui
                          ......
                }
            }
        };
 
        new Thread(new Runnable() {
            @Override
            public void run() {
                //FIXME 這裏直接更新ui是不行的
                //還有其他更新ui方式,runOnUiThread()等          
                message.what = 0x11;     
                handler.sendMessage(message);  
            }
        }).start();

二、常用api

    //消息
    Message message = Message.obtain();
    //發送消息
        new Handler().sendMessage(message);
    //延時1s發送消息
        new Handler().sendMessageDelayed(message, 1000);
    //發送帶標記的消息(內部創建了message,並設置msg.what = 0x1)
        new Handler().sendEmptyMessage(0x1);
    //延時1s發送帶標記的消息
        new Handler().sendEmptyMessageDelayed(0x1, 1000);
    //延時1秒發送消息(第二個參數爲:相對系統開機時間的絕對時間,而SystemClock.uptimeMillis()是當前開機時間)
        new Handler().sendMessageAtTime(message, SystemClock.uptimeMillis() + 1000);
 
    //避免內存泄露的方法:
    //移除標記爲0x1的消息
        new Handler().removeMessages(0x1);
    //移除回調的消息
        new Handler().removeCallbacks(Runnable);
    //移除回調和所有message
        new Handler().removeCallbacksAndMessages(null);

三、handler使用避免內存泄露

  • handler怎麼使用會產生內存泄露?
public class MainActivity extends AppCompatActivity {

   final Handler handler = new Handler() {
       @Override
       public void handleMessage(Message msg) {
           super.handleMessage(msg);
               ......
       }
   };

   @Override
   protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_main);
       //activity被執行時,被延遲的這個消息存於主線程消息隊列中1分鐘,
       //此消息包含handler引用,而handler由匿名內部類創建,持有activity引用,
       //activity便不能正常銷燬,從而泄露
       handler.postDelayed(new Runnable() {
           @Override
           public void run() {
               ......
           }
       }, 1000 * 60);
   }

頁面初始化,開啓了一個延時的handler事件,最後頁面關閉的時候,沒有及時釋放Handler,會導致該handler一直持有當前Activity的引用,導致內存泄漏

  • 如何避免handler的內存泄露?
public class MainActivity extends AppCompatActivity {
 
    //創建靜態內部類
    private static class MyHandler extends Handler{
        //持有弱引用MainActivity,GC回收時會被回收掉.
        private final WeakReference<MainActivity> mAct;
        public MyHandler(MainActivity mainActivity){
            mAct =new WeakReference<MainActivity>(mainActivity);
        }
        @Override
        public void handleMessage(Message msg) {
            MainActivity mainAct=mAct.get();
            super.handleMessage(msg);
            if(mainAct!=null){
                //執行業務邏輯
            }
        }
    }
    private static final Runnable myRunnable = new Runnable() {
        @Override
        public void run() {
            //執行我們的業務邏輯
        }
    };
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        MyHandler myHandler=new MyHandler(this);
        //延遲5分鐘後發送
        myHandler.postDelayed(myRunnable, 1000 * 60 * 5);
    }
}

雷區

  • 使用Handler.post(Runnable)的注意事項

Handler.post(Runnable)其實就是生成一個what爲0的Message,調用

myHandler.removeMessages(0);

會使runnable任務從消息隊列中清除。

詳細解釋可以參考這:Android Handler使用Message的一個注意事項

  • 子線程直接創建Handler發生異常

子線程直接創建Handler,系統會拋出異常

Can't create handler inside thread that has not called Looper.prepare()

原因是非主線程沒有loop對象,所以要調用Looper.prepare()方法,而且如果主線程給子線程發送消息,還要調用一個Looper.loop()的方法(此方法保證消息隊列中的消息被不停的拿出,並被處理)

class MyThread extends Thread{
        @Override
        public void run() {
            super.run();
            Looper.prepare();
            Handler handler = new Handler() {
                @Override
                public void handleMessage(Message msg) {
                    super.handleMessage(msg);
                    //處理消息
                }
            };
            Looper.loop();
        }
}
  • 空指針異常

activity如被finish,但是handler剛好還在處理消息,如果需要用的資源已被釋放,則會出現空指針異常。

所以在onDestory中去remove掉我們要處理的事件,還是有必要的。不想處理就直接try catch或者判空。

  • removeCallbacks失效

有時候你會發現removeCallbacks會失效,不能從消息隊列中移除。
出現這情況是activity切入後臺,再回到前臺,此時的runnable由於被重定義,就會和原先的runnable並非同一個對象。所以這麼做,加上static即可

static Handler handler = new Handler();
static Runnable myRunnable = new Runnable() {
        @Override
        public void run() {
            //執行我們的業務邏輯
        }
    };

這樣,因爲靜態變量在內存中只有一個拷貝,保證runnable始終是同一個對象。

四、handlerThread

  • handlerThread是什麼?

異步存在形式有thread,handlerThead,asyncTask,線程池,intentService,handlerThread繼承thread,不過內部比普通線程多了一個Looper。

//內部Looper.prepare()
@Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
}

  • HandlerThread使用及銷燬
public class MainActivity extends AppCompatActivity {
 
    private HandlerThread thread;
    static Handler mHandler;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //創建一個HandlerThread並啓動它
        thread = new HandlerThread("MyHandlerThread");
        thread.start();
 
        //使用HandlerThread的looper對象創建Handler
        mHandler = new Handler(thread.getLooper(), new Handler.Callback() {
            @Override
            public boolean handleMessage(Message msg) {
                //這個方法是運行在 handler-thread 線程中的,可以執行耗時操作,因此不能更新ui,要注意
                if (msg.what == 0x1) {
                    try {
                        Thread.sleep(3000);
                        Log.e("測試: ", "執行了3s的耗時操作");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //這個方法是運行在 handler-thread 線程中的,可以執行耗時操作,因此不能更新ui,要注意
//                    ((Button) MainActivity.this.findViewById(R.id.button)).setText("hello");
                }
                return false;
            }
        });
 
        //停止handlerthread接收事件
        findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                thread.quit();
            }
        });
 
        //運行
        mHandler.sendEmptyMessage(0x1);
    }
 
}

上面demo中,只要調用了

mHandler.sendEmptyMessage(0x1);

就會開始執行任務

幾個地方要注意:

  1. handleMessage()可以做耗時操作,但是不能更新ui
  2. 如果不手動的調用HandlerThread.quit()或者HandlerThread…quitSafely()方法,HandlerThread會將持續的接收新的任務事件。
  3. 只有handleMessage()方法執行完,這輪的任務纔算完成,HandlerThread纔會去執行下一個任務。而且在此次執行時,即使手動的去調用quit()方法,HandlerThread的此次任務也不會停止。但是,會停止下輪任務的接收。
舉例:
 
//耗時任務換成這個,點擊按鈕執行quit()方法,發現此次任務依舊執行
for (int i = 0; i < 99999999; i++) {
    Log.e("測試: ", "輸出" +i);
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章