主頁 > 移動端開發 > Android全面決議Handler

Android全面決議Handler

2021-07-26 08:18:45 移動端開發

前言:

由于HandlerBinder是Android開發的倆大利器之一,所以有必要來深入講解一下Handler,關于Binder可以參考我上一篇博客《IPC機制 Binder》,廢話不多說,今天我將圖文并茂,一節一節解剖Handler,一節一節的總結Handler相關知識點,

<style>#mermaid-svg-w8x1zwBESUZ0ZyLz .label{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);fill:#333;color:#333}#mermaid-svg-w8x1zwBESUZ0ZyLz .label text{fill:#333}#mermaid-svg-w8x1zwBESUZ0ZyLz .node rect,#mermaid-svg-w8x1zwBESUZ0ZyLz .node circle,#mermaid-svg-w8x1zwBESUZ0ZyLz .node ellipse,#mermaid-svg-w8x1zwBESUZ0ZyLz .node polygon,#mermaid-svg-w8x1zwBESUZ0ZyLz .node path{fill:#ECECFF;stroke:#9370db;stroke-width:1px}#mermaid-svg-w8x1zwBESUZ0ZyLz .node .label{text-align:center;fill:#333}#mermaid-svg-w8x1zwBESUZ0ZyLz .node.clickable{cursor:pointer}#mermaid-svg-w8x1zwBESUZ0ZyLz .arrowheadPath{fill:#333}#mermaid-svg-w8x1zwBESUZ0ZyLz .edgePath .path{stroke:#333;stroke-width:1.5px}#mermaid-svg-w8x1zwBESUZ0ZyLz .flowchart-link{stroke:#333;fill:none}#mermaid-svg-w8x1zwBESUZ0ZyLz .edgeLabel{background-color:#e8e8e8;text-align:center}#mermaid-svg-w8x1zwBESUZ0ZyLz .edgeLabel rect{opacity:0.9}#mermaid-svg-w8x1zwBESUZ0ZyLz .edgeLabel span{color:#333}#mermaid-svg-w8x1zwBESUZ0ZyLz .cluster rect{fill:#ffffde;stroke:#aa3;stroke-width:1px}#mermaid-svg-w8x1zwBESUZ0ZyLz .cluster text{fill:#333}#mermaid-svg-w8x1zwBESUZ0ZyLz div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);font-size:12px;background:#ffffde;border:1px solid #aa3;border-radius:2px;pointer-events:none;z-index:100}#mermaid-svg-w8x1zwBESUZ0ZyLz .actor{stroke:#ccf;fill:#ECECFF}#mermaid-svg-w8x1zwBESUZ0ZyLz text.actor>tspan{fill:#000;stroke:none}#mermaid-svg-w8x1zwBESUZ0ZyLz .actor-line{stroke:grey}#mermaid-svg-w8x1zwBESUZ0ZyLz .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333}#mermaid-svg-w8x1zwBESUZ0ZyLz .messageLine1{stroke-width:1.5;stroke-dasharray:2, 2;stroke:#333}#mermaid-svg-w8x1zwBESUZ0ZyLz #arrowhead path{fill:#333;stroke:#333}#mermaid-svg-w8x1zwBESUZ0ZyLz .sequenceNumber{fill:#fff}#mermaid-svg-w8x1zwBESUZ0ZyLz #sequencenumber{fill:#333}#mermaid-svg-w8x1zwBESUZ0ZyLz #crosshead path{fill:#333;stroke:#333}#mermaid-svg-w8x1zwBESUZ0ZyLz .messageText{fill:#333;stroke:#333}#mermaid-svg-w8x1zwBESUZ0ZyLz .labelBox{stroke:#ccf;fill:#ECECFF}#mermaid-svg-w8x1zwBESUZ0ZyLz .labelText,#mermaid-svg-w8x1zwBESUZ0ZyLz .labelText>tspan{fill:#000;stroke:none}#mermaid-svg-w8x1zwBESUZ0ZyLz .loopText,#mermaid-svg-w8x1zwBESUZ0ZyLz .loopText>tspan{fill:#000;stroke:none}#mermaid-svg-w8x1zwBESUZ0ZyLz .loopLine{stroke-width:2px;stroke-dasharray:2, 2;stroke:#ccf;fill:#ccf}#mermaid-svg-w8x1zwBESUZ0ZyLz .note{stroke:#aa3;fill:#fff5ad}#mermaid-svg-w8x1zwBESUZ0ZyLz .noteText,#mermaid-svg-w8x1zwBESUZ0ZyLz .noteText>tspan{fill:#000;stroke:none}#mermaid-svg-w8x1zwBESUZ0ZyLz .activation0{fill:#f4f4f4;stroke:#666}#mermaid-svg-w8x1zwBESUZ0ZyLz .activation1{fill:#f4f4f4;stroke:#666}#mermaid-svg-w8x1zwBESUZ0ZyLz .activation2{fill:#f4f4f4;stroke:#666}#mermaid-svg-w8x1zwBESUZ0ZyLz .mermaid-main-font{font-family:"trebuchet ms", verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-w8x1zwBESUZ0ZyLz .section{stroke:none;opacity:0.2}#mermaid-svg-w8x1zwBESUZ0ZyLz .section0{fill:rgba(102,102,255,0.49)}#mermaid-svg-w8x1zwBESUZ0ZyLz .section2{fill:#fff400}#mermaid-svg-w8x1zwBESUZ0ZyLz .section1,#mermaid-svg-w8x1zwBESUZ0ZyLz .section3{fill:#fff;opacity:0.2}#mermaid-svg-w8x1zwBESUZ0ZyLz .sectionTitle0{fill:#333}#mermaid-svg-w8x1zwBESUZ0ZyLz .sectionTitle1{fill:#333}#mermaid-svg-w8x1zwBESUZ0ZyLz .sectionTitle2{fill:#333}#mermaid-svg-w8x1zwBESUZ0ZyLz .sectionTitle3{fill:#333}#mermaid-svg-w8x1zwBESUZ0ZyLz .sectionTitle{text-anchor:start;font-size:11px;text-height:14px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-w8x1zwBESUZ0ZyLz .grid .tick{stroke:#d3d3d3;opacity:0.8;shape-rendering:crispEdges}#mermaid-svg-w8x1zwBESUZ0ZyLz .grid .tick text{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-w8x1zwBESUZ0ZyLz .grid path{stroke-width:0}#mermaid-svg-w8x1zwBESUZ0ZyLz .today{fill:none;stroke:red;stroke-width:2px}#mermaid-svg-w8x1zwBESUZ0ZyLz .task{stroke-width:2}#mermaid-svg-w8x1zwBESUZ0ZyLz .taskText{text-anchor:middle;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-w8x1zwBESUZ0ZyLz .taskText:not([font-size]){font-size:11px}#mermaid-svg-w8x1zwBESUZ0ZyLz .taskTextOutsideRight{fill:#000;text-anchor:start;font-size:11px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-w8x1zwBESUZ0ZyLz .taskTextOutsideLeft{fill:#000;text-anchor:end;font-size:11px}#mermaid-svg-w8x1zwBESUZ0ZyLz .task.clickable{cursor:pointer}#mermaid-svg-w8x1zwBESUZ0ZyLz .taskText.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}#mermaid-svg-w8x1zwBESUZ0ZyLz .taskTextOutsideLeft.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}#mermaid-svg-w8x1zwBESUZ0ZyLz .taskTextOutsideRight.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}#mermaid-svg-w8x1zwBESUZ0ZyLz .taskText0,#mermaid-svg-w8x1zwBESUZ0ZyLz .taskText1,#mermaid-svg-w8x1zwBESUZ0ZyLz .taskText2,#mermaid-svg-w8x1zwBESUZ0ZyLz .taskText3{fill:#fff}#mermaid-svg-w8x1zwBESUZ0ZyLz .task0,#mermaid-svg-w8x1zwBESUZ0ZyLz .task1,#mermaid-svg-w8x1zwBESUZ0ZyLz .task2,#mermaid-svg-w8x1zwBESUZ0ZyLz .task3{fill:#8a90dd;stroke:#534fbc}#mermaid-svg-w8x1zwBESUZ0ZyLz .taskTextOutside0,#mermaid-svg-w8x1zwBESUZ0ZyLz .taskTextOutside2{fill:#000}#mermaid-svg-w8x1zwBESUZ0ZyLz .taskTextOutside1,#mermaid-svg-w8x1zwBESUZ0ZyLz .taskTextOutside3{fill:#000}#mermaid-svg-w8x1zwBESUZ0ZyLz .active0,#mermaid-svg-w8x1zwBESUZ0ZyLz .active1,#mermaid-svg-w8x1zwBESUZ0ZyLz .active2,#mermaid-svg-w8x1zwBESUZ0ZyLz .active3{fill:#bfc7ff;stroke:#534fbc}#mermaid-svg-w8x1zwBESUZ0ZyLz .activeText0,#mermaid-svg-w8x1zwBESUZ0ZyLz .activeText1,#mermaid-svg-w8x1zwBESUZ0ZyLz .activeText2,#mermaid-svg-w8x1zwBESUZ0ZyLz .activeText3{fill:#000 !important}#mermaid-svg-w8x1zwBESUZ0ZyLz .done0,#mermaid-svg-w8x1zwBESUZ0ZyLz .done1,#mermaid-svg-w8x1zwBESUZ0ZyLz .done2,#mermaid-svg-w8x1zwBESUZ0ZyLz .done3{stroke:grey;fill:#d3d3d3;stroke-width:2}#mermaid-svg-w8x1zwBESUZ0ZyLz .doneText0,#mermaid-svg-w8x1zwBESUZ0ZyLz .doneText1,#mermaid-svg-w8x1zwBESUZ0ZyLz .doneText2,#mermaid-svg-w8x1zwBESUZ0ZyLz .doneText3{fill:#000 !important}#mermaid-svg-w8x1zwBESUZ0ZyLz .crit0,#mermaid-svg-w8x1zwBESUZ0ZyLz .crit1,#mermaid-svg-w8x1zwBESUZ0ZyLz .crit2,#mermaid-svg-w8x1zwBESUZ0ZyLz .crit3{stroke:#f88;fill:red;stroke-width:2}#mermaid-svg-w8x1zwBESUZ0ZyLz .activeCrit0,#mermaid-svg-w8x1zwBESUZ0ZyLz .activeCrit1,#mermaid-svg-w8x1zwBESUZ0ZyLz .activeCrit2,#mermaid-svg-w8x1zwBESUZ0ZyLz .activeCrit3{stroke:#f88;fill:#bfc7ff;stroke-width:2}#mermaid-svg-w8x1zwBESUZ0ZyLz .doneCrit0,#mermaid-svg-w8x1zwBESUZ0ZyLz .doneCrit1,#mermaid-svg-w8x1zwBESUZ0ZyLz .doneCrit2,#mermaid-svg-w8x1zwBESUZ0ZyLz .doneCrit3{stroke:#f88;fill:#d3d3d3;stroke-width:2;cursor:pointer;shape-rendering:crispEdges}#mermaid-svg-w8x1zwBESUZ0ZyLz .milestone{transform:rotate(45deg) scale(0.8, 0.8)}#mermaid-svg-w8x1zwBESUZ0ZyLz .milestoneText{font-style:italic}#mermaid-svg-w8x1zwBESUZ0ZyLz .doneCritText0,#mermaid-svg-w8x1zwBESUZ0ZyLz .doneCritText1,#mermaid-svg-w8x1zwBESUZ0ZyLz .doneCritText2,#mermaid-svg-w8x1zwBESUZ0ZyLz .doneCritText3{fill:#000 !important}#mermaid-svg-w8x1zwBESUZ0ZyLz .activeCritText0,#mermaid-svg-w8x1zwBESUZ0ZyLz .activeCritText1,#mermaid-svg-w8x1zwBESUZ0ZyLz .activeCritText2,#mermaid-svg-w8x1zwBESUZ0ZyLz .activeCritText3{fill:#000 !important}#mermaid-svg-w8x1zwBESUZ0ZyLz .titleText{text-anchor:middle;font-size:18px;fill:#000;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-w8x1zwBESUZ0ZyLz g.classGroup text{fill:#9370db;stroke:none;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);font-size:10px}#mermaid-svg-w8x1zwBESUZ0ZyLz g.classGroup text .title{font-weight:bolder}#mermaid-svg-w8x1zwBESUZ0ZyLz g.clickable{cursor:pointer}#mermaid-svg-w8x1zwBESUZ0ZyLz g.classGroup rect{fill:#ECECFF;stroke:#9370db}#mermaid-svg-w8x1zwBESUZ0ZyLz g.classGroup line{stroke:#9370db;stroke-width:1}#mermaid-svg-w8x1zwBESUZ0ZyLz .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5}#mermaid-svg-w8x1zwBESUZ0ZyLz .classLabel .label{fill:#9370db;font-size:10px}#mermaid-svg-w8x1zwBESUZ0ZyLz .relation{stroke:#9370db;stroke-width:1;fill:none}#mermaid-svg-w8x1zwBESUZ0ZyLz .dashed-line{stroke-dasharray:3}#mermaid-svg-w8x1zwBESUZ0ZyLz #compositionStart{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-w8x1zwBESUZ0ZyLz #compositionEnd{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-w8x1zwBESUZ0ZyLz #aggregationStart{fill:#ECECFF;stroke:#9370db;stroke-width:1}#mermaid-svg-w8x1zwBESUZ0ZyLz #aggregationEnd{fill:#ECECFF;stroke:#9370db;stroke-width:1}#mermaid-svg-w8x1zwBESUZ0ZyLz #dependencyStart{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-w8x1zwBESUZ0ZyLz #dependencyEnd{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-w8x1zwBESUZ0ZyLz #extensionStart{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-w8x1zwBESUZ0ZyLz #extensionEnd{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-w8x1zwBESUZ0ZyLz .commit-id,#mermaid-svg-w8x1zwBESUZ0ZyLz .commit-msg,#mermaid-svg-w8x1zwBESUZ0ZyLz .branch-label{fill:lightgrey;color:lightgrey;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-w8x1zwBESUZ0ZyLz .pieTitleText{text-anchor:middle;font-size:25px;fill:#000;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-w8x1zwBESUZ0ZyLz .slice{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-w8x1zwBESUZ0ZyLz g.stateGroup text{fill:#9370db;stroke:none;font-size:10px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-w8x1zwBESUZ0ZyLz g.stateGroup text{fill:#9370db;fill:#333;stroke:none;font-size:10px}#mermaid-svg-w8x1zwBESUZ0ZyLz g.statediagram-cluster .cluster-label text{fill:#333}#mermaid-svg-w8x1zwBESUZ0ZyLz g.stateGroup .state-title{font-weight:bolder;fill:#000}#mermaid-svg-w8x1zwBESUZ0ZyLz g.stateGroup rect{fill:#ECECFF;stroke:#9370db}#mermaid-svg-w8x1zwBESUZ0ZyLz g.stateGroup line{stroke:#9370db;stroke-width:1}#mermaid-svg-w8x1zwBESUZ0ZyLz .transition{stroke:#9370db;stroke-width:1;fill:none}#mermaid-svg-w8x1zwBESUZ0ZyLz .stateGroup .composit{fill:white;border-bottom:1px}#mermaid-svg-w8x1zwBESUZ0ZyLz .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px}#mermaid-svg-w8x1zwBESUZ0ZyLz .state-note{stroke:#aa3;fill:#fff5ad}#mermaid-svg-w8x1zwBESUZ0ZyLz .state-note text{fill:black;stroke:none;font-size:10px}#mermaid-svg-w8x1zwBESUZ0ZyLz .stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.7}#mermaid-svg-w8x1zwBESUZ0ZyLz .edgeLabel text{fill:#333}#mermaid-svg-w8x1zwBESUZ0ZyLz .stateLabel text{fill:#000;font-size:10px;font-weight:bold;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-w8x1zwBESUZ0ZyLz .node circle.state-start{fill:black;stroke:black}#mermaid-svg-w8x1zwBESUZ0ZyLz .node circle.state-end{fill:black;stroke:white;stroke-width:1.5}#mermaid-svg-w8x1zwBESUZ0ZyLz #statediagram-barbEnd{fill:#9370db}#mermaid-svg-w8x1zwBESUZ0ZyLz .statediagram-cluster rect{fill:#ECECFF;stroke:#9370db;stroke-width:1px}#mermaid-svg-w8x1zwBESUZ0ZyLz .statediagram-cluster rect.outer{rx:5px;ry:5px}#mermaid-svg-w8x1zwBESUZ0ZyLz .statediagram-state .divider{stroke:#9370db}#mermaid-svg-w8x1zwBESUZ0ZyLz .statediagram-state .title-state{rx:5px;ry:5px}#mermaid-svg-w8x1zwBESUZ0ZyLz .statediagram-cluster.statediagram-cluster .inner{fill:white}#mermaid-svg-w8x1zwBESUZ0ZyLz .statediagram-cluster.statediagram-cluster-alt .inner{fill:#e0e0e0}#mermaid-svg-w8x1zwBESUZ0ZyLz .statediagram-cluster .inner{rx:0;ry:0}#mermaid-svg-w8x1zwBESUZ0ZyLz .statediagram-state rect.basic{rx:5px;ry:5px}#mermaid-svg-w8x1zwBESUZ0ZyLz .statediagram-state rect.divider{stroke-dasharray:10,10;fill:#efefef}#mermaid-svg-w8x1zwBESUZ0ZyLz .note-edge{stroke-dasharray:5}#mermaid-svg-w8x1zwBESUZ0ZyLz .statediagram-note rect{fill:#fff5ad;stroke:#aa3;stroke-width:1px;rx:0;ry:0}:root{--mermaid-font-family: '"trebuchet ms", verdana, arial';--mermaid-font-family: "Comic Sans MS", "Comic Sans", cursive}#mermaid-svg-w8x1zwBESUZ0ZyLz .error-icon{fill:#522}#mermaid-svg-w8x1zwBESUZ0ZyLz .error-text{fill:#522;stroke:#522}#mermaid-svg-w8x1zwBESUZ0ZyLz .edge-thickness-normal{stroke-width:2px}#mermaid-svg-w8x1zwBESUZ0ZyLz .edge-thickness-thick{stroke-width:3.5px}#mermaid-svg-w8x1zwBESUZ0ZyLz .edge-pattern-solid{stroke-dasharray:0}#mermaid-svg-w8x1zwBESUZ0ZyLz .edge-pattern-dashed{stroke-dasharray:3}#mermaid-svg-w8x1zwBESUZ0ZyLz .edge-pattern-dotted{stroke-dasharray:2}#mermaid-svg-w8x1zwBESUZ0ZyLz .marker{fill:#333}#mermaid-svg-w8x1zwBESUZ0ZyLz .marker.cross{stroke:#333} :root { --mermaid-font-family: "trebuchet ms", verdana, arial;}</style> <style>#mermaid-svg-w8x1zwBESUZ0ZyLz { color: rgba(0, 0, 0, 0.75); font: ; }</style>
Handler學習大綱
1:定義
2:作用
3:為什么要使用Handler訊息傳遞機制
4:Handler必須掌握的相關概念
5:作業原理和原始碼決議
6:Handler的延伸
7:總結

