㈠ vertx獲取所有的handler
Vert.x - 學習記錄 - 只有一二三的博客 - CSDN博客
2020年12月22日真正調用處理邏輯的入口往往是處理器(Handler),Vert.x保證同一個普通Verticle(也就是EventLoop Vert...
CSDN編程社區
Vert.x - SpringBoot 整合 vertx - 小畢超的博客 - CSDN博客 - vertx
1. vertx 前面的文章講解了 vertx 的簡介及 vertx-web 的路由,看過的小夥伴應該對 vertx 有了一定的了解,從前面的演示來看,都
2. SpringBoot 整合 vertx 首先新建一個 SpringBoot 項目,注意不要引入其他 web 框架。在 pom 中引入依賴,注意這里我使用的是 vertx 4.1.
CSDN編程社區
二. Vert.x - Web開發之路由 - 知乎
第一種就是get請求直接拼接在URL後的參數,比如:http://localhost:8080/method?param=hello router.route(HttpMethod.GET, "/method").handler(request
第二種是獲取路徑的參數,比如:http://localhost:8080/method/xiaoming/xm123 // 獲取參數 router.route(HttpMethod.GET, "/method/:user/:
第三種是獲取到請求體中的數據,也就是post提交的數據。這個稍微有一些繁瑣,首先要指定一個BodyHandler,然後才能通過requestContext對象來獲取body體中的數據。獲取body
知乎
Vert.x學習筆記(一) Vert.x 核心包 - 蒲公英雲
6月11日創建Vertx對象 要使用Vertx的第一步就是創建Vertx對象,所有API都要通過這個對象來調用。一般情況下,一個程序只需要一個Vertx對象即可,不過有...
蒲公英雲
vert.x學習(七),使用表單獲取用戶提交的數據 | 表單
router.post("/user")的作用是當用戶在表單頁面點擊保存按鈕後,觸發post請求,vert.x會將這個請求路由到router.post("/user").handler裡面,在...
www.lmlphp.com
深入淺出Vert.x 第一季 第6集 Vert.x獲取請求參數——body參數
231次播放
07:25
合集
深入淺出Vert.x 第一季 vertx快速入門 java高性能非同步非阻塞框架
聽歌不循環
2020年10月21日
Vertx-web第三課 bodyhandler
38:49
合集
Vertx4.*
嗶哩嗶哩
2021年07月10日
...框架的使用體驗(一) - 程小明的coding的博客 - CSDN博客 - vertx 並發
6月21日Vert.x的Handler內部是atomic/原子操作,Verticle內部是thread safe/線程安全的,Verticle之間傳遞的數據是immutable/不可改變的。
CSDN編程社區
源碼分析 - Vertx入門和源碼淺析 - 個人文章 - SegmentFault 思否
1. Vertx 實現類VertxImpl,它的重要線程 eventLoopGroup acceptorEventLoopGroup
2. Verticle 1) Verticle起作用的方式,主要是通過Vertx進行部署,也就是deployVerticle 2) Verticle部署,主要是Vertx框架把vertx 和context 傳給Verticle
3. Context上下文 1) 子類:ContextInternal EventLoopContext 2) 上下文是通過Vertx創建,如ContextInternal callingContext = vertx.getOrCreateContext(); 3
思否開發者社區
vertx web記錄route 每個handler的執行時長 - 蒲公英雲
1. 配置route,將route模塊載入到vertx中,vertx通過調用listen方法綁定監聽
2. 請求到來之後,調用 listenContext.runOnContext將請求交給route模塊處理
3. 模塊handler會創建VertxThread,並開啟一個由vertx管理的線程進行處理
蒲公英雲
06-架構師-SpringMVC如何獲取handler源碼分析
22:16
合集
架構師專場——springmvc
㈡ 關於 Handler 的這 20 個問題,你都清楚嗎
Android 11 開始,AsyncTask 正式謝幕,變成了不推薦使用的 API。官方建議採用 Kotlin 協程替代,或者自行實現。
事實上,無論是 AsyncTask 還是協程,背後都有 Handler 的功勞。無論從普及原理的角度、還是從自行實現的角度,我們都需要吃透這個 Android 系統所特有的線程間通信方式Handler 機制!
初嘗 Handler 機制的時候,原以為 Handler 類發揮了很大的作用。當你深入了解它的原理之後,會發現 Handler 只是該機制的 調用入口和回調 而已,最重要的東西是 Looper 和 MessagQueue,以及不斷流轉的 Message。
本次針對 Handler 機制常被提及和容易困擾的 20 個問題進行整理和回答,供大家解惑和回顧~
簡述下 Handler 機制的總體原理?
Looper 存在哪?如何可以保證線程獨有?
如何理解 ThreadLocal 的作用?
主線程 Main Looper 和一般 Looper 的異同?
Handler 或者說 Looper 如何切換線程?
Looper 的 loop() 死循環為什麼不卡死?
Looper 的等待是如何能夠准確喚醒的?
Message 如何獲取?為什麼這么設計?
MessageQueue 如何管理 Message?
理解 Message 和 MessageQueue 的異同?
Message 的執行時刻如何管理?
Handler、Mesage 和 Runnable 的關系如何理解?
IdleHandler 空閑 Message 了解過嗎?有什麼用?
非同步 Message 或同步屏障了解過嗎?怎麼用?什麼原理?
Looper 和 MessageQueue、Message 及 Handler 的關系?
Native 側的 NativeMessageQueue 和 Looper 的作用是?
Native 側如何使用 Looper?
Handler 為什麼可能導致內存泄露?如何避免?
Handler 在系統當中的應用
Android 為什麼不允許並發訪問 UI?
1. 簡述下 Handler 機制的總體原理?
Looper 准備和開啟輪循:
尚無 Message 的話,調用 Native 側的 pollOnce() 進入 無限等待
存在 Message,但執行時間 when 尚未滿足的話,調用 pollOnce() 時傳入剩餘時長參數進入 有限等待
Looper#prepare() 初始化線程獨有的 Looper 以及 MessageQueue
Looper#loop() 開啟 死循環 讀取 MessageQueue 中下一個滿足執行時間的 Message
Message 發送、入隊和出隊:
Native 側如果處於無限等待的話:任意線程向 Handler 發送 Message 或 Runnable 後,Message 將按照 when 條件的先後,被插入 Handler 持有的 Looper 實例所對應的 MessageQueue 中 適當的位置 。MessageQueue 發現有合適的 Message 插入後將調用 Native 側的 wake() 喚醒無限等待的線程。這將促使 MessageQueue 的讀取繼續 進入下一次循環 ,此刻 Queue 中已有滿足條件的 Message 則出隊返回給 Looper
Native 側如果處於有限等待的話:在等待指定時長後 epoll_wait 將返回。線程繼續讀取 MessageQueue,此刻因為時長條件將滿足將其出隊
Looper 處理 Message 的實現:
Looper 得到 Message 後回調 Message 的 callback 屬性即 Runnable,或依據 target 屬性即 Handler,去執行 Handler 的回調。
存在 mCallback 屬性的話回調 Handler$Callback
反之,回調 handleMessage()
2. Looper 存在哪?如何可以保證線程獨有?
Looper 實例被管理在靜態屬性 sThreadLocal 中
ThreadLocal 內部通過 ThreadLocalMap 持有 Looper,key 為 ThreadLocal 實例本身,value 即為 Looper 實例
每個 Thread 都有一個自己的 ThreadLocalMap,這樣可以保證每個線程對應一個獨立的 Looper 實例,進而保證 myLooper() 可以獲得線程獨有的 Looper
彩蛋:一個 App 擁有幾個 Looper 實例?幾個 ThreadLocal 實例?幾個 MessageQueue 實例?幾個 Message 實例?幾個 Handler 實例
一個線程只有一個 Looper 實例
一個 Looper 實例只對應著一個 MessageQueue 實例
一個 MessageQueue 實例可對應多個 Message 實例,其從 Message 靜態池裡獲取,存在 50 的上限
一個線程可以擁有多個 Handler 實例,其Handler 只是發送和執行任務邏輯的入口和出口
ThreadLocal 實例是靜態的,整個進程 共用一個實例 。每個 Looper 存放的 ThreadLocalMap 均弱引用它作為 key
3. 如何理解 ThreadLocal 的作用?
首先要明確並非不是用來切換線程的, 只是為了讓每個線程方便程獲取自己的 Looper 實例 ,見 Looper#myLooper()
後續可供 Handler 初始化時 指定其所屬的 Looper 線程
也可用來線程判斷自己是否 是主線程
4. 主線程 Main Looper 和一般 Looper 的異同?
區別:
Main Looper 不可 quit
主線程需要不斷讀取系統消息和用書輸入,是進程的入口,只可被系統直接終止。進而其 Looper 在創建的時候設置了 不可quit的標志 ,而 其他線程的 Looper 則可以也必須手動 quit
Main Looper 實例還被 靜態緩存
為了便於每個線程獲得主線程 Looper 實例,見 Looper#getMainLooper(),Main Looper 實例還作為 sMainLooper 屬性緩存到了 Looper 類中。
相同點:
都是通過 Looper#prepare() 間接調用 Looper 構造函數創建的實例
都被靜態實例 ThreadLocal 管理,方便每個線程獲取自己的 Looper 實例
彩蛋:主線程為什麼不用初始化 Looper?
App 的入口並非 MainActivity,也不是 Application,而是 ActivityThread。
其為了 Application、ContentProvider、Activity 等組件的運行,必須事先啟動不停接受輸入的 Looper 機制,所以在 main() 執行的最後將調用 prepareMainLooper() 創建 Looper 並調用 loop() 輪循。
不需要我們調用,也不可能有我們調用。
可以說如果主線程沒有創建 Looper 的話,我們的組件也不可能運行得到!
5. Handler 或者說 Looper 如何切換線程?
Handler 創建的時候指定了其所屬線程的 Looper,進而持有了 Looper 獨有的 MessageQueue
Looper#loop() 會持續讀取 MessageQueue 中合適的 Message,沒有 Message 的時候進入等待
當向 Handler 發送 Message 或 Runnable 後,會向持有的 MessageQueue 中插入 Message
Message 抵達並滿足條件後會喚醒 MessageQueue 所屬的線程,並將 Message 返回給 Looper
Looper 接著回調 Message 所指向的 Handler Callback 或 Runnable,達到線程切換的目的
簡言之,向 Handler 發送 Message 其實是向 Handler 所屬線程的獨有 MessageQueue 插入 Message。而線程獨有的 Looper 又會持續讀取該 MessageQueue。所以向其他線程的 Handler 發送完 Message,該線程的 Looper 將自動響應。
6. Looper 的 loop() 死循環為什麼不卡死?
為了讓主線程持續處理用戶的輸入,loop() 是 死循環 ,持續調用 MessageQueue#next() 讀取合適的 Message。
但當沒有 Message 的時候,會調用 pollOnce() 並通過 Linux 的 epoll 機制進入等待並釋放資源。同時 eventFd 會監聽 Message 抵達的寫入事件並進行喚醒。
這樣可以 空閑時釋放資源、不卡死線程,同時能持續接收輸入的目的 。
彩蛋1:loop() 後的處理為什麼不可執行
因為 loop() 是死循環,直到 quit 前後面的處理無法得到執行,所以避免將處理放在 loop() 的後面。
彩蛋2:Looper 等待的時候線程到底是什麼狀態?
調用 Linux 的 epoll 機制進入 等待 ,事實上 Java 側列印該線程的狀態,你會發現線程處於 Runnable 狀態,只不過 CPU 資源被暫時釋放。
7. Looper 的等待是如何能夠准確喚醒的?
讀取合適 Message 的 MessageQueue#next() 會因為 Message 尚無或執行條件尚未滿足進行兩種等的等待:
無限等待
尚無 Message(隊列中沒有 Message 或建立了同步屏障但尚無非同步 Message)的時候,調用 Natvie 側的 pollOnce() 會傳入參數 -1 。
Linux 執行 epoll_wait() 將進入無限等待,其等待合適的 Message 插入後調用 Native 側的 wake() 向喚醒 fd 寫入事件觸發喚醒 MessageQueue 讀取的下一次循環
有限等待
有限等待的場合將下一個 Message 剩餘時長作為參數 交給 epoll_wait(),epoll 將等待一段時間之後 自動返回 ,接著回到 MessageQueue 讀取的下一次循環
8. Message 如何獲取?為什麼這么設計?
享元設計模式:通過 Message 的靜態方法 obatin() 獲取,因為該方法不是無腦地 new,而是 從單鏈表池子里獲取實例 ,並在 recycle() 後將其放回池子
好處在於復用 Message 實例,滿足頻繁使用 Message 的場景,更加高效
當然,緩存池存在上限 50 ,因為沒必要無限制地緩存,這本身也是一種浪費
需要留意緩存池是靜態的,也就是整個進程共用一個緩存池
9. MessageQueue 如何管理 Message?
MessageQueue 通過單鏈表管理 Message,不同於進程共用的 Message Pool,其是線程獨有的
通過 Message 的執行時刻 when 對 Message 進行排隊和出隊
MessageQueue 除了管理 Message,還要管理空閑 Handler 和 同步屏障
10. 理解 Message 和 MessageQueue 的異同?
相同點:都是通過 單鏈表來管理 Message 實例;
Message 通過 obtain() 和 recycle() 向單鏈表獲取插入節點
MessageQueue 通過 enqueueMessage() 和 next() 向單鏈表獲取和插入節點
區別:
Message 單鏈表是 靜態的,供進程使用的緩存池
MessageQueue 單鏈表 非靜態,只供 Looper 線程使用
11. Message 的執行時刻如何管理?
發送的 Message 都是按照執行時刻 when 屬性的先後管理在 MessageQueue 里
延時 Message 的 when 等於調用的當前時刻和 delay 之和
非延時 Message 的 when 等於當前時刻(delay 為 0)
插隊 Message 的 when 固定為 0,便於插入隊列的 head
之後 MessageQueue 會根據 讀取的時刻和 when 進行比較
將 when 已抵達的出隊,
尚未抵達的計算出 當前時刻和目標 when 的插值 ,交由 Native 等待對應的時長,時間到了自動喚醒繼續進行 Message 的讀取
事實上,無論上述哪種 Message 都不能保證在其對應的 when 時刻執行,往往都會延遲一些!因為必須等當前執行的 Message 處理完了才有機會讀取隊列的下一個 Message。
比如發送了非延時 Message,when 即為發送的時刻,可它們不會立即執行。都要等主線程現有的任務(Message)走完才能有機會出隊,而當這些任務執行完 when 的時刻已經過了。假使隊列的前面還有其他 Message 的話,延遲會更加明顯!
彩蛋:. onCreate() 里向 Handler 發送大量 Message 會導致主線程卡頓嗎?
不會,發送的大量 Message 並非立即執行,只是先放到隊列當中而已。
onCreate() 以及之後同步調用的 onStart() 和 onResume() 處理,本質上也是 Message。等這個 Message 執行完之後,才會進行讀取 Message 的下一次循環,這時候才能回調 onCreate 里發送的 Message。
需要說明的是,如果發送的是 FrontOfQueue 將 Message 插入隊首也不會立即先執行,因為 onStart 和 onResume 是 onCreate 之後同步調用的,本質上是同一個 Message 的作業周期
12. Handler、Mesage 和 Runnable 的關系如何理解?
作為使用 Handler 機制的入口, Handler 是發送 Message 或 Runnable 的起點
發送的 Runnable 本質上也是 Message ,只不過作為 callback 屬性被持有
Handler 作為 target 屬性被持有在 Mesage 中 ,在 Message 執行條件滿足的時候供 Looper 回調
事實上,Handler 只是供 App 使用 Handler 機制的 API,實質來說,Message 是更為重要的載體。
13. IdleHandler 空閑 Message 了解過嗎?有什麼用?
適用於期望 空閑時候執行,但不影響主線程操作 的任務
系統應用:
Activity destroy 回調就放在了 IdleHandler 中
ActivityThread 中 GCHandler 使用了 IdleHandler,在空閑的時候執行 GC 操作
App 應用:
發送一個返回 true 的 IdleHandler,在裡面讓某個 View 不停閃爍,這樣當用戶發呆時就可以誘導用戶點擊這個 View
將某部分初始化放在 IdleHandler 里不影響 Activity 的啟動
14. 非同步 Message 或同步屏障了解過嗎?怎麼用?什麼原理?
非同步 Message:設置了 isAsync 屬性的 Message 實例
可以用非同步 Handler 發送
也可以調用 Message#setAsynchronous() 直接設置為非同步 Message
同步屏障:在 MessageQueue 的 某個位置放一個 target 屬性為 null 的 Message ,確保此後的非非同步 Message 無法執行,只能執行非同步 Message
原理:當 MessageQueue 輪循 Message 時候 發現建立了同步屏障的時候,會去跳過其他 Message,讀取下個 async 的 Message 並執行,屏障移除之前同步 Message 都會被阻塞
應用:比如 屏幕刷新 Choreographer 就使用到了同步屏障 ,確保屏幕刷新事件不會因為隊列負荷影響屏幕及時刷新。
注意: 同步屏障的添加或移除 API 並未對外公開,App 需要使用的話需要依賴反射機制
15. Looper 和 MessageQueue、Message 及 Handler 的關系?
Message 是承載任務的載體,在 Handler 機制中貫穿始終
Handler 則是對外公開的 API,負責發送 Message 和處理任務的回調,是 Message 的生產者
MessagQueue 負責管理待處理 Message 的入隊和出隊,是 Message 的容器
Looper 負責輪循 MessageQueue,保持線程持續運行任務,是 Message 的消費者
彩蛋:如何保證 MessageQueue 並發訪問安全?
任何線程都可以通過 Handler 生產 Message 並放入 MessageQueue 中,可 Queue 所屬的 Looper 在持續地讀取並嘗試消費 Message。如何保證兩者不產生死鎖?
Looper 在消費 Message 之前要先拿到 MessageQueue 的鎖, 只不過沒有 Message 或 Message 尚未滿足條件的進行等待前會事先釋放鎖 ,具體在於 nativePollOnce() 的調用在 synchronized 方法塊的外側。
Message 入隊前也需先拿到 MessageQueue 的鎖,而這時 Looper 線程正在等待且不持有鎖,可以確保 Message 的成功入隊。入隊後執行喚醒後釋放鎖,Native 收到 event 寫入後恢復 MessagQueue 的讀取並可以拿到鎖,成功出隊。
這樣一種在沒有 Message 可以消費時執行等待同時不佔著鎖的機制,避免了生產和消費的死鎖。
16. Native 側的 NativeMessageQueue 和 Looper 的作用是?
NativeMessageQueue 負責連接 Java 側的 MessageQueue,進行後續的 wait 和 wake,後續將創建 wake 的FD,並通過 epoll 機制等待或喚醒。 但並不參與管理 Java 的 Message
Native 側也需要 Looper 機制,等待和喚醒的需求是同樣的,所以將這部分實現都封裝到了 JNI 的NativeMessageQueue 和 Native 的 Looper 中, 供 Java 和 Native 一起使用
17. Native 側如何使用 Looper?
Looper Native 部分承擔了 Java 側 Looper 的等待和喚醒,除此之外其還提供了 Message、MessageHandler 或 WeakMessageHandler、LooperCallback 或 SimpleLooperCallback 等 API
這些部分可供 Looper 被 Native 側直接調用,比如 InputFlinger 廣泛使用了 Looper
主要方法是調用 Looper 構造函數或 prepare 創建 Looper,然後通過 poll 開始輪詢,接著 sendMessage 或 addEventFd,等待 Looper 的喚醒。 使用過程和 Java 的調用思路類似
18. Handler 為什麼可能導致內存泄露?如何避免?
持有 Activity 實例的內名內部類或內部類的 生命周期 應當和 Activity 保持一致,否則產生內存泄露的風險。
如果 Handler 使用不當,將造成不一致,表現為:匿名內部類或內部類寫法的 Handler、Handler$Callback、Runnable,或者Activity 結束時仍有活躍的 Thread 線程或 Looper 子線程
具體在於:非同步任務仍然活躍或通過發送的 Message 尚未處理完畢,將使得內部類實例的 生命周期被錯誤地延長 。造成本該回收的 Activity 實例 被別的 Thread 或 Main Looper 占據而無法及時回收 (活躍的 Thread 或 靜態屬性 sMainLooper 是 GC Root 對象)
建議的做法:
無論是 Handler、Handler$Callback 還是 Runnable,盡量採用 靜態內部類 + 弱引用 的寫法,確保盡管發生不當引用的時候也可以因為弱引用能清楚持有關系
另外在 Activity 銷毀的時候及時地 終止 Thread、停止子線程的 Looper 或清空 Message ,確保徹底切斷 Activity 經由 Message 抵達 GC Root 的引用源頭(Message 清空後會其與 Handler 的引用關系,Thread 的終止將結束其 GC Root 的源頭)
注意:靜態的 sThreadLocal 實例不持有存放 Looper 實例的 ThreadLocalMap,而是由 Thread 持有。從這個角度上來講,Looper 會被活躍的 GC Root Thread 持有,進而也可能導致內存泄露。
彩蛋:網傳的 Handler$Callback 方案能否解決內存泄露?
不能。
Callback 採用內部類或匿名內部類寫法的話,默認持有 Activity 的引用,而 Callback 被 Handler 持有。這最終將導致 Message -> Handler -> Callback -> Activity 的鏈條仍然存在。
19. Handler 在系統當中的應用
特別廣泛,比如:
Activity 生命周期的管理
屏幕刷新
HandlerThread、IntentService
AsyncTask 等。
主要利用 Handler 的切換線程、主線程非同步 Message 的重要特性。注意:Binder 線程非主線程,但很多操作比如生命周期的管理都要回到主線程,所以很多 Binder 調用過來後都要通過 Handler 切換回主線程執行後續任務,比如 ActviityThread$H 就是 extends Handler。
20. Android 為什麼不允許並發訪問 UI?
Android 中 UI 非線程安全,並發訪問的話會造成數據和顯示錯亂。
但此限制的檢查始於ViewRootImpl#checkThread(),其會在刷新等多個訪問 UI 的時機被調用,去檢查當前線程,非主線程的話拋出異常。
而 ViewRootImpl 的創建在 onResume() 之後,也就是說如果在 onResume() 執行前啟動線程訪問 UI 的話是不會報錯的,這點需要留意!
彩蛋:onCreate() 里子線程更新 UI 有問題嗎?為什麼?
不會。
因為異常的檢測處理在 ViewRootImpl 中,該實例的創建和檢測在 onResume() 之後進行。
㈢ 獲取swagger 上url 和方法名(spring boot )
@Test
public void listComboCourse() {
List> resultList =new ArrayList<>();
//RequestMappingHandlerMapping requestMappingHandlerMapping =
// applicationContext.getBean(RequestMappingHandlerMapping.class);
RequestMappingHandlerMapping requestMappingHandlerMapping =
(RequestMappingHandlerMapping)applicationContext.getBean("requestMappingHandlerMapping");
// 獲取url與類和方法的對應信息
Map map = requestMappingHandlerMapping.getHandlerMethods();
for (Map.Entry mappingInfoHandlerMethodEntry : map.entrySet()) {
Map resultMap =new LinkedHashMap<>();
RequestMappingInfo requestMappingInfo = mappingInfoHandlerMethodEntry.getKey();
HandlerMethod handlerMethod = mappingInfoHandlerMethodEntry.getValue();
resultMap.put("className",handlerMethod.getMethod().getDeclaringClass().getName()); // 類名
Annotation[] parentAnnotations = handlerMethod.getBeanType().getAnnotations();
for (Annotation annotation : parentAnnotations) {
if (annotationinstanceof Api) {
Api api = (Api) annotation;
resultMap.put("classDesc",api.value());
resultMap.put("functionDesc",api.tags()[0].toString());//介面描述
}else if (annotationinstanceof RequestMapping) {
RequestMapping requestMapping = (RequestMapping) annotation;
if (null != requestMapping.value() && requestMapping.value().length >0) {
resultMap.put("classURL",requestMapping.value()[0]);//類URL
}
}
}
resultMap.put("methodName", handlerMethod.getMethod().getName()); // 方法名
Annotation[] annotations = handlerMethod.getMethod().getDeclaredAnnotations();
if (annotations !=null) {
// 處理具體的方法信息
for (Annotation annotation : annotations) {
if (annotationinstanceof ApiOperation) {
ApiOperation methodDesc = (ApiOperation) annotation;
String desc = methodDesc.value();
resultMap.put("methodDesc",desc);//介面描述
}
}
}
PatternsRequestCondition p = requestMappingInfo.getPatternsCondition();
for (String url : p.getPatterns()) {
if(!url.contains("app")){
resultMap.put("methodURL",url);//請求URL
}
}
methodsCondition = requestMappingInfo.getMethodsCondition();
for (RequestMethod requestMethod : methodsCondition.getMethods()) {
resultMap.put("requestType",requestMethod.toString());//請求方式:POST/PUT/GET/DELETE
}
resultList.add(resultMap);
}
System.out.println(JSON.toJSON(resultList));
resultList.forEach(a->{
});
}
㈣ Android中Handler的使用方法
Handler在Android中主要是負責發送和處理消息。它的主要用途大致是下面兩個:
1)按計劃發送消息或執行某個Runnanble;
2)從其他線程中發送來的消息放入消息隊列中,避免線程沖突(常見於更新UI線程)
學寫一下,在UI線程中,系統已經有一個Activity來處理了,你可以再起若干個Handler來處理。在實例化Handler的時候,只要有Handler的指針,任何線程也都可以sendMessage。
Handler對於Message的處理是非同步處理的。一個Looper 只有處理完一條Message才會讀取下一條,所以消息的處理是阻塞形式的(handleMessage()方法里不應該有耗時操作,可以將耗時操作放在其他線程執行,操作完後發送Message(通過sendMessges方法),然後由handleMessage()更新UI)。
根據對視頻的學習寫了一個通過Button控制項啟動進度條(類似於下載等操作)的程序,簡單介紹一下,有兩個Button控制項,一個是開始,點擊之後會顯示一個進度條以每次十分之一的進度進行(一開始是隱藏的),另一個是停止,可以中斷進度。
java代碼:
1 package zzl.handleactivity;
2
3 import android.app.Activity;
4 import android.os.Bundle;
5 import android.os.Handler;
6 import android.os.Message;
7 import android.view.Gravity;
8 import android.view.View;
9 import android.view.View.OnClickListener;
10 import android.widget.Button;
11 import android.widget.ProgressBar;
12 import android.widget.Toast;
13
14 public class Handler_01 extends Activity {
15
16 //聲明變數
17 private Button startButton=null;
18 private Button endButton=null;
19 private ProgressBar firstBar=null;
20 private Toast toast=null;
21 @Override
22 protected void onCreate(Bundle savedInstanceState) {
23 super.onCreate(savedInstanceState);
24 setContentView(R.layout.main);
25
26 //根據ID獲取對象
27 startButton =(Button)findViewById(R.id.startButton);
28 endButton=(Button)findViewById(R.id.endButton);
29 firstBar=(ProgressBar)findViewById(R.id.firstBar);
30 //給對象設置動作監聽器
31 startButton.setOnClickListener(new StartButtonListener());
32 endButton.setOnClickListener(new EndButtonListener());
33 }
34
35 class StartButtonListener implements OnClickListener{
36
37 @Override
38 public void onClick(View v) {
39 // TODO Auto-generated method stub
40 //一開始執行,加入到消息隊列,不延遲,
41 //然後馬上執行run方法
42 firstBar.setVisibility(View.VISIBLE);
43 firstBar.setMax(100);
44 handler.post(upRunnable);
45 toast=Toast.makeText(Handler_01.this, "運行開始", Toast.LENGTH_SHORT);
46 toast.setGravity(Gravity.CENTER, 0, 0);
47 toast.show();
48 }
49 }
50 class EndButtonListener implements OnClickListener{
51
52 @Override
53 public void onClick(View v) {
54 // TODO Auto-generated method stub
55 //停止
56 handler.removeCallbacks(upRunnable);
57 System.out.println("It's time to stop...");
58 }
59 }
60
61 //創建handler對象,在調用post方法
62 //非同步消息處理:將下載或者處理大數據等等單獨放到另一個線程
63 //更好的用戶體驗
64 Handler handler=new Handler(){
65
66 @Override
67 public void handleMessage(Message msg){
68 firstBar.setProgress(msg.arg1);
69 firstBar.setSecondaryProgress(msg.arg1+10);
70 //handler.post(upRunnable);
71 if(msg.arg1<=100) {
72 handler.post(upRunnable); //將要執行的線程放入到隊列當中
73 }else {
74 handler.removeCallbacks(upRunnable);
75 }
76 }
77 };
78
79 //聲明線程類:實現Runnable的介面
80 Runnable upRunnable=new Runnable() {
81
82 int i=0;
83 @Override
84 public void run() {//程序的運行狀態
85 // TODO Auto-generated method stub
86 //postDelayed方法:把線程對象加入到消息隊列中
87 // 隔2000ms(延遲)
88 System.out.println("It's time to start...");
89 i=i+10;
90 //獲取Message消息對象
91 Message msg=handler.obtainMessage();
92 //將msg對象的arg1(還有arg2)對象的值設置
93 //使用這兩個變數傳遞消息優點:系統消耗性能較少
94 msg.arg1=i;
95 try{
96 //設置當前顯示睡眠1秒
97 Thread.sleep(1000);
98 }catch(InterruptedException e){
99 e.printStackTrace();
100 }
101 //將msg對象加入到消息隊列當中
102 handler.sendMessage(msg);
103 if(i==100){//當值滿足時,將線程對象從handle中剔除
104 handler.removeCallbacks(upRunnable);
105 firstBar.setVisibility(View.GONE);
106 //臨時彈出
107
108 toast=Toast.makeText(Handler_01.this, "運行結束", Toast.LENGTH_SHORT);
109 toast.setGravity(Gravity.CENTER, 0, 0);
110 toast.show();
111 }
112 }
113 };
114 }
main.xml
1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
2 xmlns:tools="http://schemas.android.com/tools"
3 android:orientation="vertical"
4 android:layout_width="match_parent"
5 android:layout_height="match_parent"
6 tools:context=".Handler_01" >
7
8 <ProgressBar
9 android:id="@+id/firstBar"
10 style="?android:attr/progressBarStyleHorizontal"
11 android:layout_width="200dp"
12 android:layout_height="wrap_content"
13 android:visibility="gone"/>
14
15 <Button
16 android:id="@+id/startButton"
17 android:layout_width="wrap_content"
18 android:layout_height="wrap_content"
19 android:text="@string/start" />
20
21 <Button
22 android:id="@+id/endButton"
23 android:layout_width="wrap_content"
24 android:layout_height="wrap_content"
25 android:text="@string/end" />
26
27 </LinearLayout>
總結:
1)當點擊開始或者運行結束的時候,都會通過調用Toas彈出臨時窗口,Toast.makeText(Handler_01.this, "運行結束", Toast.LENGTH_SHORT),這一句一開始總是執行出錯,原因在於必須調用它的show方法才可以顯示出來,還可以通過設置它的位置來顯示;
2)在xml中 android:text="@string/end",則需要在layout下的string文件中敲寫相應的代碼
3)原本代碼有一些瑕疵,就是沒有下面這一段代碼:
1 if(msg.arg1<=100) {
2 handler.post(upRunnable); //將要執行的線程放入到隊列當中
3 }else {
4 handler.removeCallbacks(upRunnable);
5 }
這樣就導致了upRunnable的run方法出現了死循環,這樣,雖然程序UI本身沒有問題,但是內部卻又很大的缺陷
這是因為
1 if(i==100){//當值滿足時,將線程對象從handle中剔除
2 handler.removeCallbacks(upRunnable);
3 firstBar.setVisibility(View.GONE);
4 toast=Toast.makeText(Handler_01.this, "運行結束", Toast.LENGTH_SHORT);
5 toast.setGravity(Gravity.CENTER, 0, 0);
6 toast.show();
7 }
這一段代碼看似是把upRunnable線程從線程對象隊列中移除,但是再次之前又前執行了handler.sendMessage(msg);這句代碼
從而導致下面的代碼又被執行到
1 public void handleMessage(Message msg){
2 firstBar.setProgress(msg.arg1);
3 firstBar.setSecondaryProgress(msg.arg1+10);
4
5 }
這樣肯定會使upRunnable線程重新加入到線程對象隊列中,updateThread的run方法重復執行,這就導致了死循環。所以必須加上之前的那段代碼,通過判斷來控制循環終止。並且run方法中的if(i==100)的那段代碼也是可以不用的,不過我是寫了一些其他代碼就懶得優化了,這是後話了。
4) 剛剛大家看到我們可以通過敲寫System.out.println在logcat中顯示,一開始eclipse編譯器中是沒有,這是如何顯示出來的?
大家可以再window的show view中找到logCat(deprecated)通過下圖中綠色的「+」添加出來
然後顯示內容的時候,選擇右上角「V D I W E 」的I就可以比較清晰的顯示出來了,當然你也可以選擇另外一個logCat來顯示,方法類似。
5)實際上,Handler在默認情況下,和調用它的Activity是處於同一個線程的。 上述Handler的使用示例中,雖然聲明了線程對象,但是在實際調用當中它並沒有調用線程的start()方法,而是直接調用當前線程的run()方法。
如果要實現調用另一個新的線程,只要注釋post方法,然後加上這樣兩段代碼即可: Thread t = new Thread(r); t.start();