用IdleHandler來性能優化及原理源碼分析

背景:

經常在做一些app冷啟動速度優化等性能優化工作時候,經常可能會發現有時候需要引入一些第三方sdk,或者庫,這些庫一般會要求我們在onCreate中進行初始化等,但是onCreate屬于生命周期的回調方法,如果onCreate中業務過多就會影響整個app的冷啟動時長,很多人這個時候第一想到可能是放入子線程等,但是經常又發現有的調用還要求在主線程調用等限制,所以針對這種情況下就經常會使用到一個IdleHandler,這個當初使用時候大家可能就只是字面意思理解為空閑的Handler,在空閑時候執行,其實沒有深入去看看它到底實現原理是什么,今天剛好有機會就來給大家進行一個剖析。

IdleHandler 的概念

IdleHandler 是 MessageQueue 提供的一個接口,用于監聽消息隊列的空閑狀態。它允許開發者在主線程處于空閑時,執行一些低優先級的任務。IdleHandler 的核心方法 queueIdle() 會在系統檢測到消息隊列空閑時調用。返回值決定了 IdleHandler 是否繼續在隊列中保留:若返回 true,則該回調在下次隊列空閑時繼續執行;反之,返回 false 則表示僅執行一次后移除。

IdleHandler 和性能優化

IdleHandler 的出現正好迎合了性能優化的需求。在實際開發中,有一些任務并非必須實時完成,例如日志上報、資源預加載、緩存清理、數據統計、一些動畫或預渲染任務等。利用 IdleHandler,可以將這些不緊急的任務推遲到主線程消息隊列空閑時再執行,從而避免干擾用戶看到的實時界面更新,提高整體界面流暢度和響應速度。因此,在 UI 主線程相對繁忙時,通過 IdleHandler 來分攤任務,可以讓系統先處理用戶的核心交互,就比如onCreate是生命周期方法,如理里面初始化太多東西影響冷啟動速度,針對一些可以延后不那么緊急任務可以待系統空閑時再處理任務,充分利用 CPU 空閑時間。

源碼分析IdleHandler

為了更加全面了解IdleHandler,下面將要從以下幾個部分進行IdleHandler的源碼分析。
1、
IdleHandler的接口解釋:
frameworks/base/core/java/android/os/MessageQueue.java

    /*** Callback interface for discovering when a thread is going to block* waiting for more messages.*/public static interface IdleHandler {/*** Called when the message queue has run out of messages and will now* wait for more.  Return true to keep your idle handler active, false* to have it removed.  This may be called if there are still messages* pending in the queue, but they are all scheduled to be dispatched* after the current time.*/boolean queueIdle();}

上面注釋就可以看出來,這個IdleHandler屬于一個回調接口,這個接口在線程即將要阻塞等待更多消息時候會被調用。
queueIdle就是對應的回調函數,這個queueIdle有一個返回值true或false,這兩個值分別有如下區別:
如果返回true意味著下一次空閑依舊會執行這個IdleHandler的回調函數。
如果返回false就代表當前IdleHandler已經執行完畢,下次空閑不會再執行該IdleHandler。