一:Handler的定義

Handler的定義我覺得可以用一句話高度概括,那就是:Handler是Android提供的一套完整的訊息傳遞機制,

二:Handler的作用

我們都知道,在Android種,一般情況下View是不能在作業執行緒更新的,除了surfaceview(特殊),那么如果我們非要在作業執行緒中更新UI那該怎么辦了,那只能借助handler了,所以handler的作用就是:在多執行緒的應用場景中,將作業執行緒中需更新UI的操作資訊傳遞到UI主執行緒,從而間接的實作作業執行緒對UI的更新處理,最終實作異步訊息的處理,

<style>#mermaid-svg-Cc4j4wHdWmhpaoFd .label{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);fill:#333;color:#333}#mermaid-svg-Cc4j4wHdWmhpaoFd .label text{fill:#333}#mermaid-svg-Cc4j4wHdWmhpaoFd .node rect,#mermaid-svg-Cc4j4wHdWmhpaoFd .node circle,#mermaid-svg-Cc4j4wHdWmhpaoFd .node ellipse,#mermaid-svg-Cc4j4wHdWmhpaoFd .node polygon,#mermaid-svg-Cc4j4wHdWmhpaoFd .node path{fill:#ECECFF;stroke:#9370db;stroke-width:1px}#mermaid-svg-Cc4j4wHdWmhpaoFd .node .label{text-align:center;fill:#333}#mermaid-svg-Cc4j4wHdWmhpaoFd .node.clickable{cursor:pointer}#mermaid-svg-Cc4j4wHdWmhpaoFd .arrowheadPath{fill:#333}#mermaid-svg-Cc4j4wHdWmhpaoFd .edgePath .path{stroke:#333;stroke-width:1.5px}#mermaid-svg-Cc4j4wHdWmhpaoFd .flowchart-link{stroke:#333;fill:none}#mermaid-svg-Cc4j4wHdWmhpaoFd .edgeLabel{background-color:#e8e8e8;text-align:center}#mermaid-svg-Cc4j4wHdWmhpaoFd .edgeLabel rect{opacity:0.9}#mermaid-svg-Cc4j4wHdWmhpaoFd .edgeLabel span{color:#333}#mermaid-svg-Cc4j4wHdWmhpaoFd .cluster rect{fill:#ffffde;stroke:#aa3;stroke-width:1px}#mermaid-svg-Cc4j4wHdWmhpaoFd .cluster text{fill:#333}#mermaid-svg-Cc4j4wHdWmhpaoFd div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);font-size:12px;background:#ffffde;border:1px solid #aa3;border-radius:2px;pointer-events:none;z-index:100}#mermaid-svg-Cc4j4wHdWmhpaoFd .actor{stroke:#ccf;fill:#ECECFF}#mermaid-svg-Cc4j4wHdWmhpaoFd text.actor>tspan{fill:#000;stroke:none}#mermaid-svg-Cc4j4wHdWmhpaoFd .actor-line{stroke:grey}#mermaid-svg-Cc4j4wHdWmhpaoFd .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333}#mermaid-svg-Cc4j4wHdWmhpaoFd .messageLine1{stroke-width:1.5;stroke-dasharray:2, 2;stroke:#333}#mermaid-svg-Cc4j4wHdWmhpaoFd #arrowhead path{fill:#333;stroke:#333}#mermaid-svg-Cc4j4wHdWmhpaoFd .sequenceNumber{fill:#fff}#mermaid-svg-Cc4j4wHdWmhpaoFd #sequencenumber{fill:#333}#mermaid-svg-Cc4j4wHdWmhpaoFd #crosshead path{fill:#333;stroke:#333}#mermaid-svg-Cc4j4wHdWmhpaoFd .messageText{fill:#333;stroke:#333}#mermaid-svg-Cc4j4wHdWmhpaoFd .labelBox{stroke:#ccf;fill:#ECECFF}#mermaid-svg-Cc4j4wHdWmhpaoFd .labelText,#mermaid-svg-Cc4j4wHdWmhpaoFd .labelText>tspan{fill:#000;stroke:none}#mermaid-svg-Cc4j4wHdWmhpaoFd .loopText,#mermaid-svg-Cc4j4wHdWmhpaoFd .loopText>tspan{fill:#000;stroke:none}#mermaid-svg-Cc4j4wHdWmhpaoFd .loopLine{stroke-width:2px;stroke-dasharray:2, 2;stroke:#ccf;fill:#ccf}#mermaid-svg-Cc4j4wHdWmhpaoFd .note{stroke:#aa3;fill:#fff5ad}#mermaid-svg-Cc4j4wHdWmhpaoFd .noteText,#mermaid-svg-Cc4j4wHdWmhpaoFd .noteText>tspan{fill:#000;stroke:none}#mermaid-svg-Cc4j4wHdWmhpaoFd .activation0{fill:#f4f4f4;stroke:#666}#mermaid-svg-Cc4j4wHdWmhpaoFd .activation1{fill:#f4f4f4;stroke:#666}#mermaid-svg-Cc4j4wHdWmhpaoFd .activation2{fill:#f4f4f4;stroke:#666}#mermaid-svg-Cc4j4wHdWmhpaoFd .mermaid-main-font{font-family:"trebuchet ms", verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-Cc4j4wHdWmhpaoFd .section{stroke:none;opacity:0.2}#mermaid-svg-Cc4j4wHdWmhpaoFd .section0{fill:rgba(102,102,255,0.49)}#mermaid-svg-Cc4j4wHdWmhpaoFd .section2{fill:#fff400}#mermaid-svg-Cc4j4wHdWmhpaoFd .section1,#mermaid-svg-Cc4j4wHdWmhpaoFd .section3{fill:#fff;opacity:0.2}#mermaid-svg-Cc4j4wHdWmhpaoFd .sectionTitle0{fill:#333}#mermaid-svg-Cc4j4wHdWmhpaoFd .sectionTitle1{fill:#333}#mermaid-svg-Cc4j4wHdWmhpaoFd .sectionTitle2{fill:#333}#mermaid-svg-Cc4j4wHdWmhpaoFd .sectionTitle3{fill:#333}#mermaid-svg-Cc4j4wHdWmhpaoFd .sectionTitle{text-anchor:start;font-size:11px;text-height:14px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-Cc4j4wHdWmhpaoFd .grid .tick{stroke:#d3d3d3;opacity:0.8;shape-rendering:crispEdges}#mermaid-svg-Cc4j4wHdWmhpaoFd .grid .tick text{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-Cc4j4wHdWmhpaoFd .grid path{stroke-width:0}#mermaid-svg-Cc4j4wHdWmhpaoFd .today{fill:none;stroke:red;stroke-width:2px}#mermaid-svg-Cc4j4wHdWmhpaoFd .task{stroke-width:2}#mermaid-svg-Cc4j4wHdWmhpaoFd .taskText{text-anchor:middle;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-Cc4j4wHdWmhpaoFd .taskText:not([font-size]){font-size:11px}#mermaid-svg-Cc4j4wHdWmhpaoFd .taskTextOutsideRight{fill:#000;text-anchor:start;font-size:11px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-Cc4j4wHdWmhpaoFd .taskTextOutsideLeft{fill:#000;text-anchor:end;font-size:11px}#mermaid-svg-Cc4j4wHdWmhpaoFd .task.clickable{cursor:pointer}#mermaid-svg-Cc4j4wHdWmhpaoFd .taskText.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}#mermaid-svg-Cc4j4wHdWmhpaoFd .taskTextOutsideLeft.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}#mermaid-svg-Cc4j4wHdWmhpaoFd .taskTextOutsideRight.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}#mermaid-svg-Cc4j4wHdWmhpaoFd .taskText0,#mermaid-svg-Cc4j4wHdWmhpaoFd .taskText1,#mermaid-svg-Cc4j4wHdWmhpaoFd .taskText2,#mermaid-svg-Cc4j4wHdWmhpaoFd .taskText3{fill:#fff}#mermaid-svg-Cc4j4wHdWmhpaoFd .task0,#mermaid-svg-Cc4j4wHdWmhpaoFd .task1,#mermaid-svg-Cc4j4wHdWmhpaoFd .task2,#mermaid-svg-Cc4j4wHdWmhpaoFd .task3{fill:#8a90dd;stroke:#534fbc}#mermaid-svg-Cc4j4wHdWmhpaoFd .taskTextOutside0,#mermaid-svg-Cc4j4wHdWmhpaoFd .taskTextOutside2{fill:#000}#mermaid-svg-Cc4j4wHdWmhpaoFd .taskTextOutside1,#mermaid-svg-Cc4j4wHdWmhpaoFd .taskTextOutside3{fill:#000}#mermaid-svg-Cc4j4wHdWmhpaoFd .active0,#mermaid-svg-Cc4j4wHdWmhpaoFd .active1,#mermaid-svg-Cc4j4wHdWmhpaoFd .active2,#mermaid-svg-Cc4j4wHdWmhpaoFd .active3{fill:#bfc7ff;stroke:#534fbc}#mermaid-svg-Cc4j4wHdWmhpaoFd .activeText0,#mermaid-svg-Cc4j4wHdWmhpaoFd .activeText1,#mermaid-svg-Cc4j4wHdWmhpaoFd .activeText2,#mermaid-svg-Cc4j4wHdWmhpaoFd .activeText3{fill:#000 !important}#mermaid-svg-Cc4j4wHdWmhpaoFd .done0,#mermaid-svg-Cc4j4wHdWmhpaoFd .done1,#mermaid-svg-Cc4j4wHdWmhpaoFd .done2,#mermaid-svg-Cc4j4wHdWmhpaoFd .done3{stroke:grey;fill:#d3d3d3;stroke-width:2}#mermaid-svg-Cc4j4wHdWmhpaoFd .doneText0,#mermaid-svg-Cc4j4wHdWmhpaoFd .doneText1,#mermaid-svg-Cc4j4wHdWmhpaoFd .doneText2,#mermaid-svg-Cc4j4wHdWmhpaoFd .doneText3{fill:#000 !important}#mermaid-svg-Cc4j4wHdWmhpaoFd .crit0,#mermaid-svg-Cc4j4wHdWmhpaoFd .crit1,#mermaid-svg-Cc4j4wHdWmhpaoFd .crit2,#mermaid-svg-Cc4j4wHdWmhpaoFd .crit3{stroke:#f88;fill:red;stroke-width:2}#mermaid-svg-Cc4j4wHdWmhpaoFd .activeCrit0,#mermaid-svg-Cc4j4wHdWmhpaoFd .activeCrit1,#mermaid-svg-Cc4j4wHdWmhpaoFd .activeCrit2,#mermaid-svg-Cc4j4wHdWmhpaoFd .activeCrit3{stroke:#f88;fill:#bfc7ff;stroke-width:2}#mermaid-svg-Cc4j4wHdWmhpaoFd .doneCrit0,#mermaid-svg-Cc4j4wHdWmhpaoFd .doneCrit1,#mermaid-svg-Cc4j4wHdWmhpaoFd .doneCrit2,#mermaid-svg-Cc4j4wHdWmhpaoFd .doneCrit3{stroke:#f88;fill:#d3d3d3;stroke-width:2;cursor:pointer;shape-rendering:crispEdges}#mermaid-svg-Cc4j4wHdWmhpaoFd .milestone{transform:rotate(45deg) scale(0.8, 0.8)}#mermaid-svg-Cc4j4wHdWmhpaoFd .milestoneText{font-style:italic}#mermaid-svg-Cc4j4wHdWmhpaoFd .doneCritText0,#mermaid-svg-Cc4j4wHdWmhpaoFd .doneCritText1,#mermaid-svg-Cc4j4wHdWmhpaoFd .doneCritText2,#mermaid-svg-Cc4j4wHdWmhpaoFd .doneCritText3{fill:#000 !important}#mermaid-svg-Cc4j4wHdWmhpaoFd .activeCritText0,#mermaid-svg-Cc4j4wHdWmhpaoFd .activeCritText1,#mermaid-svg-Cc4j4wHdWmhpaoFd .activeCritText2,#mermaid-svg-Cc4j4wHdWmhpaoFd .activeCritText3{fill:#000 !important}#mermaid-svg-Cc4j4wHdWmhpaoFd .titleText{text-anchor:middle;font-size:18px;fill:#000;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-Cc4j4wHdWmhpaoFd g.classGroup text{fill:#9370db;stroke:none;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);font-size:10px}#mermaid-svg-Cc4j4wHdWmhpaoFd g.classGroup text .title{font-weight:bolder}#mermaid-svg-Cc4j4wHdWmhpaoFd g.clickable{cursor:pointer}#mermaid-svg-Cc4j4wHdWmhpaoFd g.classGroup rect{fill:#ECECFF;stroke:#9370db}#mermaid-svg-Cc4j4wHdWmhpaoFd g.classGroup line{stroke:#9370db;stroke-width:1}#mermaid-svg-Cc4j4wHdWmhpaoFd .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5}#mermaid-svg-Cc4j4wHdWmhpaoFd .classLabel .label{fill:#9370db;font-size:10px}#mermaid-svg-Cc4j4wHdWmhpaoFd .relation{stroke:#9370db;stroke-width:1;fill:none}#mermaid-svg-Cc4j4wHdWmhpaoFd .dashed-line{stroke-dasharray:3}#mermaid-svg-Cc4j4wHdWmhpaoFd #compositionStart{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-Cc4j4wHdWmhpaoFd #compositionEnd{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-Cc4j4wHdWmhpaoFd #aggregationStart{fill:#ECECFF;stroke:#9370db;stroke-width:1}#mermaid-svg-Cc4j4wHdWmhpaoFd #aggregationEnd{fill:#ECECFF;stroke:#9370db;stroke-width:1}#mermaid-svg-Cc4j4wHdWmhpaoFd #dependencyStart{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-Cc4j4wHdWmhpaoFd #dependencyEnd{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-Cc4j4wHdWmhpaoFd #extensionStart{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-Cc4j4wHdWmhpaoFd #extensionEnd{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-Cc4j4wHdWmhpaoFd .commit-id,#mermaid-svg-Cc4j4wHdWmhpaoFd .commit-msg,#mermaid-svg-Cc4j4wHdWmhpaoFd .branch-label{fill:lightgrey;color:lightgrey;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-Cc4j4wHdWmhpaoFd .pieTitleText{text-anchor:middle;font-size:25px;fill:#000;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-Cc4j4wHdWmhpaoFd .slice{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-Cc4j4wHdWmhpaoFd g.stateGroup text{fill:#9370db;stroke:none;font-size:10px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-Cc4j4wHdWmhpaoFd g.stateGroup text{fill:#9370db;fill:#333;stroke:none;font-size:10px}#mermaid-svg-Cc4j4wHdWmhpaoFd g.statediagram-cluster .cluster-label text{fill:#333}#mermaid-svg-Cc4j4wHdWmhpaoFd g.stateGroup .state-title{font-weight:bolder;fill:#000}#mermaid-svg-Cc4j4wHdWmhpaoFd g.stateGroup rect{fill:#ECECFF;stroke:#9370db}#mermaid-svg-Cc4j4wHdWmhpaoFd g.stateGroup line{stroke:#9370db;stroke-width:1}#mermaid-svg-Cc4j4wHdWmhpaoFd .transition{stroke:#9370db;stroke-width:1;fill:none}#mermaid-svg-Cc4j4wHdWmhpaoFd .stateGroup .composit{fill:white;border-bottom:1px}#mermaid-svg-Cc4j4wHdWmhpaoFd .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px}#mermaid-svg-Cc4j4wHdWmhpaoFd .state-note{stroke:#aa3;fill:#fff5ad}#mermaid-svg-Cc4j4wHdWmhpaoFd .state-note text{fill:black;stroke:none;font-size:10px}#mermaid-svg-Cc4j4wHdWmhpaoFd .stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.7}#mermaid-svg-Cc4j4wHdWmhpaoFd .edgeLabel text{fill:#333}#mermaid-svg-Cc4j4wHdWmhpaoFd .stateLabel text{fill:#000;font-size:10px;font-weight:bold;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-Cc4j4wHdWmhpaoFd .node circle.state-start{fill:black;stroke:black}#mermaid-svg-Cc4j4wHdWmhpaoFd .node circle.state-end{fill:black;stroke:white;stroke-width:1.5}#mermaid-svg-Cc4j4wHdWmhpaoFd #statediagram-barbEnd{fill:#9370db}#mermaid-svg-Cc4j4wHdWmhpaoFd .statediagram-cluster rect{fill:#ECECFF;stroke:#9370db;stroke-width:1px}#mermaid-svg-Cc4j4wHdWmhpaoFd .statediagram-cluster rect.outer{rx:5px;ry:5px}#mermaid-svg-Cc4j4wHdWmhpaoFd .statediagram-state .divider{stroke:#9370db}#mermaid-svg-Cc4j4wHdWmhpaoFd .statediagram-state .title-state{rx:5px;ry:5px}#mermaid-svg-Cc4j4wHdWmhpaoFd .statediagram-cluster.statediagram-cluster .inner{fill:white}#mermaid-svg-Cc4j4wHdWmhpaoFd .statediagram-cluster.statediagram-cluster-alt .inner{fill:#e0e0e0}#mermaid-svg-Cc4j4wHdWmhpaoFd .statediagram-cluster .inner{rx:0;ry:0}#mermaid-svg-Cc4j4wHdWmhpaoFd .statediagram-state rect.basic{rx:5px;ry:5px}#mermaid-svg-Cc4j4wHdWmhpaoFd .statediagram-state rect.divider{stroke-dasharray:10,10;fill:#efefef}#mermaid-svg-Cc4j4wHdWmhpaoFd .note-edge{stroke-dasharray:5}#mermaid-svg-Cc4j4wHdWmhpaoFd .statediagram-note rect{fill:#fff5ad;stroke:#aa3;stroke-width:1px;rx:0;ry:0}:root{--mermaid-font-family: '"trebuchet ms", verdana, arial';--mermaid-font-family: "Comic Sans MS", "Comic Sans", cursive}#mermaid-svg-Cc4j4wHdWmhpaoFd .error-icon{fill:#522}#mermaid-svg-Cc4j4wHdWmhpaoFd .error-text{fill:#522;stroke:#522}#mermaid-svg-Cc4j4wHdWmhpaoFd .edge-thickness-normal{stroke-width:2px}#mermaid-svg-Cc4j4wHdWmhpaoFd .edge-thickness-thick{stroke-width:3.5px}#mermaid-svg-Cc4j4wHdWmhpaoFd .edge-pattern-solid{stroke-dasharray:0}#mermaid-svg-Cc4j4wHdWmhpaoFd .edge-pattern-dashed{stroke-dasharray:3}#mermaid-svg-Cc4j4wHdWmhpaoFd .edge-pattern-dotted{stroke-dasharray:2}#mermaid-svg-Cc4j4wHdWmhpaoFd .marker{fill:#333}#mermaid-svg-Cc4j4wHdWmhpaoFd .marker.cross{stroke:#333} :root { --mermaid-font-family: "trebuchet ms", verdana, arial;}</style> <style>#mermaid-svg-Cc4j4wHdWmhpaoFd { color: rgba(0, 0, 0, 0.75); font: ; }</style>
更新UI的相關資訊
傳遞作業執行緒更新UI的相關資訊
在主執行緒更新UI
作業執行緒
Handler
主執行緒
更新結束

