網站首頁 編程語言 正文
1.消息機制原理的解釋
在主線程里創建一個Handler,然后在分線程中引用這個Handler來發送Message對象給MessageQueue,循環器Looper從MessageQueue里面取出一個需要處理的Message,交給Handler處理,一般是進行UI處理。處理完之后,Message就沒有太大的用處,Looper清理Message,讓Message回到默認狀態。
2.Android的消息機制概述
Handler的背景(三個常見問題)
(1)Android為什么要提供Handler?
這是因為Android規定訪問UI只能在主線程中進行,如果在子線程中訪問UI,那么程序就會拋出異常,但是Android又建議不要在主線程中進行耗時操作,否則會導致線程無法響應即ANR,比如我們需要從服務器拉取一些信息并將其顯示在UI上,這個時候必須在子線程中進行拉取工作,拉取完畢后,不能在子線程上直接訪問UI,這時候通過Handler就可以將訪問UI的操作切換到主線程去執行。
(2)系統為什么不允許在子線程中訪問UI呢?
這個因為Android的UI控件并不是線程安全的,如果在多線程中并發訪問可能會導致UI控件處于不可預期的狀態。
(3)為什么系統不對UI控件的訪問加上鎖機制呢?
缺點有兩個:
- 加上鎖機制會讓UI訪問的邏輯變得復雜
- 鎖機制會降低UI訪問的效率,因為鎖機制會阻塞某些線程的執行
鑒于這兩個缺點,最簡單且高效的方法就是采用單線程模型來處理UI操作。
Handler的工作原理的解釋
子線程默認沒有Looper的,如果需要使用Handler,那么在Handler創建時就需要為線程創建Looper,利用Looper來構建內部的消息循環系統,如果當前線程沒有Looper,那么就會報錯。Handler創建完畢之后,Handler通過post方法把一個Runnable傳到Looper中去處理,或者通過send方法發送消息,Looper會調用MessageQueue的enqueueMessage方法將消息發入消息隊列中,然后Looper不斷循環發現需要處理的消息之后,就會調用消息中的Runnable或者或者Handler的handleMessage方法,這樣一來,Handler中的業務邏輯就被切除到創建Handler所在的線程中去執行。
3.消息機制的分析
1.了解Message
可理解為線程間通訊的數據單元,可通過message攜帶需要的數據
? ?創建Message的方法
val message:Message= Message()
val message1:Message=Message.obtain()//它利用了Message中消息池(sPool)
? ?封裝數據:
int what:標識
int arg1:保存int數據
int arg2:保存int數據
Object obj:保存任意時刻的數據
Long when :記錄應該被處理的時間值,若為即時消息,時間值=發送時間,若為延時消息,時間值=發送時間+延遲時間
Handler target:用來處理消息的Handler對象,就是發送消息的Handler
Runnable callback:用來處理消息的回調器
Message next:指向下一個Message用來形成一個鏈表
Message sPool:用來緩存處理過的Message,以便復用
2.了解Handler
Handler是Message的處理器,同時也負責消息的發送和移除的工作
(1)Handler的構造方法
Android API 30以上,使用Handler()方法時會顯示刪除線,并提示相關的方法已經被棄用,不建議使用。如圖所示:
但是安卓不是棄用Handler這個類,而只是棄用Handler的兩個構造方法:
Handler()
Handler(callback:Handler.Callback)
?安卓建議采用如下方法來解決
1.使用Executor
2.明確指定Looper
3.使用Looper.getMainLooper()定位并使用主線程的Looper
4.如果又想在其他線程,又想要不出bug,請使用Handler(looper:Looper)或者Handler(Looper.myLooper())這兩個構造方法。
(2)?Handler導致的內存泄漏問題
在Android中最常用的一種內存泄漏是Handler導致的泄漏,原因:
(1)在Activity被摧毀時,延遲消息還沒發出,Handler可能有未執行完或者正在執行的Message,MessagesQueue就會持有這個消息的引用,導致Handler持有Activity的引用,進而導致GC無法回收Activity。
(2)Handler中有還沒執行完的Message,還在運行,而運行中的子線程不會被回收,所以就導致了內存泄漏。
解決方法:
1.在Activity的onDestroy()方法中,清空Handler中的未執行或者正在執行的Message和Callbacks
override fun onDestroy() {
super.onDestroy()
handler.removeCallbacksAndMessages(null)
}
2.static+弱引用
class MyHandler(activity: HandlerActivity):Handler(Looper.getMainLooper()){
private val myWeakReference:WeakReference<HandlerActivity> = WeakReference(activity)
override fun handleMessage(msg: Message) {//處理消息的回調方法
myWeakReference.get()?.run {
...
}
}
}
(3)Handler的常用方法:
- 發送即時消息:sendMessage(msg? Message)
- 發送延時消息:sendMessageDelayed(msg Message,delayMillis?Long)
- 處理方法:handleMessage(msg Message)(回調方法)
- 移除還未處理的消息:removeMessages(what int)
部分源碼:
由下面的源碼可以看出,這些方法的本質是調用了queue.enqueueMessage(msg,uptimeMillis)方法。
sendMessage(Message msg)
->sendMessageDelayed(msg,0)
sendEmptyMessage(int what)
->sendEmptyMessageDelayed(what,0)
sendEmptyMessageDelayed(what,0)//發送不帶數據的消息
->sendMessageDelayed(msg,delayMillis)
sendMessageDelayed(Message msg,long delayMillis)
->sendMessageAtTime(msg,SystemClock.uptimeMillis()+delayMillis)
sendMessageAtTime(Message msg,long uptimeMillis)
->enaueueMessage(queue,msg,uptimeMillis)//將消息添加到消息隊列中
enaueueMessage(MessageQueue queue,Message msg,long uptimeMillis)
->queue.enqueueMessage(msg,uptimeMillis)//調用消息隊列保存消息對象
removeMessage(int what)//移除消息
->mQueue.removeMessage(this,what,null)//調用消息隊列移除它內部的指定what消息
handleMessage(Message msg)//處理消息的回調方法
3.消息隊列的工作原理
MessageQueue消息隊列,負責入隊和出隊,儲存Handler發送的消息,它是一個按Message的when的排序的優先隊列。
雖然MessageQueue叫消息隊列,但是它內部是用鏈表來實現的。
MessageQueue主要包含兩個操作:插入和讀取,讀取操作本身會伴隨刪除操作,插入和讀取對應的方法分別為enqueueMessage和next:
boolean enqueueMessage(Message msg, long when):往消息隊列中插入一條消息
Message next() :消息隊列中取出一條消息并將其從消息隊列中移除
enqueueMessage和?next方法的部分源碼:
enqueueMessage的主要操作就是單鏈表的插入操作
boolean enqueueMessage(Message msg, long when) {//將Messages插入消息隊列
...
msg.when = when;//指定消息應該被處理的時間
...
for (;;) {//將當前消息對象保存到消息隊列中的一個合適的位置
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}//最終的結果是:消息隊列是按when來排序的
...
nativeWake(mPtr);//通過本地方法實現對處理等待狀態的底層線程
...
}
?next方法是一個無限循環的方法。如果消息隊列中沒有消息,那么next方法可以阻塞在這里,
當有新的消息到來時,next方法會返回這條消息并將其中單鏈表中移除。
Message next() {//取出一個合適的Message對象,可能不會立刻返回
...
nativePollOnce(ptr, nextPollTimeoutMillis);//本地方法,會導致可能處理等待狀態,但不會阻塞主線程
...
Message msg = mMessages;//取出消息隊列中的第一個消息
...
return msg;//返回
...
}
4.Looper的工作原理
Looper在Android的消息機制中扮演著消息循環的角色。
Looper為一個線程開啟一個消息循環,創建MessageQueue,負責循環取出Message Queue里面的當前需要處理的Message,也就是說,它會一直不停地從MessageQueue中查看是否會有新消息,如果有新消息就會交給對應的Handler進行處理,處理完后,將Message緩存到消息池中以備復用,否則就一直阻塞在那里,Looper退出后,Handler發送消息會失敗,線程會立刻終止。
常用方法:?
Looper.prepare():為當前線程創建一個Looper。
Looper.loop():開啟消息循環.
Looper.getMainLooper():獲取主線程的Looper。
Looper.quit()直接推遲Looper
Looper.quitSafely()設定一個退出標記,然后把消息隊列中的已有消息處理完畢才安全地退出。
如何為一個線程創建Looper?
thread {
Looper.prepare()
val handler:Handler=MyHandler(this)
Looper.loop()
}
?loop方法的部分源碼:
public static void loop() {
final Looper me = myLooper();//得到looper對象
...
for (;;) {//無限循環
...
Message msg = me.mQueue.next(); // 從消息隊列中取出消息
...
msg.target.dispatchMessage(msg);//調用Handler去分發并處理消息
...
msg.recycleUnchecked();//回收利用Message
...
}
}
4.Handler使用(DEMO)
功能描述:
1.初始時
顯示10,可以通過點擊按鈕改變其值
2.點擊自動增加
每隔一秒上面的文本數值增加1,但最大顯示20并作出提示
3.點擊自動減少
每隔一秒上面的文本數值減少1,但是最小顯示1并作出提示
4.點擊暫停
上面的數值文本不再變化
效果圖:
代碼如下:
class HandlerActivity : AppCompatActivity() {
lateinit var handler: Handler
lateinit var number:TextView
lateinit var increase:Button
lateinit var decrease:Button
lateinit var pause:Button
//static+弱引用
class MyHandler(activity: HandlerActivity):Handler(Looper.getMainLooper()){
private val myWeakReference:WeakReference<HandlerActivity> = WeakReference(activity)
override fun handleMessage(msg: Message) {//處理消息的回調方法
myWeakReference.get()?.run {
var numberint:Int=Integer.parseInt(number.text.toString())
when(msg.what){
1->{
if(numberint==20){
Toast.makeText(this,"已經達到最大值",Toast.LENGTH_SHORT).show()
return
}
numberint++
number.text= numberint.toString()
handler.sendEmptyMessageDelayed(1,1000)
}
2->{
if(numberint==1){
Toast.makeText(this,"已經達到最小值",Toast.LENGTH_SHORT).show()
return
}
numberint--
number.text=numberint.toString()
handler.sendEmptyMessageDelayed(2,1000)
}
else -> {}
}
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_handler)
number=findViewById(R.id.number)
increase=findViewById(R.id.increase)
decrease=findViewById(R.id.decrease)
pause=findViewById(R.id.pause)
handler=MyHandler(this)
increase.setOnClickListener {
increase.isEnabled=false
decrease.isEnabled=true
pause.isEnabled=true
handler.removeMessages(2)
handler.sendEmptyMessage(1)
}
decrease.setOnClickListener {
increase.isEnabled=true
decrease.isEnabled=false
pause.isEnabled=true
handler.removeMessages(1)
handler.sendEmptyMessage(2)
}
pause.setOnClickListener {
increase.isEnabled=true
decrease.isEnabled=true
pause.isEnabled=false
handler.removeMessages(1)
handler.removeMessages(2)
}
}
override fun onDestroy() {
super.onDestroy()
handler.removeCallbacksAndMessages(null)
}
}
原文鏈接:https://blog.csdn.net/weixin_63357306/article/details/128174477
相關推薦
- 2022-03-09 C++之const限定符詳解_C 語言
- 2023-06-21 Android崩潰日志收集和保存解析_Android
- 2022-07-18 SpringMVC文件上傳功能實現
- 2022-05-29 C#實現文本轉語音功能_C#教程
- 2022-11-25 CentOS?7.9?升級內核?kernel-ml-5.6.14版本的方法_云其它
- 2022-07-22 Python函數默認參數避坑指南
- 2022-03-27 Android?Studio實現井字游戲_Android
- 2022-08-13 Redis - String內存開銷問題以及基本/擴展數據類型的使用
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支