添加IdleHandler接口

    /*** Add a new {@link IdleHandler} to this message queue.  This may be* removed automatically for you by returning false from* {@link IdleHandler#queueIdle IdleHandler.queueIdle()} when it is* invoked, or explicitly removing it with {@link #removeIdleHandler}.** <p>This method is safe to call from any thread.** @param handler The IdleHandler to be added.*/public void addIdleHandler(@NonNull IdleHandler handler) {if (handler == null) {throw new NullPointerException("Can't add a null IdleHandler");}synchronized (this) {//這里其實就是加入到mIdleHandlers這個集合中mIdleHandlers.add(handler);}}

這里的addIdleHandler添加就是簡單加入的mIdleHandlers這個集合中,方便后面執行時候進行遍歷

IdleHandler的觸發執行

    @UnsupportedAppUsageMessage next() {int pendingIdleHandlerCount = -1; // -1 only during first iterationint nextPollTimeoutMillis = 0;for (;;) {//進入等待消息,要被喚醒2種情況,1被其他函數wake,2等待時間nextPollTimeoutMillis到nativePollOnce(ptr, nextPollTimeoutMillis);synchronized (this) {// Try to retrieve the next message.  Return if found.final long now = SystemClock.uptimeMillis();Message prevMsg = null;Message msg = mMessages;//當消息的Handler為空時,則查詢異步消息if (msg != null && msg.target == null) {// Stalled by a barrier.  Find the next asynchronous message in the queue.//當查詢到異步消息,則立刻退出循環do {prevMsg = msg;msg = msg.next;} while (msg != null && !msg.isAsynchronous());}if (msg != null) {//檢測msg時間是還沒到了if (now < msg.when) {// Next message is not ready.  Set a timeout to wake up when it is ready.//還沒到msg的處理時間,那么就會與當前時間進行差值,這個差值就是等待時間nextPollTimeoutMillisnextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);} else {// Got a message.mBlocked = false;if (prevMsg != null) {prevMsg.next = msg.next;} else {//設置下一個消息為mMessagesmMessages = msg.next;}msg.next = null;msg.markInUse();//直接return回去消息,也就是有消息都在這里return了不會執行下面業務return msg;}} else {//沒有找到消息// No more messages.nextPollTimeoutMillis = -1;}// If first time idle, then get the number of idlers to run.// Idle handles only run if the queue is empty or if the first message// in the queue (possibly a barrier) is due to be handled in the future.//找了一圈發現沒有要處理的消息了,那么就開始判斷是否有IdleHandler的消息要處理//注意這里的pendingIdleHandlerCount第一次進入才是-1,后續一旦執行IdleHandler都是變成0,不會再進入if (pendingIdleHandlerCount < 0&& (mMessages == null || now < mMessages.when)) {//把mIdleHandlers集合大小賦值給pendingIdleHandlerCountpendingIdleHandlerCount = mIdleHandlers.size();}//如果發現沒有IdleHandler,那么就返回循環進行block阻塞等待喚醒if (pendingIdleHandlerCount <= 0) {// No idle handlers to run.  Loop and wait some more.mBlocked = true;continue;}//如果mPendingIdleHandlers這個數組沒有初始化就進行初始化,這里最小大小為4應該是為了性能以防頻繁擴容if (mPendingIdleHandlers == null) {mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];}//mIdleHandlers轉成了數組mPendingIdleHandlersmPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);}// Run the idle handlers.// We only ever reach this code block during the first iteration.//開始正式執行對應的IdleHandlerfor (int i = 0; i < pendingIdleHandlerCount; i++) {final IdleHandler idler = mPendingIdleHandlers[i];mPendingIdleHandlers[i] = null; // release the reference to the handlerboolean keep = false;try {//執行IdleHandler的對應回調業務方法keep = idler.queueIdle();} catch (Throwable t) {Log.wtf(TAG, "IdleHandler threw exception", t);}//這里的keep就是queueIdle的返回值,如果返回false就會從mIdleHandlers進行移除if (!keep) {synchronized (this) {mIdleHandlers.remove(idler);}}}//把pendingIdleHandlerCount變成0,不是-1,這樣防止死循環執行IdleHandler// Reset the idle handler count to 0 so we do not run them again.pendingIdleHandlerCount = 0;//這里是為了讓盡快遍歷一下是否有消息,因為可能執行IdleHandler期間有新消息// While calling an idle handler, a new message could have been delivered// so go back and look again for a pending message without waiting.nextPollTimeoutMillis = 0;}}

上面next方法,關于IdleHandler執行總結如下:
1、next方法遍歷消息隊列,確實沒有尋找到要處理的消息任務
2、沒有要處理的消息則開始處理IdleHandler相關的任務
3、如果IdleHandler的queueIdle返回false則會從mIdleHandlers刪除,下次空閑就不會在執行這個IdleHandler,否則true的話會每次空閑都執行

實戰案例

測試一下queueIdle返回false,即只執行一次queueIdle

    protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main2);getMainLooper().getQueue().addIdleHandler(new MessageQueue.IdleHandler() {@Overridepublic boolean queueIdle() {//TODO 執行一些不那么緊急任務                Trace.beginSection("queueIdle no next runing");Log.i("lsm66666","queueIdle return false,no next runing");Trace.endSection();return false;}});}

驗證結果:
在這里插入圖片描述

再測試一下queueIdle返回true,即執行多次queueIdle

    protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main2);getMainLooper().getQueue().addIdleHandler(new MessageQueue.IdleHandler() {@Overridepublic boolean queueIdle() {//TODO 執行一些不那么緊急任務                Trace.beginSection("queueIdle has next runing");Log.i("lsm66666","queueIdle return true,has next runing");Trace.endSection();return true;}});}

看看日志打印情況
在這里插入圖片描述

systrace結合分析IdleHandler

在這里插入圖片描述

上面也可以看出來IdleHandler的執行都在沒有消息可以處理了,才會執行,而且執行完成后就進入消息等待時期。
從上面也可以看出來,IdleHandler確實是在主線程空閑暫時沒有消息處理時候進行執行的,可以起到一個錯峰執行的目的,不過也要注意以下幾點:

1、切勿讓IdleHandler執行耗時操作,應該是可以快速完成的一些任務

2、盡量都讓IdleHandler執行是一次性任務,即queueIdle返回false,以防每次空閑都有重復調用

文章參考:https://mp.weixin.qq.com/s/7R2ws-6oBij3OrNMA_IfTA

更多framework實戰開發干貨,請關注下面“千里馬學框架”

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/71550.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/71550.shtml
英文地址,請注明出處:http://en.pswp.cn/web/71550.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

SyntaxError: Invalid or unexpected token in JSON at position x

&#x1f90d; 前端開發工程師、技術日更博主、已過CET6 &#x1f368; 阿珊和她的貓_CSDN博客專家、23年度博客之星前端領域TOP1 &#x1f560; 牛客高級專題作者、打造專欄《前端面試必備》 、《2024面試高頻手撕題》、《前端求職突破計劃》 &#x1f35a; 藍橋云課簽約作者、…

04.基于C++實現多線程TCP服務器與客戶端通信

基于C實現多線程TCP服務器與客戶端通信 目錄 一、項目背景與目標二、從零開始理解網絡通信三、相關技術背景知識 1. 守護進程(Daemon Process)2. 線程池(Thread Pool)3. RAII設計模式 四、項目整體結構與邏輯五、核心模塊詳細分析 1. TCP服務器模塊2. 線程池模塊3. 任務處理模…

從0到1入門Linux

一、常用命令 ls 列出目錄內容 cd切換目錄mkdir創建新目錄rm刪除文件或目錄cp復制文件或目錄mv移動或重命名文件和目錄cat查看文件內容grep在文件中查找指定字符串ps查看當前進程狀態top查看內存kill終止進程df -h查看磁盤空間存儲情況iotop -o直接查看比較高的磁盤讀寫程序up…

Nginx負載均衡配置詳解:輕松實現高可用與高性能

在現代Web應用中&#xff0c;負載均衡是確保系統高可用性和高性能的關鍵技術之一。Nginx作為一款高性能的HTTP服務器和反向代理服務器&#xff0c;其負載均衡功能被廣泛應用于各種場景。本文將詳細介紹如何使用Nginx實現負載均衡配置&#xff0c;幫助開發者輕松應對高并發和大流…

使用chroot預安裝軟件到ubuntu22中

1、安裝依賴 # 安裝依賴工具 sudo apt update && sudo apt install -y \ squashfs-tools \ genisoimage \ xorriso \ isolinux \ syslinux-utils \ p7zip-full sudo apt update sudo apt install grub-pc-bin grub-efi-amd64-bin -y # 創建工作目錄 mkdir -p ./custom-…

php代碼審計工具-rips

代碼審計 代碼審計就是檢查所寫的代碼中是否有漏洞&#xff0c;檢查程序的源代碼是否有權限從而被黑客攻擊&#xff0c;同時也檢查了書寫的代碼是否規范。通過自動化的審查和人工審查的方式&#xff0c;逐行檢查源代碼&#xff0c;發現源代碼中安全缺陷所造成的漏洞&#xff0…

Docker參數,以及倉庫搭建

一。Docker的構建參數 注釋&#xff1a; 1.對于CMD&#xff0c;如果不想顯示&#xff0c;而是使用交互界面&#xff1a;docker run -ti --rm --name test2 busybox:v5 sh 2.對于CMD&#xff0c;一個交互界面只可以使用一個&#xff0c;如果想多次使用CMD&#xff0c;則用ENTR…

基于Python Django的人臉識別上課考勤系統(附源碼,部署)

博主介紹&#xff1a;?程序員徐師兄、7年大廠程序員經歷。全網粉絲12w、csdn博客專家、掘金/華為云/阿里云/InfoQ等平臺優質作者、專注于Java技術領域和畢業項目實戰? &#x1f345;文末獲取源碼聯系&#x1f345; &#x1f447;&#x1f3fb; 精彩專欄推薦訂閱&#x1f447;…

基于python實現的疫情數據可視化分析系統

基于python實現的疫情數據可視化分析系統 開發語言:Python 數據庫&#xff1a;MySQL所用到的知識&#xff1a;Django框架工具&#xff1a;pycharm、Navicat 系統功能實現 總體設計 系統實現 系統功能模塊 系統首頁可以查看首頁、疫情信息、核酸檢測、新聞資訊、個人中心、后…

(十 八)趣學設計模式 之 觀察者模式!

目錄 一、 啥是觀察者模式&#xff1f;二、 為什么要用觀察者模式&#xff1f;三、 觀察者模式的實現方式四、 觀察者模式的優缺點五、 觀察者模式的應用場景六、 總結 &#x1f31f;我的其他文章也講解的比較有趣&#x1f601;&#xff0c;如果喜歡博主的講解方式&#xff0c;…

Spring Boot 緩存最佳實踐:從基礎到生產的完整指南

Spring Boot 緩存最佳實踐&#xff1a;從基礎到生產的完整指南 引言 在現代分布式系統中&#xff0c;緩存是提升系統性能的銀彈。Spring Boot 通過 spring-boot-starter-cache? 模塊提供了開箱即用的緩存抽象&#xff0c;但如何根據業務需求實現靈活、可靠的緩存方案&#xf…

蘋果Siri升級遇阻,國行iPhone或將引入阿里、百度AI自救

AI整合進展緩慢 蘋果正加速將生成式AI技術整合至Siri&#xff0c;但內部消息稱其底層技術研發落后于競爭對手&#xff0c;進展未達預期。 國行iPhone將引入雙AI模型 蘋果計劃在2025年中期為國行iPhone引入AI功能&#xff0c;目前已敲定與 阿里巴巴、百度 合作&#xff0c;用戶…

阿里推出全新推理模型(因果語言模型),僅1/20參數媲美DeepSeek R1

阿里Qwen 團隊正式發布了他們最新的研究成果——QwQ-32B大語言模型&#xff01;這款模型不僅名字萌萌噠(QwQ)&#xff0c;實力更是不容小覷&#xff01;&#x1f60e; QwQ-32B 已在 Hugging Face 和 ModelScope 開源&#xff0c;采用了 Apache 2.0 開源協議。大家可通過 Qwen C…

TomcatServlet

https://www.bilibili.com/video/BV1UN411x7xe tomcat tomcat 架構圖&#xff0c;與 jre&#xff0c;應用程序之前的關系 安裝使用 tomcat 10 開始&#xff0c;api 從 javax.* 轉為使用 jakarta.*&#xff0c;需要至少使用 jdk 11 cmd 中默認 gbk 編碼&#xff0c;解決控制…

JDK ZOOKEEPER KAFKA安裝

JDK17下載安裝 mkdir -p /usr/local/develop cd /usr/local/develop 將下載的包上傳服務器指定路徑 解壓文件 tar -zxvf jdk-17.0.14_linux-x64_bin.tar.gz -C /usr/local/develop/ 修改文件夾名 mv /usr/local/develop/jdk-17.0.14 /usr/local/develop/java17 配置環境變量…

高考數學。。。

2024上 具體來說&#xff0c;直線的參數方程可以寫為&#xff1a; x1t y?t z1t 二、簡答題(本大題共5小題&#xff0c;每小題7分&#xff0c;共35分。) 12.數學學習評價不僅要關注結果評價&#xff0c;也要關注過程評價。簡要說明過程評價應關注哪幾個方面。…

C# 實現鼠標軌跡錄制與回放自動化功能(附源碼)

在軟件自動化測試或者重復性辦公任務中&#xff0c;鼠標操作的自動化可以大大減少人工干預&#xff0c;提高工作效率。這里將詳細介紹如何使用 C# 實現鼠標軌跡的錄制與回放功能&#xff0c;代碼結構清晰&#xff0c;具有較強的擴展性。 引用 NuGet 包 在開發這個功能時&…

Nacos 核心功能實戰筆記(超詳細)

Nacos 核心功能實戰筆記 一、Nacos 簡介 1. 是什么&#xff1f; 全稱&#xff1a;Nacos Naming and Configuration Service定位&#xff1a;阿里巴巴開源的 動態服務發現、配置管理、服務管理平臺核心功能&#xff1a;服務注冊與發現 統一配置管理 服務健康監測適用場景&…

安裝remixd,在VScode創建hardhat

在終端&#xff0c;以管理員身份&#xff0c;cmd 需要科學上網 npm install -g remix-project/remixd 在vscode插件中&#xff0c;安裝solidity插件&#xff0c;是暗灰色那款 1.將nodeJs的版本升級至18以上 2.在vscode打開一個新的文件&#xff0c;在終端輸入 npx hardhat 3.…

unity pico開發 四 物體交互 抓取 交互層級

文章目錄 手部設置物體交互物體抓取添加抓取抓取三種類型抓取點偏移抓取事件抓取時不讓物體吸附到手部 射線抓取交互層級 手部設置 為手部&#xff08;LeftHandController&#xff09;添加XRDirInteractor腳本 并添加一個球形碰撞盒&#xff0c;勾選isTrigger,調整大小為0.1 …