三:為什么要使用Handler訊息傳遞機制

我們既然知道了使用handler可以間接實作作業執行緒對UI的更新,那你是否奇怪,為什么更新非要使用Handler,而不能是其它,那是因為,多個執行緒并發更新UI的同時要保證執行緒安全,詳細描述見下表(圖片來自:https://www.jianshu.com/p/f0b23ee5a922)
圖片來自:https://www.jianshu.com/p/f0b23ee5a922

四:Handler必須掌握的相關概念

關于Handler的先關概念主要包括如下4大點,即 HandlerMessageMessage QueueLooper,希望大家先熟悉相關概念,下圖大致高度介紹相關概念:(圖片來自:https://www.jianshu.com/p/f0b23ee5a922)
在這里插入圖片描述

五:Handler作業原理和原始碼分析

在第四步我們主要明白了幾個相關概念,接下來,會深層次的揭破他們的原始碼,從原始碼里搞清他們之間的作業關系和作業原理,

5.1:Handler Looper Message 關系是什么?

5.1.1分析Handler

首先我們來分析分析一下 Handler 的用法,我們知道,要創建一個 Handler 物件
非常的簡單明了,直接進行 new 一個物件即可,但是這里會隱
藏著什么注意點呢,現在可以試著寫一下下面的一小段代碼,然后自己運行看看:

public class MainActivity extends AppCompatActivity{
private Handler mHandler0;
private Handler mHandler1;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mHandler0 = new Handler();
new Thread(new Runnable() {
@Override
public void run() {
mHandler1 = new Handler();
}
}).start();
}

這一小段程式代碼主要創建了兩個 Handler 物件,其中,一個在主執行緒中創建,
而另外一個則在子執行緒中創建,現在運行一下程式,則你會發現,在子執行緒創建
的Handler物件竟然會導致程式直接崩潰,奔潰例外資訊如下:

2021-07-23 12:06:19.034 14393-14451/com.pcl.lpr.debug E/AndroidRuntime: FATAL EXCEPTION: Thread-10
    Process: com.pcl.lpr.debug, PID: 14393
    java.lang.RuntimeException: Can't create handler inside thread Thread[Thread-10,5,main] that has not called Looper.prepare()
        at android.os.Handler.<init>(Handler.java:207)
        at android.os.Handler.<init>(Handler.java:119)
        at com.pcl.ocr.ui.TestActivity$1.run(TestActivity.java:23)
        at java.lang.Thread.run(Thread.java:919)

提示的錯誤竟然是:Can't create handler inside thread that has not called Looper.prepare()
于是我們按照 logcat 中所說,在子執行緒中加入 Looper.prepare(),即代碼如下:

public class TestActivity extends AppCompatActivity {
    private Handler mHandler0;
    private Handler mHandler1;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);
        mHandler0 = new Handler();
        new Thread(new Runnable() {
            @Override
            public void run() {
                Looper.prepare();
                mHandler1 = new Handler();
            }
        }).start();
        
    }
}

