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