再次運行一下程式,發現程式不會再崩潰了,可是,單單只加這句Looper.prepare()是否就能解決問題了,我們探討問題,就要知其然,才能了解得更多,我們還是先分析一下原始碼吧,看看為什么在子執行緒中沒有加Looper.prepare()就會出現崩潰,而主執行緒中為什么不用加這句代碼?我們看下Handler()建構式:

public Handler() {
this(null, false);
}

建構式非常簡單,直接呼叫 this(null, false),于是接著看其呼叫的函式:

public Handler(Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || kla
ss.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static o
r leaks might occur: " +
klass.getCanonicalName());
 }
}

mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Lo
oper.prepare()");
}
mQueue = mLooper.mQueue;mCallback = callback;
mAsynchronous = async;
}

不難看出,原始碼中呼叫了 mLooper = Looper.myLooper()方法獲取一個 Looper物件,若此時 Looper 物件為 null,則會直接拋出一個“Can't create handlerinside thread that has not called Looper.prepare()”例外,那什么時候造成 mLooper 是為空呢?那就接著分析 Looper.myLooper()

public static Looper myLooper() {
  return sThreadLocal.get();
}

這個方法在 sThreadLocal 變數中直接取出 Looper 物件,若 sThreadLocal 變數中存在 Looper 物件,則直接回傳,若不存在,則直接回傳 null,而 sThreadLocal變數是什么呢?

static final ThreadLocat<Looper> sThreadLocal = new ThreadLocal<Looper>();

它是本地執行緒變數,存放在 Looper 物件,由這也可看出,每個執行緒只有存有一個 Looper 物件,可是,是在哪里給 sThreadLocal 設定 Looper 的呢,通過前面的試驗,我們不難猜到,應該是在Looper.prepare()方法中,現在來看看它的原始碼:

private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
    throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));}

由此看到,我們的判斷是正確的,在 Looper.prepare()方法中給 sThreadLocal變數設定 Looper 物件,這樣也就理解了為什么要先呼叫 Looper.prepare()方法,才能創建 Handler 物件,才不會導致崩潰,但是,仔細想想,為什么主執行緒就不用呼叫呢?不要急,我們接著分析一下主執行緒,我們查看一下ActivityThread中的 main()方法,代碼如下:

public static void main(String[] args) {
		SamplingProfilerIntegration.start();// CloseGuard defaults to true and can be quite spammy
		// disable it here, but selectively enable it later (via
		// StrictMode) on debug builds, but using DropBox, not logs.
		CloseGuard.setEnabled(false);
		Environment.initForCurrentUser();
		
		// Set the reporter for event logging in libcore
		EventLogger.setReporter(new EventLoggingReporter());
		Security.addProvider(new AndroidKeyStoreProvider());
		
		// Make sure TrustedCertificateStore looks in the right place for CAcertificates
		final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
		TrustedCertificateStore.setDefaultUserDirectory(configDir);
		Process.setArgV0("<pre-initialized>");
		Looper.prepareMainLooper();
		
		ActivityThread thread = new ActivityThread();thread.attach(false);
		if (sMainThreadHandler == null) {
	    	sMainThreadHandler = thread.getHandler();
		}
		
		if (false) {
		   Looper.myLooper().setMessageLogging(new
		   LogPrinter(Log.DEBUG, "ActivityThread"));
		}
		
		Looper.loop();
		throw new RuntimeException("Main thread loop unexpectedly exited");
}

代碼中呼叫了 Looper.prepareMainLooper()方法,而這個方法又會繼續呼叫了Looper.prepare()方法,代碼如下:

public static void prepareMainLooper() {
	prepare(false);
	synchronized (Looper.class) {
	if (sMainLooper != null) {
	  throw new IllegalStateException("The main Looper has already been prepared.");
	}	
	sMainLooper = myLooper();
}}

分析到這里已經真相大白,主執行緒中 google 工程師已經自動幫我們創建了一個Looper 物件了,因而我們不再需要手動再呼叫 Looper.prepare()再創建,而子執行緒中,因為沒有自動幫我們創建 Looper 物件,因此需要我們手動添加,呼叫方法是 Looper.prepare(),這樣,我們才能正確地創建 Handler 物件,

5.1.2發送訊息

當我們正確的創建 Handler 物件后,接下來我們來了解一下怎么發送訊息,有一點基礎的朋友肯定對這個方法已經了如指掌了,具體是先創建出一個 Message物件,然后可以利用一些方法,如 setData()或者使用 arg 引數等方式來存放資料于訊息中,再借助 Handler 物件將訊息發送出去就可以了,

new Thread(new Runnable() {
	@Override
	public void run() {
		Message msg = Message.obtain();
		msg.arg1 = 1;
		msg.arg2 = 2;
		Bundle bundle = new Bundle();
		bundle.putChar("key", 'v');
		bundle.putString("key","value");
		msg.setData(bundle);
		mHandler0.sendMessage(msg);
		}
	}).start();

通過 Message 物件進行傳遞訊息,在訊息中添加各種資料,之后訊息通過mHandler0 進行傳遞,之后我們再利用 Handler 中的 handleMessage()方法將此時傳遞的 Message 進行捕獲出來,再分析得到存盤在 msg 中的資料,但是,這個流程到底是怎么樣的呢?具體我們還是來分析一下原始碼,首先分析一下發送方法sendMessage():

public final boolean sendMessage(Message msg){
   return sendMessageDelayed(msg, 0);
}

通過呼叫 sendMessageDelayed(msg, 0)方法:

public final boolean sendMessageDelayed(Message msg, long delayMillis){
	if (delayMillis < 0) {
    	delayMillis = 0;
	}
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

再能過呼叫 sendMessageDelayed(Message msg, long delayMillis),方法中第一個引數是指發送的訊息 msg,第二個引數是指延遲多少毫秒發送,我們著重看一下此方法:

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
   MessageQueue queue = mQueue;
		if (queue == null) {
			RuntimeException e = new RuntimeException(
			this + " sendMessageAtTime() called with no mQueue");
			Log.w("Looper", e.getMessage(), e);
			return false;
		}
    return enqueueMessage(queue, msg, uptimeMillis);
  }

由這里可以分析得出,原來訊息 Message 物件是建立一個訊息佇列 MessageQueue, 這個物件MessageQueuemQueue 賦值,而由原始碼分析得出 mQueue =mLooper.mQueue,而 mLooper 則是 Looper 物件,我們由上面已經知道,每個執行緒只有一個 Looper,因此,一個 Looper 也就對應了一個 MessageQueue 物件,之后呼叫 enqueueMessage(queue, msg, uptimeMillis)直接入隊操作:

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
	msg.target = this;
	if (mAsynchronous) {
		msg.setAsynchronous(true);
		}
	return queue.enqueueMessage(msg, uptimeMillis);
}

方 法 通 過 調 用 MessageQueueenqueueMessage(Message msg, long uptimeMills)方法:

boolean enqueueMessage(Message msg, long when) {
	if (msg.target == null) {
    	throw new IllegalArgumentException("Message must have a target.");
	}
	
	if (msg.isInUse()) {
       throw new IllegalStateException(msg + " This message is already in use.");
	}


	synchronized (this) {
		if (mQuitting) {
		  IllegalStateException e = new IllegalStateException(msg.target + " sending message to a Handler on a dead thread");
		  Log.w("MessageQueue", e.getMessage(), e);msg.recycle();
		   return false;
		}
	msg.markInUse();
	msg.when = when;
	Message p = mMessages;
    boolean needWake;
    
	if (p == null || when == 0 || when < p.when) {
		// New head, wake up the event queue if blocked.
		msg.next = p;
		mMessages = msg;
		needWake = mBlocked;
	}else {
		// Inserted within the middle of the queue. Usually we don't have to wake
		// up the event queue unless there is a barrier at the head of the queue
		// and the message is the earliest asynchronous message in the queue.
        needWake = mBlocked && p.target == null && msg.isAsynchronous();
        Message prev;
		for (;;) {
			prev = p;p = p.next;			
			if (p == null || when < p.when) {
			    break;
			}
			
			if (needWake && p.isAsynchronous()) {
		    	needWake = false;
			}
        }
		msg.next = p; // invariant: p == prev.next
		prev.next = msg;
	}
	   // We can assume mPtr != 0 because mQuitting is false.
		if (needWake) {
		nativeWake(mPtr);
		}
	}
   return true;
}

首先要知道,原始碼中用 mMessages 代表當前等待處理的訊息,MessageQueue 也沒有使用一個集合保存所有的訊息,觀察中間的代碼部分,佇列中根據時間 when來時間排序,這個時間也就是我們傳進來延遲的時間 uptimeMills 引數,之后再根據時間的順序呼叫 msg.next,從而指定下一個將要處理的訊息是什么,如果只是通過 sendMessageAtFrontOfQueue()方法來發送訊息

public final boolean sendMessageAtFrontOfQueue(Message msg) {
	MessageQueue queue = mQueue;
	if (queue == null) {
		RuntimeException e = new RuntimeException(
		this + " sendMessageAtTime() called with no mQueue");
		Log.w("Looper", e.getMessage(), e);
		return false;
	}
    return enqueueMessage(queue, msg, 0);
}

它也是直接呼叫 enqueueMessage()進行入隊,但沒有延遲時間,此時會將傳遞的此訊息直接添加到隊頭處,現在入隊操作已經了解得差不多了,接下來應該來了解一下出隊操作,那么出隊在哪里進行的呢,不要忘記 MessageQueue 物件是在 Looper 中賦值,因此我們可以在 Looper 類中找,來看一看Looper.loop()方法:

public static void loop() {
    final Looper me = myLooper();
	if (me == null) {
	   throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
	}
   final MessageQueue queue = me.mQueue;
   // Make sure the identity of this thread is that of the local process,
   // and keep track of what that identity token actually is.
   Binder.clearCallingIdentity();
   final long ident = Binder.clearCallingIdentity();
   for (;;) {
        Message msg = queue.next(); // might block
		if (msg == null) {
		// No message indicates that the message queue is quitting.
		    return;
	   }
     // This must be in a local variable, in case a UI event sets the logger
     Printer logging = me.mLogging;
	if (logging != null) {
	   logging.println(">>Dispatching to " + msg.target + " " +msg.callback + ": " + msg.what);
	}
    msg.target.dispatchMessage(msg);
	if (logging != null) {
	   logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
	}
  // Make sure that during the course of dispatching the
  // identity of the thread wasn't corrupted.final long newIdent = Binder.clearCallingIdentity();
	if (ident != newIdent) {
		  Log.wtf(TAG, "Thread identity changed from 0x"+ Long.toHexString(ident) + " to 0x"+   Long.toHexString(newIdent) + " while dispatching to"+ msg.target.getClass().getName() + " "+ msg.callback + " what=" + msg.what);
	}
   msg.recycleUnchecked();

}
}

代碼比較多,我們只挑重要的分析一下,我們可以看到下面的代碼用 for(;;)進入了一個死回圈,之后不斷的從 MessageQueue 物件 queue 中取出訊息 msg,而我們不難知道,此時的 next()就是進行佇列的出隊方法,next()方法代碼有點長,有興趣的話可以自行翻閱查看,主要邏輯是判斷當前的 MessageQueue 是否存在待處理的 mMessages 訊息,如果有,則將這個訊息出隊,然后讓下一個消成為 mMessages,否則就進入一個阻塞狀態,一直等到有新的訊息入隊喚醒,回看 loop()方法,可以發現當執行 next()方法后會執行msg.target.dispatchMessage(msg)方法,而不難看出,此時 msg.target 就是Handler 物件,繼續看一下 dispatchMessage()方法:

public void dispatchMessage(Message msg) {
	if (msg.callback != null) {
	    handleCallback(msg);
	} else {
    	if (mCallback != null) {
	    	if (mCallback.handleMessage(msg)) {
		      return;
		    }
	    }
	   handleMessage(msg);
	}
}

先進行判斷 mCallback 是否為空,若不為空則呼叫 mCallbackhandleMessage()方法,否則直接呼叫 handleMessage()方法,并將訊息作為引數傳出去,這樣我們就完全一目了然,為什么我們要使用handleMessage()來捕獲我們之前傳遞過去的資訊,現在我們根據上面的理解,不難寫出異步訊息處理機制的執行緒了,

class myThread extends Thread{
	public Handler myHandler;
	@Override
	public void run() {
		Looper.prepare();
		myHandler = new Handler(){
			@Override
			public void handleMessage(Message msg) {
			super.handleMessage(msg);
			//處理訊息
			}
		};
	   Looper.loop();
	}
}

當然除了發送訊息外,還有以下幾個方法可以在子執行緒中進行 UI 操作:

  1. View 的 post()方法
  2. Handler 的 post()方法
  3. Activity 的 runOnUiThread()方法

其實這幾個方法的本質都是一樣的,只要我們勤于查看這幾個方法的原始碼,不難
看出最后呼叫的也是 Handler 機制,也是借用了異步訊息處理機制來實作的,

5.1.3:總結

通過上面對異步訊息處理執行緒的講解,我們不難真正地理解到了 HandlerLooper以及 Message 之間的關系,概括性來說,Looper 負責的是創建一個 MessageQueue物件,然后進入到一個無限回圈體中不斷取出訊息,而這些訊息都是由一個或者多個 Handler 進行創建處理,

5.2:Messagequeue 的資料結構是什么?為什么要用這個資料結構?

5.2.1:為什么要用 Message Queue

為什么要用 Message Queue,我覺得要從一下十個方面考慮:

  1. 解耦
    在專案啟動之初來預測將來專案會碰到什么需求,是極其困難的,訊息佇列在處理程序中間插入了一個隱含的、基于資料的介面層,兩邊的處理程序都要實作這一介面,這允許你獨立的擴展或修改兩邊的處理程序,只要確保它們遵守同樣的介面約束.
  2. 冗余
    有些情況下,處理資料的程序會失敗,除非資料被持久化,否則將造成丟失,訊息佇列把資料進行持久化直到它們已經被完全處理,通過這一方式規避了資料丟失風險,在被許多訊息佇列所采用的”插入-獲取-洗掉”范式中,在把一個訊息從佇列中洗掉之前,需要你的處理程序明確的指出該訊息已經被處理完畢,確保你的資料被安全的保存直到你使用完畢,
  3. 擴展性
    因為訊息佇列解耦了你的處理程序,所以增大訊息入隊和處理的頻率是很容易的;只要另外增加處理程序即可,不需要改變代碼、不需要調節引數,擴展就像調大電力按鈕一樣簡單,
  4. 靈活性 & 峰值處理能力
    在訪問量劇增的情況下,應用仍然需要繼續發揮作用,但是這樣的突發流量并不常見;如果為以能處理這類峰值訪問為標準來投入資源隨時待命無疑是巨大的浪費,使用訊息佇列能夠使關鍵組件頂住突發的訪問壓力,而不會因為突發的超負荷的請求而完全,
    崩潰,
  5. 可恢復性
    當體系的一部分組件失效,不會影響到整個系統,訊息佇列降低了行程間的耦合度,所以即使一個處理訊息的行程掛掉,加入佇列中的訊息仍然可以在系統恢復后被處理,而這種允許重試或者延后處理請求的能力通常是造就一個略感不便的用戶和一個沮喪透頂的用戶之間的區別,
  6. 送達保證
    訊息佇列提供的冗余機制保證了訊息能被實際的處理,只要一個行程讀取了該佇列即可,在此基礎上,IronMQ 提供了一個”只送達一次”保證,無論有多少行程在從佇列中領取資料,每一個訊息只能被處理一次,這之所以成為可能,是因為獲取一個訊息只是”預定”了這個訊息,暫時把它移出了佇列,除非客戶端明確的表示已經處理完了這個訊息,否則這個訊息會被放回佇列中去,在一段可配置的時間之后可再次被處理,
  7. 順序保證
    在大多使用場景下,資料處理的順序都很重要,訊息佇列本來就是排序的,并且能保證資料會按照特定的順序來處理,IronMO 保證訊息通過 FIFO(先進先出)的順序來處理,因此訊息在佇列中的位置就是從佇列中檢索他們的位置,
  8. 緩沖
    在任何重要的系統中,都會有需要不同的處理時間的元素,例如,加載一張圖片比應用過濾器花費更少的時間,訊息佇列通過一個緩沖層來幫助任務最高效率的執行—寫入佇列的處理會盡可能的快速,而不受從佇列讀的預備處理的約束,該緩沖有助于控制和優化資料流經過系統的速度,
  9. 理解資料流
    在一個分布式系統里,要得到一個關于用戶操作會用多長時間及其原因的總體印象,是個巨大的挑戰,訊息系列通過訊息被處理的頻率,來方便的輔助確定那些表現不佳的處理程序或領域,這些地方的資料流都不夠優化,
  10. 異步通信
    很多時候,你不想也不需要立即處理訊息,訊息佇列提供了異步處理機制,允許你把
    一個訊息放入佇列,但并不立即處理它,你想向佇列中放入多少訊息就放多少,然后
    在你樂意的時候再去處理它們,

5.2.2:Messagequeue 的資料結構是什么?

基礎資料結構中“先進先出”的一種資料結構

5.3:如何在子執行緒中創建 Handler?

在子執行緒中創建 handler,只要確保子執行緒有 Looper,UI 執行緒默認包含 Looper,此時,我們需要用到一個特殊類HandlerThread,這個類可以輕松的創建子執行緒 handler

創建步驟如下:

  1. 創建一個 HandlerThread,即創建一個包含 Looper 的執行緒
    HandlerThread 的建構式有兩個:
public HandlerThread(String name) {
   super(name);
   mPriority = Process.THREAD_PRIORITY_DEFAULT;
}
/**
* Constructs a HandlerThread. * @param name * @param priority The priority to run the thread at. The value supplied must be from
* {@link android.os.Process} and not from java.lang.Thread. */
public HandlerThread(String name, int priority) {super(name);
    mPriority = priority;
}

這里我們使用第一個就好:

HandlerThread handlerThread=new HandlerThread(“xuan”);
//創建 HandlerThread 后一定要記得 start();
handlerThread.start();
//通過 HandlerThread 的 getLooper 方法可以獲取 Looper
Looper looper=handlerThread.getLooper();
//通過 Looper 我們就可以創建子執行緒的 handler 了
Handlr handler=new Handler(looper);
通過該 handler 發送訊息,就會在子執行緒執行;

提示:如果要 handlerThread 停止:handlerThread.quit(),完整測驗代碼:

HandlerThread hanlerThread = new HandlerThread("子執行緒");
     hanlerThread.start();
     
     final Handler handler = new Handler(hanlerThread.getLooper()) {
		@Override
			public void handleMessage(Message msg) {
			super.handleMessage(msg);
			Log.d("----->", "執行緒:" + Thread.currentThread().getName());
		}
     };

	findViewById(R.id.bt).setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {
			   handler.sendEmptyMessage(100);
			}
	});

intentService(子執行緒)中,如果要回掉在 UI 執行緒怎么辦呢?

new Handler(getMainLooper()).post(new Runnable() {
	@Override
	public void run() {
		// person.getName() Realm objects can only be accessed on the thread they were created.         Toast.makeText(getApplicationContext(), "Loaded Person from
		broadcast-receiver->intent-service: " + info, Toast.LENGTH_LONG).show();
	}
});

5.4:Handler post 方法原理?

5.4.1:原始碼分析

5.4.1.1:點進去看 postDelayed()中的方法,里面呼叫 sendMessageDelayed 方法,和post() 里面呼叫的方法一樣,

public final boolean postDelayed(Runnable r, long delayMillis)
{
return sendMessageDelayed(getPostMessage(r), delayMillis);
}

5.4.1.2:分析sendMessageDelayed()方法

public final boolean sendMessageDelayed(Message msg, long delayMillis){
	if (delayMillis < 0) {
	    delayMillis = 0;
	}
   return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

里面呼叫了 sendMessageAtTime(),這里的 SystemClock.uptimeMillis()是獲取系統從開機啟動到現在的時間,期間不包括休眠的時間,這里獲得到的時間是一個相對的時間,而不是通過獲取當前的時間(絕對時間),而之所以使用這種方式來計算時間,而不是獲得當前 currenttime 來計算,在于handler 會受到阻塞,掛起狀態,睡眠等,這些時候是不應該執行的;如果使用絕對時間的話,就會搶占資源來執行當前 handler 的內容,顯然這是不應該出現的情況,所以要避免,

5.4.1.3:分析sendMessageAtTime()方法

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
	MessageQueue queue = mQueue;
		if (queue == null) {
			RuntimeException e = new RuntimeException(this + " sendMessageAtTime() called with no mQueue");
			Log.w("Looper", e.getMessage(), e);
			return false;
		}
    return enqueueMessage(queue, msg, uptimeMillis);
}

追到這里依然沒有看到,他在存放的時候有什么不同,但是顯然證實了訊息不是延遲放進 MessageQueen 的,那是腫么處理的,是在輪訓的時候處理的嗎?

5.4.1.4:我們點進 Looper 中看一下,主要代碼,Looper 中的 looper 呼叫了 MessageQueen中的 next 方法,難道是在 next()方法中處理的?

public static void loop() {
···
 for (;;) {
    Message msg = queue.next(); // might block
	if (msg == null) {
		// No message indicates that the message queue is quitting.
	    return;
	}
}		

5.4.1.5:分析MessageQueen 中的 next()方法

for (;;) {
	if (nextPollTimeoutMillis != 0) {
	    Binder.flushPendingCommands();
	}
   nativePollOnce(ptr, nextPollTimeoutMillis);
···
		if (msg != null) {
			if (now < msg.when) {
				// Next message is not ready. Set a timeout to wake up when it is ready.
				nextPollTimeoutMillis = (int) Math.min(msg.when -
				now, Integer.MAX_VALUE);
	   }else {
	    // Got a message.
	    mBlocked = false;
		if (prevMsg != null) {
	     	prevMsg.next = msg.next;
		} else {
	    	mMessages = msg.next;
		}
	    msg.next = null;
	   if (DEBUG) Log.v(TAG, "Returning message: " + msg);
	   msg.markInUse();
	   return msg;
	 }
  } else {
     // No more messages.
     nextPollTimeoutMillis = -1;
  }
 }
}

很貼心的給出了注釋解釋:“ Next message is not ready. Set a timeout to wake up when it is ready,翻譯“下一條訊息尚未準備好,設定一個超時,以便在準備就緒時喚醒,”

when 就是 uptimeMillisfor (;;) 相當于 while(true),如果頭部的這個 Message是有延遲而且延遲時間沒到的(now < msg.when),不回傳 message 而且會計算一下時間(保存為變數nextPollTimeoutMillis),然后再回圈的時候判斷如果這個 Message 有延遲,就呼叫nativePollOnce(ptr, nextPollTimeoutMillis)進行阻塞,nativePollOnce()的作用類似與 object.wait(),得出結論是通過阻塞實作的,

5.4.1.6:但是如果在阻塞這段時間里有無延遲 message 又加入MessageQueen 中又是怎么實作立即處理這個 message 的呢?我們看MessageQueen 中放入訊息enqueueMessage()方法

boolean enqueueMessage(Message msg, long when) {
	if (msg.target == null) {
	    throw new IllegalArgumentException("Message must have a target.");
	}
	
	if (msg.isInUse()) {
    	throw new IllegalStateException(msg + " This message is already in use.");
	}
	
	synchronized (this) {
		if (mQuitting) {
			IllegalStateException e = new IllegalStateException(msg.target + " sending message to a Handler on a dead thread");
			Log.w(TAG, e.getMessage(), e);
			msg.recycle();
			return false;
		}
	msg.markInUse();
	msg.when = when;
	Message p = mMessages;
	boolean needWake;
	if (p == null || when == 0 || when < p.when) {
		// New head, wake up the event queue if blocked.
		msg.next = p;
		mMessages = msg;
		needWake = mBlocked;
	}else {
	// Inserted within the middle of the queue. Usually we don't have to wake
	// up the event queue unless there is a barrier at the head of the queue
	// and the message is the earliest asynchronous message inthe queue.
	needWake = mBlocked && p.target == null && msg.isAsynchronous();
	Message prev;
	for (;;) {
		prev = p;
		p = p.next;
		
		if (p == null || when < p.when) {
	    	break;
		}
		
		if (needWake && p.isAsynchronous()) {
	     	needWake = false;
		}
     }
	msg.next = p; // invariant: p == prev.next
	prev.next = msg;
}
	// We can assume mPtr != 0 because mQuitting is false.
	if (needWake) {
		nativeWake(mPtr);
		}
	}
   return true;
}

在這里 p 是現在訊息佇列中的頭部訊息,我們看到 when < p.when 的時候它交換了放入 message 與原來訊息佇列頭部 P 的位置,并且 needWake = mBlocked;(在 next()中當訊息為延遲訊息的時候mBlocked=true),繼續向下看 ,當needWake =true 的時候 nativeWake(mPtr)(喚起執行緒)一切都解釋的通了,如果當前插入的訊息不是延遲 message,或比當前的延遲短,這個訊息就會插入頭部并且喚起執行緒來,

好了,經過上面一系列的原始碼決議,我們把我們跟蹤的所有資訊整理下,

5.4.2:跟蹤整理

跟蹤的所有資訊整理如下:

  1. 訊息是通過 MessageQueen 中的 enqueueMessage()方法加入訊息隊列中的,并且它在放入中就進行好排序,鏈表頭的延遲時間小,尾部延遲時間最大
  2. Looper.loop()通過 MessageQueue 中的 next()去取訊息
  3. next()中如果當前鏈表頭部訊息是延遲訊息,則根據延遲時間進行訊息佇列會阻塞,不回傳給 Looper message,知道時間到了,回傳給 message
  4. 如果在阻塞中有新的訊息插入到鏈表頭部則喚醒執行緒
  5. Looper 將新訊息交給回呼給 handler 中的 handleMessage 后,繼續呼叫MessageQueennext()方法,如果剛剛的延遲訊息還是時間未到,則計算時間繼續阻塞

5.4.3:總結

handler.postDelay() 的實作 是通過 MessageQueue 中執行時間順序排列,訊息佇列阻塞,和喚醒的方式結合實作的,如果真的是通過延遲將訊息放入到 MessageQueen 中,那放入多個延遲訊息就要維護多個定時器,

5.5:Android 訊息機制的原理及原始碼決議

5.5.1:訊息機制概括

5.5.1.1:訊息機制的簡介

Android 中使用訊息機制,我們首先想到的就是 Handler,沒錯,HandlerAndroid 訊息機制的上層介面,Handler 的使用程序很簡單,通過它可以輕松地將一個任務切換到 Handler 所在的執行緒中去執行,通常情況下,Handler 的使用場景就是更新 UI,

下面簡單看一個訊息機制的一個簡單實體:

public class Activity extends android.app.Activity {
	private Handler mHandler = new Handler(){
		@Override
		public void handleMessage(Message msg) {
			super.handleMessage(msg);
			System.out.println(msg.what);
		}
	};

@Override
public void onCreate(Bundle savedInstanceState, PersistableBundle persistentState) {
   super.onCreate(savedInstanceState, persistentState);
   setContentView(R.layout.activity_main);
		new Thread(new Runnable() {
		@Override
		public void run() {
			//...............耗時操作
				Message message = Message.obtain();
				message.what = 1;
				mHandler.sendMessage(message);
			}
		}).start();
      }
}

在子執行緒中,進行耗時操作,執行完操作后,發送訊息,通知主執行緒更新 UI,這便是訊息機制的典型應用場景,我們通常只會接觸到 HandlerMessage 來完成訊息機制,其實內部還有兩大助手來共同完成訊息傳遞,

5.5.1.2:訊息機制的模型

訊息機制主要包含:MessageQueueHandlerLooper這三大部分,以及Message,下面我們一一介紹,Message:需要傳遞的訊息,可以傳遞資料;MessageQueue:訊息佇列,但是它的內部實作并不是用的佇列,實際上是通過一個單鏈表的資料結構來維護訊息串列,因為單鏈表在插入和洗掉上比較有優勢,主要功能向訊息池投遞訊息(MessageQueue.enqueueMessage)和取走訊息池的訊息(MessageQueue.next)Handler:訊息輔助類,主要功能向訊息池發送各種訊息事件(Handler.sendMessage)和處理相應訊息事件(Handler.handleMessage)Looper:不斷回圈執行(Looper.loop),從 MessageQueue 中讀取訊息,按分發機制將訊息分發給目標處理者,

5.5.1.3:訊息機制的架構

訊息機制的運行流程:

在子執行緒執行完耗時操作,當 Handler 發送訊息時,將會呼叫MessageQueue.enqueueMessage,向訊息佇列中添加訊息,當通過Looper.loop開啟回圈后,會不斷地從執行緒池中讀取訊息,即呼叫 MessageQueue.next,然后呼叫目標 Handler(即發送該訊息的 Handler)的 dispatchMessage 方法傳遞訊息,然后回傳到 Handler 所在執行緒,目標 Handler 收到訊息,呼叫 handleMessage 方法,接收訊息,處理訊息,
在這里插入圖片描述
MessageQueueHandlerLooper 三者之間的關系:每個執行緒中只能存在一個LooperLooper 是保存在 ThreadLocal 中的,主執行緒(UI 執行緒)已經創建了一個 Looper,所以在主執行緒中不需要再創建 Looper,但是在其他執行緒中需要創建Looper,每個執行緒中可以有多個 Handler,即一個 Looper 可以處理來自多個Handler 的訊息, Looper 中維護一個 MessageQueue,來維護訊息佇列,訊息佇列中的Message 可以來自不同的 Handler
在這里插入圖片描述
下面是訊息機制的整體架構圖,接下來我們將慢慢解剖整個架構,
在這里插入圖片描述
從中我們可以看出:Looper 有一個 MessageQueue 訊息佇列;MessageQueue 有一組待處理的 MessageMessage 中記錄發送和處理訊息的 HandlerHandler 中有 LooperMessageQueue

5.5.2:訊息機制的原始碼決議

5.5.2.1:Looper

1:初始化Looper
要想使用訊息機制,首先要創建一個 Looper,初始化 Looper,無參情況下,默認呼叫 prepare(true);表示的是這個 Looper 可以退出,而對于false 的情況則表示當前 Looper 不可以退出,

public static void prepare() {
   prepare(true);
}

private static void prepare(boolean quitAllowed) {
	if (sThreadLocal.get() != null) {
    	throw new RuntimeException("Only one Looper may be created per thread");
	}
    sThreadLocal.set(new Looper(quitAllowed));
}

這里看出,不能重復創建 Looper,只能創建一個,創建 Looper,并保存在ThreadLocal,其中 ThreadLocal 是執行緒本地存盤區(Thread Local Storage,簡稱為 TLS),每個執行緒都有自己的私有的本地存盤區域,不同執行緒之間彼此不能訪問對方的 TLS 區域,
2:開啟Looper

public static void loop() {
  final Looper me = myLooper(); //獲取 TLS 存盤的 Looper 物件
	if (me == null) {
	  throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
	}
  final MessageQueue queue = me.mQueue; //獲取 Looper 物件中的訊息佇列
  Binder.clearCallingIdentity();
  final long ident = Binder.clearCallingIdentity();
  for (;;) { //進入 loop 的主回圈方法
	     Message msg = queue.next(); //可能會阻塞,因為 next()方法可能會無限回圈
		 if (msg == null) { //訊息為空,則退出回圈
		   return;
		}
		//默認為 null,可通過 setMessageLogging()方法來指定輸出,用于debug 功能
        Printer logging = me.mLogging; 
		if (logging != null) {
		    logging.println(">>>>> Dispatching to " + msg.target + " " +
	    	msg.callback + ": " + msg.what);
		}
       msg.target.dispatchMessage(msg); //獲取 msg 的目標 Handler,然后用于分發 Message
		if (logging != null) {
		logging.println("<<<<< Finished to " + msg.target + " " + msg.
		callback);
		}
		final long newIdent = Binder.clearCallingIdentity();
		if (ident != newIdent) {
		  -------
		}
       msg.recycleUnchecked();
   }
}

loop()進入回圈模式,不斷重復下面的操作,直到訊息為空時退出回圈:讀取 MessageQueue 的下一條 Message(關于 next(),后面詳細介紹);把 Message 分發給相應的 target,當 next()取出下一條訊息時,佇列中已經沒有訊息時,next()會無限回圈,產生阻塞,等待 MessageQueue 中加入訊息,然后重新喚醒,主執行緒中不需要自己創建 Looper,這是由于在程式啟動的時候,系統已經幫我們自動呼叫了 Looper.prepare()方法,查看 ActivityThread 中的 main()方法,代碼如下所示:

public static void main(String[] args) {..........................
	Looper.prepareMainLooper();
	..........................
	Looper.loop();
	..........................
}

其中prepareMainLooper()方法會呼叫 prepare(false)方法,

5.5.2.2:Handler

創建 Handler:

public Handler() {
   this(null, false);
}

public Handler(Callback callback, boolean async) {
.................................
//必須先執行 Looper.prepare(),才能獲取 Looper 物件,否則為 null.
mLooper = Looper.myLooper(); //從當前執行緒的 TLS 中獲取 Looper 物件
if (mLooper == null) {
    throw new RuntimeException("");
}

mQueue = mLooper.mQueue; //訊息佇列,來自 Looper 物件
mCallback = callback; //回呼方法
mAsynchronous = async; //設定訊息是否為異步處理方式}

對于 Handler 的無參構造方法,默認采用當前執行緒 TLS 中的 Looper 物件,并且callback 回呼方法為 null,且訊息為同步處理方式,只要執行的 Looper.prepare()方法,那么便可以獲取有效的 Looper 物件,

5.5.2.3:發送訊息

發送訊息有幾種方式,但是歸根結底都是呼叫了 sendMessageAtTime()方法,在子執行緒中通過 Handlerpost()方式或 send()方式發送訊息,最終都是呼叫了sendMessageAtTime()方法,

  1. post 方法
public final boolean post(Runnable r)
{
   return sendMessageDelayed(getPostMessage(r), 0);
}
public final boolean postAtTime(Runnable r, long uptimeMillis)
{
  return sendMessageAtTime(getPostMessage(r), uptimeMillis);
}
public final boolean postAtTime(Runnable r, Object token, long uptimeMillis)
{
return sendMessageAtTime(getPostMessage(r, token), uptimeMillis);
}
public final boolean postDelayed(Runnable r, long delayMillis)
{
  return sendMessageDelayed(getPostMessage(r), delayMillis);
}
  1. send 方法
public final boolean sendMessage(Message msg)
{
  return sendMessageDelayed(msg, 0);
}
public final boolean sendEmptyMessage(int what)
{
  return sendEmptyMessageDelayed(what, 0);
}
 public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
	Message msg = Message.obtain();
	msg.what = what;
   return sendMessageDelayed(msg, delayMillis);
}
public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis)
{
	Message msg = Message.obtain();
	msg.what = what;
	return sendMessageAtTime(msg, uptimeMillis);
}
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
	if (delayMillis < 0) {
	   delayMillis = 0;
	}
   return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

就連子執行緒中呼叫 Activity 中的 runOnUiThread()中更新 UI,其實也是發送訊息通知主執行緒更新 UI,最終也會呼叫 sendMessageAtTime()方法,

public final void runOnUiThread(Runnable action) {
	if (Thread.currentThread() != mUiThread) {
	   mHandler.post(action);
	} else {
	   action.run();
	}
}

如果當前的執行緒不等于 UI 執行緒(主執行緒),就去呼叫 Handlerpost()方法,最侄訓呼叫 sendMessageAtTime()方法,否則就直接呼叫 Runnable 物件的 run()方法,下面我們就來一探究竟,到底 sendMessageAtTime()方法有什么作用?

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
	//其中 mQueue 是訊息佇列,從 Looper 中獲取的
	MessageQueue queue = mQueue;
		if (queue == null) {
			RuntimeException e = new RuntimeException(
			this + " sendMessageAtTime() called with no mQueue");
			Log.w("Looper", e.getMessage(), e);
		return false;
	}
   //呼叫 enqueueMessage 方法
   return enqueueMessage(queue, msg, uptimeMillis);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
     msg.target = this;
	if (mAsynchronous) {
	   msg.setAsynchronous(true);
	}
    //呼叫 MessageQueue 的 enqueueMessage 方法
    return queue.enqueueMessage(msg, uptimeMillis);
}

可以看到 sendMessageAtTime()方法的作用很簡單,就是呼叫MessageQueueenqueueMessage()方法,往訊息佇列中添加一個訊息,下面來看enqueueMessage()`方法的具體執行邏輯,

boolean enqueueMessage(Message msg, long when) {
	
	// 每一個 Message 必須有一個 target
	if (msg.target == null) {
	    throw new IllegalArgumentException("Message must have a target.");
	}
	
	if (msg.isInUse()) {
	   throw new IllegalStateException(msg + " This message is already in use.");
	}
	synchronized (this) {
		if (mQuitting) {
		    //正在退出時,回收 msg,加入到訊息池
		    msg.recycle();
		    return false;
		}
		msg.markInUse();
		msg.when = when;
		Message p = mMessages;
		boolean needWake;
		
	if (p == null || when == 0 || when < p.when) {
		//p 為 null(代表 MessageQueue 沒有訊息) 或者 msg 的觸發時間是佇列中最早的,則進入該該分支
		msg.next = p;
		mMessages = msg;
		needWake = mBlocked;
	} else {
	//將訊息按時間順序插入到 MessageQueue,一般地,不需要喚醒事件佇列,除非訊息隊頭存在 barrier,并且同時 Message 是佇列中最早的異步訊息,
	
	needWake = mBlocked && p.target == null && msg.isAsynchronous();
	Message prev;
	for (;;) {
		prev = p;
		p = p.next;
	    if (p == null || when < p.when) {
		   break;
		}
		
		if (needWake && p.isAsynchronous()) {
		   needWake = false;
		}
	}
	msg.next = p;
	prev.next = msg;
	}
	if (needWake) {
	   nativeWake(mPtr);
	}
 }
return true;
}

MessageQueue 是按照 Message 觸發時間的先后順序排列的,隊頭的訊息是將要最早觸發的訊息,當有訊息需要加入訊息佇列時,會從佇列頭開始遍歷,直到找到訊息應該插入的合適位置,以保證所有訊息的時間順序,

5.5.2.4:獲取訊息

當發送了訊息后,在 MessageQueue 維護了訊息佇列,然后在 Looper 中通過 loop()方法,不斷地獲取訊息,上面對 loop()方法進行了介紹,其中最重要的是呼叫了 queue.next()方法,通過該方法來提取下一條資訊,下面我們來看一下 next()方法的具體流程,
dispatchMessage():

Message next() {
    final long ptr = mPtr;
	if (ptr == 0) { //當訊息回圈已經退出,則直接回傳
    	return null;
	}
	
	int pendingIdleHandlerCount = -1; // 回圈迭代的首次為-1
	int nextPollTimeoutMillis = 0;
    for (;;) {
		if (nextPollTimeoutMillis != 0) {
		    Binder.flushPendingCommands();
		}
		//阻塞操作,當等待 nextPollTimeoutMillis 時長,或者訊息佇列被喚醒,都會回傳
		nativePollOnce(ptr, nextPollTimeoutMillis);
		synchronized (this) {
		final long now = SystemClock.uptimeMillis();
		Message prevMsg = null;
		Message msg = mMessages;
		if (msg != null && msg.target == null) {
		//當訊息 Handler 為空時,查詢 MessageQueue 中的下一條異步訊息 msg,為空則退出回圈,
		do {
			prevMsg = msg;
			msg = msg.next;
		} while (msg != null && !msg.isAsynchronous());
		}
		if (msg != null) {
			if (now < msg.when) {
			     //當異步訊息觸發時間大于當前時間,則設定下一次輪詢的超時時長
			     nextPollTimeoutMillis = (int) Math.min(msg.when - now,Integer.MAX_VALUE);
			} else {
			    // 獲取一條訊息,并回傳
			    mBlocked = false;
				if (prevMsg != null) {
				    prevMsg.next = msg.next;
				} 
				else {
				    mMessages = msg.next;
				}
			    msg.next = null;
			    //設定訊息的使用狀態,即 flags |= FLAG_IN_USE
			    msg.markInUse();
			    return msg; //成功地獲取 MessageQueue 中的下一條即將要執行的訊息
			}
		} else {
			//沒有訊息
			nextPollTimeoutMillis = -1;
		    }
			//訊息正在退出,回傳 null
			if (mQuitting) {
		    	dispose();
		    	return null;
			}
			...............................
		}}

nativePollOnce 是阻塞操作,其中 nextPollTimeoutMillis 代表下一個訊息到來前,還需要等待的時長;當 nextPollTimeoutMillis = -1 時,表示訊息佇列中無訊息,會一直等待下去,可以看出 next()方法根據訊息的觸發時間,獲取下一條需要執行的訊息,佇列中訊息為空時,則會進行阻塞操作,

5.5.2.5:分發訊息

loop()方法中,獲取到下一條訊息后,執行 msg.target.dispatchMessage(msg),來分發訊息到目標 Handler 物件,下面就來具體看下 dispatchMessage(msg)方法的執行流程,

public void dispatchMessage(Message msg) {
	if (msg.callback != null) {
	   //當 Message 存在回呼方法,回呼 msg.callback.run()方法;
	   handleCallback(msg);
	} else {
		if (mCallback != null) {
			//當 Handler 存在 Callback 成員變數時,回呼方法 handleMessage();
			if (mCallback.handleMessage(msg)) {
		    	return;
			}
	}
	//Handler 自身的回呼方法 handleMessage()
	handleMessage(msg);
}}

private static void handleCallback(Message message) {
    message.callback.run();
}

分發訊息流程:
Messagemsg.callback 不為空時,則回呼方法 msg.callback.run();當 HandlermCallback 不為空時,則回呼方法 mCallback.handleMessage(msg);最后呼叫 Handler 自身的回呼方法handleMessage(),該方法默認為空,Handler子類通過覆寫該方法來完成具體的邏輯:

訊息分發的優先級:
Message 的回呼方法:message.callback.run(),優先級最高;HandlerCallback 的回呼方法:Handler.mCallback.handleMessage(msg),優先級僅次于 1;Handler 的默認方法:Handler.handleMessage(msg),優先級最低,對于很多情況下,訊息分發后的處理方法是第 3 種情況,即Handler.handleMessage(),一般地往往通過覆寫該方法從而實作自己的業務邏輯,

5.5.3:總結

以上便是訊息機制的原理,以及從原始碼角度來決議訊息機制的運行程序,可以簡單地用下圖來理解:
在這里插入圖片描述

六:Handler的延伸

Handler 雖然簡單易用,但是要用好它還是需要注意一點,另外 Handler 相關 還有些鮮為人知的知識技巧,比如 IdleHandler,由于 Handler 的特性,它在 Android 里的應用非常廣泛,比如: AsyncTask
HandlerThreadMessengerIdleHandlerIntentService 等等,這些我會講解一些,我沒講到的可以自行搜索相關內容進行了解,

6.1 Handler 引起的記憶體泄露原因以及最佳解決方案

Handler 允許我們發送延時訊息,如果在延時期間用戶關閉了 Activity,那么該Activity 會泄露,這個泄露是因為 Message 會持有 Handler,而又因為 Java 的特性,內部類會持有外部類,使得 Activity 會被 Handler 持有,這樣最終就導致 Activity 泄露,解決該問題的最有效的方法是:將 Handler 定義成靜態的內部類,在內部持有Activity 的弱參考,并及時移除所有訊息,示例代碼如下:

private static class SafeHandler extends Handler {
	private WeakReference<HandlerActivity> ref;
	public SafeHandler(HandlerActivity activity) {
	this.ref = new WeakReference(activity);
}
@Override
public void handleMessage(final Message msg) {
	HandlerActivity activity = ref.get();
	if (activity != null) {
	  activity.handleMessage(msg);
	}
 }
}

并且再在 Activity.onDestroy() 前移除訊息,加一層保障:

@Override
protected void onDestroy() {
	safeHandler.removeCallbacksAndMessages(null);
	super.onDestroy();
}

這樣雙重保障,就能完全避免記憶體泄露了,
注意

單純的在 onDestroy 移除訊息并不保險,因為 onDestroy 并不一定執行,

6.2 為什么我們能在主執行緒直接使用 Handler,而不需要創建 Looper ?

前面我們提到了每個 Handler 的執行緒都有一個 Looper ,主執行緒當然也不例外,但是我們不曾準備過主執行緒的 Looper 而可以直接使用,這是為何?注意:通常我們認為 ActivityThread 就是主執行緒,事實上它并不是一個執行緒,而是主執行緒操作的管理者,所以吧,我覺得把 ActivityThread 認為就是主執行緒無可厚非,另外主執行緒也可以說成 UI 執行緒,在 ActivityThread.main() 方法中有如下代碼:

//android.app.ActivityThread
public static void main(String[] args) {
	//...
	Looper.prepareMainLooper();
	ActivityThread thread = new ActivityThread();
	thread.attach(false);
	if (sMainThreadHandler == null) {
	    sMainThreadHandler = thread.getHandler();
	}
	//...
    Looper.loop();
      throw new RuntimeException("Main thread loop unexpectedly exited");
}


//Looper.prepareMainLooper(); 代碼如下:
/**
* Initialize the current thread as a looper, marking it as an
* application's main looper. The main looper for your application
* is created by the Android environment, so you should never need
* to call this function yourself. See also: {@link #prepare()}
*/
public static void prepareMainLooper() {
    prepare(false);
	synchronized (Looper.class) {
		if (sMainLooper != null) {
		   throw new IllegalStateException("The main Looper has already been prepared.");
		}
    	sMainLooper = myLooper();
	}}

可以看到在 ActivityThread 里 呼叫了 Looper.prepareMainLooper() 方法,創建了主執行緒的 Looper ,并且呼叫了 loop() 方法,所以我們就可以直接使用Handler 了,

注意:

Looper.loop() 是個死回圈,后面的代碼正常情況不會執行,

6.3 主執行緒的 Looper 不允許退出

如果你嘗試退出 Looper,你會得到以下錯誤資訊:

Caused by: java.lang.IllegalStateException: Main thread not allowed to q
uit.
at android.os.MessageQueue.quit(MessageQueue.java:415)
at android.os.Looper.quit(Looper.java:240)

why? 其實原因很簡單,主執行緒不允許退出,退出就意味 APP 要掛,那樣就沒有任何意義了,

6.4 Handler 里藏著的 Callback 能干什么?

Handler 的構造方法中有幾個 要求傳入 Callback ,那它是什么,又能做什么呢?來看看 Handler.dispatchMessage(msg) 方法:

public void dispatchMessage(Message msg) {
	//這里的 callback 是 Runnable
	if (msg.callback != null) {
	    handleCallback(msg);
	} else {
		//如果 callback 處理了該 msg 并且回傳 true, 就不會再回呼 handleMessage
		if (mCallback != null) {
			if (mCallback.handleMessage(msg)) {
			    return;
			}
		}
       handleMessage(msg);
  }
}

可以看到 Handler.Callback 有優先處理訊息的權利 ,當一條訊息被 Callback 處理并攔截(回傳 true),那么 Handler 的 handleMessage(msg) 方法就不會被呼叫了;如果 Callback 處理了訊息,但是并沒有攔截,那么就意味著一個訊息可以同時被 Callback 以及 Handler 處理,

這個就很有意思了,這有什么作用呢?

我們可以利用 Callback 這個攔截機制來攔截 Handler 的訊息!
場景:Hook ActivityThread.mH , 在 ActivityThread 中有個成員變數 mH ,它是個 Handler,又是個極其重要的類,幾乎所有的插件化框架都使用了這個方法,

6.5 創建 Message 實體的最佳方式

由于 Handler 極為常用,所以為了節省開銷,AndroidMessage 設計了回識訓制,所以我們在使用的時候盡量復用 Message ,減少記憶體消耗,方法有二種:

  1. 通過 Message 的靜態方法 Message.obtain();獲取.
  2. 通過 Handler 的公有方法 handler.obtainMessage().

6.6 子執行緒里彈 Toast 的正確姿勢

有時候遇到過這種情況,當我們嘗試在子執行緒里直接去彈 Toast 的時候,會 crash

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

本質上是因為 Toast 的實作依賴于 Handler,按子執行緒使用 Handler 的要求修改即可,同理的還有 Dialog,正確示例代碼如下:

new Thread(new Runnable() {
	@Override
	public void run() {
		Looper.prepare();
		Toast.makeText(HandlerActivity.this, "不會崩潰啦!", Toast.LENGTH_SHORT).show();
		Looper.loop();
	}
});

6.7 妙用 Looper 機制

我們可以利用 Looper 的機制來幫助我們做一些事情:

  1. 將 Runnable post 到主執行緒執行;
  2. 利用 Looper 判斷當前執行緒是否是主執行緒,

完整示例代碼如下:

public final class MainThread {
	private MainThread() {
	}
    private static final Handler HANDLER = new Handler(Looper.getMainLooper());
    public static void run(@NonNull Runnable runnable) {
		if (isMainThread()) {
		   runnable.run();
		}else{
		    HANDLER.post(runnable);
		}
    }
    
	public static boolean isMainThread() {
	   return Looper.myLooper() == Looper.getMainLooper();
	}
}

七:總結

  1. Handler 的 背 后 有 Looper 、 MessageQueue 支 撐 , Looper 負 責 消 息 分 發 ,MessageQueue 負責訊息管理
  2. 在創建 Handler 之前一定需要先創建 Looper
  3. Looper 有退出的功能,但是主執行緒的 Looper 不允許退出
  4. 異步執行緒的 Looper 需要自己呼叫 Looper.myLooper().quit(); 退出
  5. Runnable 被封裝進了 Message,可以說是一個特殊的 Message
  6. Handler.handleMessage() 所在的執行緒是 Looper.loop() 方法被呼叫的執行緒,也可
    以說成 Looper 所在的執行緒,并不是創建 Handler 的執行緒
  7. 使用內部類的方式使用 Handler 可能會導致記憶體泄露,即便在 Activity.onDestroy
    里移除延時訊息,必須要寫成靜態內部類

更好的博客可以參考:Android Handler:圖文決議 Handler通信機制 的作業原理,

請點贊關注加品論!因為你的鼓勵是我寫作的最大動力!

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/290123.html

標籤:其他

上一篇:Android基礎到進階UI 計時器Chronometer 使用+實體

下一篇:多種網路請求方式 ,這么騷氣的操作確定不來看看嘛?

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more