你按 Home 键的那一刻,桌面是怎么出现的?从 AMS 调度到 Launcher 的完整启动链路拆解(附手写简易桌面实战)
目录
- [一、Launcher 到底是啥](#一、Launcher 到底是啥)
- [二、Launcher 的两种启动场景](#二、Launcher 的两种启动场景)
- [三、开机时 Launcher 怎么被拉起来的](#三、开机时 Launcher 怎么被拉起来的)
- [四、按下 Home 键时 Launcher 怎么回来的](#四、按下 Home 键时 Launcher 怎么回来的)
- [五、Launcher 加载桌面的完整流程](#五、Launcher 加载桌面的完整流程)
- 六、桌面图标是从哪里来的
- [七、多个 Launcher 怎么选](#七、多个 Launcher 怎么选)
- 八、实战:手写一个简易桌面
- [九、Launcher 与 Widget](#九、Launcher 与 Widget)
- 十、常见踩坑记录
- 十一、总结
一、Launcher 到底是啥
Launcher 说白了就是一个普通的 Android App------但是多了一个属性 CATEGORY_HOME。系统就是靠这个属性知道哪个 App 是"桌面"。
xml
<!-- AndroidManifest.xml 里声明自己是桌面 -->
<activity android:name=".HomeActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.HOME" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
你打开手机看到的那个"桌面",其实就是一个声明了
CATEGORY_HOME的 Activity。AOSP 自带的 Launcher 叫 Launcher3 ,源码在packages/apps/Launcher3/。
Launcher 和其他 App 的关系:
#mermaid-svg-gejQ1wS7gIgZtvjE{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-gejQ1wS7gIgZtvjE .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-gejQ1wS7gIgZtvjE .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-gejQ1wS7gIgZtvjE .error-icon{fill:#552222;}#mermaid-svg-gejQ1wS7gIgZtvjE .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-gejQ1wS7gIgZtvjE .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-gejQ1wS7gIgZtvjE .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-gejQ1wS7gIgZtvjE .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-gejQ1wS7gIgZtvjE .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-gejQ1wS7gIgZtvjE .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-gejQ1wS7gIgZtvjE .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-gejQ1wS7gIgZtvjE .marker{fill:#333333;stroke:#333333;}#mermaid-svg-gejQ1wS7gIgZtvjE .marker.cross{stroke:#333333;}#mermaid-svg-gejQ1wS7gIgZtvjE svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-gejQ1wS7gIgZtvjE p{margin:0;}#mermaid-svg-gejQ1wS7gIgZtvjE .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-gejQ1wS7gIgZtvjE .cluster-label text{fill:#333;}#mermaid-svg-gejQ1wS7gIgZtvjE .cluster-label span{color:#333;}#mermaid-svg-gejQ1wS7gIgZtvjE .cluster-label span p{background-color:transparent;}#mermaid-svg-gejQ1wS7gIgZtvjE .label text,#mermaid-svg-gejQ1wS7gIgZtvjE span{fill:#333;color:#333;}#mermaid-svg-gejQ1wS7gIgZtvjE .node rect,#mermaid-svg-gejQ1wS7gIgZtvjE .node circle,#mermaid-svg-gejQ1wS7gIgZtvjE .node ellipse,#mermaid-svg-gejQ1wS7gIgZtvjE .node polygon,#mermaid-svg-gejQ1wS7gIgZtvjE .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-gejQ1wS7gIgZtvjE .rough-node .label text,#mermaid-svg-gejQ1wS7gIgZtvjE .node .label text,#mermaid-svg-gejQ1wS7gIgZtvjE .image-shape .label,#mermaid-svg-gejQ1wS7gIgZtvjE .icon-shape .label{text-anchor:middle;}#mermaid-svg-gejQ1wS7gIgZtvjE .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-gejQ1wS7gIgZtvjE .rough-node .label,#mermaid-svg-gejQ1wS7gIgZtvjE .node .label,#mermaid-svg-gejQ1wS7gIgZtvjE .image-shape .label,#mermaid-svg-gejQ1wS7gIgZtvjE .icon-shape .label{text-align:center;}#mermaid-svg-gejQ1wS7gIgZtvjE .node.clickable{cursor:pointer;}#mermaid-svg-gejQ1wS7gIgZtvjE .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-gejQ1wS7gIgZtvjE .arrowheadPath{fill:#333333;}#mermaid-svg-gejQ1wS7gIgZtvjE .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-gejQ1wS7gIgZtvjE .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-gejQ1wS7gIgZtvjE .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-gejQ1wS7gIgZtvjE .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-gejQ1wS7gIgZtvjE .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-gejQ1wS7gIgZtvjE .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-gejQ1wS7gIgZtvjE .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-gejQ1wS7gIgZtvjE .cluster text{fill:#333;}#mermaid-svg-gejQ1wS7gIgZtvjE .cluster span{color:#333;}#mermaid-svg-gejQ1wS7gIgZtvjE div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-gejQ1wS7gIgZtvjE .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-gejQ1wS7gIgZtvjE rect.text{fill:none;stroke-width:0;}#mermaid-svg-gejQ1wS7gIgZtvjE .icon-shape,#mermaid-svg-gejQ1wS7gIgZtvjE .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-gejQ1wS7gIgZtvjE .icon-shape p,#mermaid-svg-gejQ1wS7gIgZtvjE .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-gejQ1wS7gIgZtvjE .icon-shape .label rect,#mermaid-svg-gejQ1wS7gIgZtvjE .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-gejQ1wS7gIgZtvjE .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-gejQ1wS7gIgZtvjE .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-gejQ1wS7gIgZtvjE :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 启动图标
启动图标
启动图标
判断谁处理 HOME Intent
Launcher
桌面 App
CATEGORY_HOME
微信
设置
相册
ActivityManagerService
二、Launcher 的两种启动场景
Launcher 不是只启动一次------它可能在两种情况下被拉起来:
| 场景 | 触发方式 | 谁发的请求 |
|---|---|---|
| 开机启动 | AMS.systemReady() 里主动调 startHomeActivityLocked() | AMS 自己 |
| 按 Home 键 | KeyEvent.KEYCODE_HOME → AMS → 解析 CATEGORY_HOME Intent | PhoneWindowManager → AMS |
#mermaid-svg-8uFXTmFJAhkotYLh{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-8uFXTmFJAhkotYLh .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-8uFXTmFJAhkotYLh .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-8uFXTmFJAhkotYLh .error-icon{fill:#552222;}#mermaid-svg-8uFXTmFJAhkotYLh .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-8uFXTmFJAhkotYLh .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-8uFXTmFJAhkotYLh .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-8uFXTmFJAhkotYLh .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-8uFXTmFJAhkotYLh .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-8uFXTmFJAhkotYLh .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-8uFXTmFJAhkotYLh .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-8uFXTmFJAhkotYLh .marker{fill:#333333;stroke:#333333;}#mermaid-svg-8uFXTmFJAhkotYLh .marker.cross{stroke:#333333;}#mermaid-svg-8uFXTmFJAhkotYLh svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-8uFXTmFJAhkotYLh p{margin:0;}#mermaid-svg-8uFXTmFJAhkotYLh .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-8uFXTmFJAhkotYLh .cluster-label text{fill:#333;}#mermaid-svg-8uFXTmFJAhkotYLh .cluster-label span{color:#333;}#mermaid-svg-8uFXTmFJAhkotYLh .cluster-label span p{background-color:transparent;}#mermaid-svg-8uFXTmFJAhkotYLh .label text,#mermaid-svg-8uFXTmFJAhkotYLh span{fill:#333;color:#333;}#mermaid-svg-8uFXTmFJAhkotYLh .node rect,#mermaid-svg-8uFXTmFJAhkotYLh .node circle,#mermaid-svg-8uFXTmFJAhkotYLh .node ellipse,#mermaid-svg-8uFXTmFJAhkotYLh .node polygon,#mermaid-svg-8uFXTmFJAhkotYLh .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-8uFXTmFJAhkotYLh .rough-node .label text,#mermaid-svg-8uFXTmFJAhkotYLh .node .label text,#mermaid-svg-8uFXTmFJAhkotYLh .image-shape .label,#mermaid-svg-8uFXTmFJAhkotYLh .icon-shape .label{text-anchor:middle;}#mermaid-svg-8uFXTmFJAhkotYLh .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-8uFXTmFJAhkotYLh .rough-node .label,#mermaid-svg-8uFXTmFJAhkotYLh .node .label,#mermaid-svg-8uFXTmFJAhkotYLh .image-shape .label,#mermaid-svg-8uFXTmFJAhkotYLh .icon-shape .label{text-align:center;}#mermaid-svg-8uFXTmFJAhkotYLh .node.clickable{cursor:pointer;}#mermaid-svg-8uFXTmFJAhkotYLh .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-8uFXTmFJAhkotYLh .arrowheadPath{fill:#333333;}#mermaid-svg-8uFXTmFJAhkotYLh .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-8uFXTmFJAhkotYLh .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-8uFXTmFJAhkotYLh .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-8uFXTmFJAhkotYLh .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-8uFXTmFJAhkotYLh .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-8uFXTmFJAhkotYLh .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-8uFXTmFJAhkotYLh .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-8uFXTmFJAhkotYLh .cluster text{fill:#333;}#mermaid-svg-8uFXTmFJAhkotYLh .cluster span{color:#333;}#mermaid-svg-8uFXTmFJAhkotYLh div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-8uFXTmFJAhkotYLh .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-8uFXTmFJAhkotYLh rect.text{fill:none;stroke-width:0;}#mermaid-svg-8uFXTmFJAhkotYLh .icon-shape,#mermaid-svg-8uFXTmFJAhkotYLh .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-8uFXTmFJAhkotYLh .icon-shape p,#mermaid-svg-8uFXTmFJAhkotYLh .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-8uFXTmFJAhkotYLh .icon-shape .label rect,#mermaid-svg-8uFXTmFJAhkotYLh .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-8uFXTmFJAhkotYLh .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-8uFXTmFJAhkotYLh .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-8uFXTmFJAhkotYLh :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 按 Home 键
开机启动
AMS.systemReady()
startHomeActivityLocked()
构造 Intent
CATEGORY_HOME
resolveActivity()
找默认 Launcher
startActivityAsUser()
正常启 Activity
KeyEvent.KEYCODE_HOME
PhoneWindowManager
interceptKeyBeforeDispatching()
launchHomeFromHotKey()
startHomeActivityLocked()
Launcher Activity 启动
两条路径最后殊途同归------都是调 AMS 的
startHomeActivityLocked(),然后走标准的 Activity 启动流程。
三、开机时 Launcher 怎么被拉起来的
回顾一下前文《Android 系统启动过程》里的时序:SystemServer 的 Other 阶段末尾调了 AMS.systemReady(),里面那一步 startHomeActivityLocked 就是拉桌面。
#mermaid-svg-0b4hTtt6lWAVyE4m{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-0b4hTtt6lWAVyE4m .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-0b4hTtt6lWAVyE4m .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-0b4hTtt6lWAVyE4m .error-icon{fill:#552222;}#mermaid-svg-0b4hTtt6lWAVyE4m .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-0b4hTtt6lWAVyE4m .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-0b4hTtt6lWAVyE4m .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-0b4hTtt6lWAVyE4m .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-0b4hTtt6lWAVyE4m .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-0b4hTtt6lWAVyE4m .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-0b4hTtt6lWAVyE4m .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-0b4hTtt6lWAVyE4m .marker{fill:#333333;stroke:#333333;}#mermaid-svg-0b4hTtt6lWAVyE4m .marker.cross{stroke:#333333;}#mermaid-svg-0b4hTtt6lWAVyE4m svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-0b4hTtt6lWAVyE4m p{margin:0;}#mermaid-svg-0b4hTtt6lWAVyE4m .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-0b4hTtt6lWAVyE4m .cluster-label text{fill:#333;}#mermaid-svg-0b4hTtt6lWAVyE4m .cluster-label span{color:#333;}#mermaid-svg-0b4hTtt6lWAVyE4m .cluster-label span p{background-color:transparent;}#mermaid-svg-0b4hTtt6lWAVyE4m .label text,#mermaid-svg-0b4hTtt6lWAVyE4m span{fill:#333;color:#333;}#mermaid-svg-0b4hTtt6lWAVyE4m .node rect,#mermaid-svg-0b4hTtt6lWAVyE4m .node circle,#mermaid-svg-0b4hTtt6lWAVyE4m .node ellipse,#mermaid-svg-0b4hTtt6lWAVyE4m .node polygon,#mermaid-svg-0b4hTtt6lWAVyE4m .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-0b4hTtt6lWAVyE4m .rough-node .label text,#mermaid-svg-0b4hTtt6lWAVyE4m .node .label text,#mermaid-svg-0b4hTtt6lWAVyE4m .image-shape .label,#mermaid-svg-0b4hTtt6lWAVyE4m .icon-shape .label{text-anchor:middle;}#mermaid-svg-0b4hTtt6lWAVyE4m .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-0b4hTtt6lWAVyE4m .rough-node .label,#mermaid-svg-0b4hTtt6lWAVyE4m .node .label,#mermaid-svg-0b4hTtt6lWAVyE4m .image-shape .label,#mermaid-svg-0b4hTtt6lWAVyE4m .icon-shape .label{text-align:center;}#mermaid-svg-0b4hTtt6lWAVyE4m .node.clickable{cursor:pointer;}#mermaid-svg-0b4hTtt6lWAVyE4m .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-0b4hTtt6lWAVyE4m .arrowheadPath{fill:#333333;}#mermaid-svg-0b4hTtt6lWAVyE4m .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-0b4hTtt6lWAVyE4m .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-0b4hTtt6lWAVyE4m .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-0b4hTtt6lWAVyE4m .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-0b4hTtt6lWAVyE4m .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-0b4hTtt6lWAVyE4m .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-0b4hTtt6lWAVyE4m .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-0b4hTtt6lWAVyE4m .cluster text{fill:#333;}#mermaid-svg-0b4hTtt6lWAVyE4m .cluster span{color:#333;}#mermaid-svg-0b4hTtt6lWAVyE4m div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-0b4hTtt6lWAVyE4m .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-0b4hTtt6lWAVyE4m rect.text{fill:none;stroke-width:0;}#mermaid-svg-0b4hTtt6lWAVyE4m .icon-shape,#mermaid-svg-0b4hTtt6lWAVyE4m .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-0b4hTtt6lWAVyE4m .icon-shape p,#mermaid-svg-0b4hTtt6lWAVyE4m .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-0b4hTtt6lWAVyE4m .icon-shape .label rect,#mermaid-svg-0b4hTtt6lWAVyE4m .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-0b4hTtt6lWAVyE4m .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-0b4hTtt6lWAVyE4m .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-0b4hTtt6lWAVyE4m :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
SystemServer
startOtherServices()
AMS.systemReady()
startHomeActivityLocked()
构造 Intent
ACTION_MAIN + CATEGORY_HOME
PackageManagerService
resolveActivity()
找到默认 Launcher?
以该 Launcher 的 ActivityInfo
启动
弹出 Launcher 选择对话框
让用户选一个
通过 Zygote fork 新进程
或者复用已有进程
Launcher Activity
onCreate / onStart / onResume
★ 桌面出现
源码路径:
java
// frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
public void systemReady(final Runnable goingCallback, ...) {
// ...一系列准备
startHomeActivityLocked(mCurrentUserId, "systemReady");
}
boolean startHomeActivityLocked(int userId, String reason) {
Intent intent = getHomeIntent(); // ★ CATEGORY_HOME
ActivityInfo aInfo = resolveActivity(intent, ...);
if (aInfo != null) {
// ★ 启动 Launcher Activity
mAtmInternal.startHomeActivity(intent, aInfo, reason, ...);
}
return true;
}
Intent getHomeIntent() {
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_HOME);
return intent;
}
注意:Launcher 启动走的是 标准 Activity 启动流程(Zygote fork → ActivityThread.main → Activity.onCreate),跟普通 App 启动没有区别。
四、按下 Home 键时 Launcher 怎么回来的
你在 App 里按 Home 键回到桌面,这个流程涉及 Input 系统 → AMS → Activity 栈管理:
#mermaid-svg-yj0fWHi3nakzpU7F{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-yj0fWHi3nakzpU7F .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-yj0fWHi3nakzpU7F .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-yj0fWHi3nakzpU7F .error-icon{fill:#552222;}#mermaid-svg-yj0fWHi3nakzpU7F .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-yj0fWHi3nakzpU7F .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-yj0fWHi3nakzpU7F .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-yj0fWHi3nakzpU7F .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-yj0fWHi3nakzpU7F .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-yj0fWHi3nakzpU7F .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-yj0fWHi3nakzpU7F .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-yj0fWHi3nakzpU7F .marker{fill:#333333;stroke:#333333;}#mermaid-svg-yj0fWHi3nakzpU7F .marker.cross{stroke:#333333;}#mermaid-svg-yj0fWHi3nakzpU7F svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-yj0fWHi3nakzpU7F p{margin:0;}#mermaid-svg-yj0fWHi3nakzpU7F .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-yj0fWHi3nakzpU7F .cluster-label text{fill:#333;}#mermaid-svg-yj0fWHi3nakzpU7F .cluster-label span{color:#333;}#mermaid-svg-yj0fWHi3nakzpU7F .cluster-label span p{background-color:transparent;}#mermaid-svg-yj0fWHi3nakzpU7F .label text,#mermaid-svg-yj0fWHi3nakzpU7F span{fill:#333;color:#333;}#mermaid-svg-yj0fWHi3nakzpU7F .node rect,#mermaid-svg-yj0fWHi3nakzpU7F .node circle,#mermaid-svg-yj0fWHi3nakzpU7F .node ellipse,#mermaid-svg-yj0fWHi3nakzpU7F .node polygon,#mermaid-svg-yj0fWHi3nakzpU7F .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-yj0fWHi3nakzpU7F .rough-node .label text,#mermaid-svg-yj0fWHi3nakzpU7F .node .label text,#mermaid-svg-yj0fWHi3nakzpU7F .image-shape .label,#mermaid-svg-yj0fWHi3nakzpU7F .icon-shape .label{text-anchor:middle;}#mermaid-svg-yj0fWHi3nakzpU7F .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-yj0fWHi3nakzpU7F .rough-node .label,#mermaid-svg-yj0fWHi3nakzpU7F .node .label,#mermaid-svg-yj0fWHi3nakzpU7F .image-shape .label,#mermaid-svg-yj0fWHi3nakzpU7F .icon-shape .label{text-align:center;}#mermaid-svg-yj0fWHi3nakzpU7F .node.clickable{cursor:pointer;}#mermaid-svg-yj0fWHi3nakzpU7F .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-yj0fWHi3nakzpU7F .arrowheadPath{fill:#333333;}#mermaid-svg-yj0fWHi3nakzpU7F .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-yj0fWHi3nakzpU7F .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-yj0fWHi3nakzpU7F .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-yj0fWHi3nakzpU7F .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-yj0fWHi3nakzpU7F .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-yj0fWHi3nakzpU7F .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-yj0fWHi3nakzpU7F .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-yj0fWHi3nakzpU7F .cluster text{fill:#333;}#mermaid-svg-yj0fWHi3nakzpU7F .cluster span{color:#333;}#mermaid-svg-yj0fWHi3nakzpU7F div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-yj0fWHi3nakzpU7F .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-yj0fWHi3nakzpU7F rect.text{fill:none;stroke-width:0;}#mermaid-svg-yj0fWHi3nakzpU7F .icon-shape,#mermaid-svg-yj0fWHi3nakzpU7F .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-yj0fWHi3nakzpU7F .icon-shape p,#mermaid-svg-yj0fWHi3nakzpU7F .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-yj0fWHi3nakzpU7F .icon-shape .label rect,#mermaid-svg-yj0fWHi3nakzpU7F .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-yj0fWHi3nakzpU7F .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-yj0fWHi3nakzpU7F .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-yj0fWHi3nakzpU7F :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
用户按 Home 键
InputReader
读到 KeyEvent
InputDispatcher
分发事件
PhoneWindowManager
interceptKeyBeforeQueueing()
处理 Home 键?
launchHomeFromHotKey()
AMS.startHomeActivityLocked()
当前前台 Activity 被 pause/stop
Launcher Activity 被 resume
(如果已经在后台)
★ 桌面出现在最前面
关键点: Home 键不会 kill 掉你的 App,只是把它从前台压到后台。Launcher 如果之前已经在 Activity 栈里,就把它 resume 回来;如果被系统杀了,就重新创建。
五、Launcher 加载桌面的完整流程
Launcher Activity 创建后,开始加载桌面内容------图标、widget、文件夹:
#mermaid-svg-dEcKqb5NHraQI4tG{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-dEcKqb5NHraQI4tG .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-dEcKqb5NHraQI4tG .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-dEcKqb5NHraQI4tG .error-icon{fill:#552222;}#mermaid-svg-dEcKqb5NHraQI4tG .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-dEcKqb5NHraQI4tG .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-dEcKqb5NHraQI4tG .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-dEcKqb5NHraQI4tG .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-dEcKqb5NHraQI4tG .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-dEcKqb5NHraQI4tG .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-dEcKqb5NHraQI4tG .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-dEcKqb5NHraQI4tG .marker{fill:#333333;stroke:#333333;}#mermaid-svg-dEcKqb5NHraQI4tG .marker.cross{stroke:#333333;}#mermaid-svg-dEcKqb5NHraQI4tG svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-dEcKqb5NHraQI4tG p{margin:0;}#mermaid-svg-dEcKqb5NHraQI4tG .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-dEcKqb5NHraQI4tG .cluster-label text{fill:#333;}#mermaid-svg-dEcKqb5NHraQI4tG .cluster-label span{color:#333;}#mermaid-svg-dEcKqb5NHraQI4tG .cluster-label span p{background-color:transparent;}#mermaid-svg-dEcKqb5NHraQI4tG .label text,#mermaid-svg-dEcKqb5NHraQI4tG span{fill:#333;color:#333;}#mermaid-svg-dEcKqb5NHraQI4tG .node rect,#mermaid-svg-dEcKqb5NHraQI4tG .node circle,#mermaid-svg-dEcKqb5NHraQI4tG .node ellipse,#mermaid-svg-dEcKqb5NHraQI4tG .node polygon,#mermaid-svg-dEcKqb5NHraQI4tG .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-dEcKqb5NHraQI4tG .rough-node .label text,#mermaid-svg-dEcKqb5NHraQI4tG .node .label text,#mermaid-svg-dEcKqb5NHraQI4tG .image-shape .label,#mermaid-svg-dEcKqb5NHraQI4tG .icon-shape .label{text-anchor:middle;}#mermaid-svg-dEcKqb5NHraQI4tG .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-dEcKqb5NHraQI4tG .rough-node .label,#mermaid-svg-dEcKqb5NHraQI4tG .node .label,#mermaid-svg-dEcKqb5NHraQI4tG .image-shape .label,#mermaid-svg-dEcKqb5NHraQI4tG .icon-shape .label{text-align:center;}#mermaid-svg-dEcKqb5NHraQI4tG .node.clickable{cursor:pointer;}#mermaid-svg-dEcKqb5NHraQI4tG .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-dEcKqb5NHraQI4tG .arrowheadPath{fill:#333333;}#mermaid-svg-dEcKqb5NHraQI4tG .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-dEcKqb5NHraQI4tG .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-dEcKqb5NHraQI4tG .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-dEcKqb5NHraQI4tG .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-dEcKqb5NHraQI4tG .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-dEcKqb5NHraQI4tG .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-dEcKqb5NHraQI4tG .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-dEcKqb5NHraQI4tG .cluster text{fill:#333;}#mermaid-svg-dEcKqb5NHraQI4tG .cluster span{color:#333;}#mermaid-svg-dEcKqb5NHraQI4tG div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-dEcKqb5NHraQI4tG .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-dEcKqb5NHraQI4tG rect.text{fill:none;stroke-width:0;}#mermaid-svg-dEcKqb5NHraQI4tG .icon-shape,#mermaid-svg-dEcKqb5NHraQI4tG .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-dEcKqb5NHraQI4tG .icon-shape p,#mermaid-svg-dEcKqb5NHraQI4tG .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-dEcKqb5NHraQI4tG .icon-shape .label rect,#mermaid-svg-dEcKqb5NHraQI4tG .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-dEcKqb5NHraQI4tG .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-dEcKqb5NHraQI4tG .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-dEcKqb5NHraQI4tG :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} LauncherActivity.onCreate()
初始化 DragLayer
(桌面根布局)
★ LauncherModel.startLoader()
在子线程加载数据
遍历所有已安装 App
PackageManager.queryIntentActivities()
从数据库读取
用户手动创建的快捷方式
从数据库读取
桌面 widget 配置
合并成 AllAppsList
主线程回调
LauncherModel.Callbacks.bindWorkspace()
创建 Workspace 页面
把图标/Widget铺到桌面上
创建 Hotseat
底部固定栏
显示桌面
Launcher3 的关键类:
| 类 | 干嘛的 |
|---|---|
| Launcher.java | 主 Activity,管理整体生命周期 |
| LauncherModel.java | 数据加载------在子线程查 PM、读数据库 |
| Workspace.java | 桌面页面容器,支持滑动翻页 |
| DragLayer.java | 最顶层布局,处理拖拽手势 |
| Hotseat.java | 底部固定栏(电话、短信、浏览器......) |
| DragController.java | 拖拽逻辑(拖图标、对齐到网格) |
| LauncherProvider.java | ContentProvider,存桌面布局到数据库 |
六、桌面图标是从哪里来的
Launcher 上的图标主要有三个来源:
#mermaid-svg-oNEtoUAFIHFvqk6Q{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-oNEtoUAFIHFvqk6Q .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-oNEtoUAFIHFvqk6Q .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-oNEtoUAFIHFvqk6Q .error-icon{fill:#552222;}#mermaid-svg-oNEtoUAFIHFvqk6Q .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-oNEtoUAFIHFvqk6Q .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-oNEtoUAFIHFvqk6Q .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-oNEtoUAFIHFvqk6Q .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-oNEtoUAFIHFvqk6Q .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-oNEtoUAFIHFvqk6Q .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-oNEtoUAFIHFvqk6Q .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-oNEtoUAFIHFvqk6Q .marker{fill:#333333;stroke:#333333;}#mermaid-svg-oNEtoUAFIHFvqk6Q .marker.cross{stroke:#333333;}#mermaid-svg-oNEtoUAFIHFvqk6Q svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-oNEtoUAFIHFvqk6Q p{margin:0;}#mermaid-svg-oNEtoUAFIHFvqk6Q .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-oNEtoUAFIHFvqk6Q .cluster-label text{fill:#333;}#mermaid-svg-oNEtoUAFIHFvqk6Q .cluster-label span{color:#333;}#mermaid-svg-oNEtoUAFIHFvqk6Q .cluster-label span p{background-color:transparent;}#mermaid-svg-oNEtoUAFIHFvqk6Q .label text,#mermaid-svg-oNEtoUAFIHFvqk6Q span{fill:#333;color:#333;}#mermaid-svg-oNEtoUAFIHFvqk6Q .node rect,#mermaid-svg-oNEtoUAFIHFvqk6Q .node circle,#mermaid-svg-oNEtoUAFIHFvqk6Q .node ellipse,#mermaid-svg-oNEtoUAFIHFvqk6Q .node polygon,#mermaid-svg-oNEtoUAFIHFvqk6Q .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-oNEtoUAFIHFvqk6Q .rough-node .label text,#mermaid-svg-oNEtoUAFIHFvqk6Q .node .label text,#mermaid-svg-oNEtoUAFIHFvqk6Q .image-shape .label,#mermaid-svg-oNEtoUAFIHFvqk6Q .icon-shape .label{text-anchor:middle;}#mermaid-svg-oNEtoUAFIHFvqk6Q .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-oNEtoUAFIHFvqk6Q .rough-node .label,#mermaid-svg-oNEtoUAFIHFvqk6Q .node .label,#mermaid-svg-oNEtoUAFIHFvqk6Q .image-shape .label,#mermaid-svg-oNEtoUAFIHFvqk6Q .icon-shape .label{text-align:center;}#mermaid-svg-oNEtoUAFIHFvqk6Q .node.clickable{cursor:pointer;}#mermaid-svg-oNEtoUAFIHFvqk6Q .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-oNEtoUAFIHFvqk6Q .arrowheadPath{fill:#333333;}#mermaid-svg-oNEtoUAFIHFvqk6Q .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-oNEtoUAFIHFvqk6Q .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-oNEtoUAFIHFvqk6Q .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-oNEtoUAFIHFvqk6Q .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-oNEtoUAFIHFvqk6Q .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-oNEtoUAFIHFvqk6Q .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-oNEtoUAFIHFvqk6Q .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-oNEtoUAFIHFvqk6Q .cluster text{fill:#333;}#mermaid-svg-oNEtoUAFIHFvqk6Q .cluster span{color:#333;}#mermaid-svg-oNEtoUAFIHFvqk6Q div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-oNEtoUAFIHFvqk6Q .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-oNEtoUAFIHFvqk6Q rect.text{fill:none;stroke-width:0;}#mermaid-svg-oNEtoUAFIHFvqk6Q .icon-shape,#mermaid-svg-oNEtoUAFIHFvqk6Q .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-oNEtoUAFIHFvqk6Q .icon-shape p,#mermaid-svg-oNEtoUAFIHFvqk6Q .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-oNEtoUAFIHFvqk6Q .icon-shape .label rect,#mermaid-svg-oNEtoUAFIHFvqk6Q .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-oNEtoUAFIHFvqk6Q .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-oNEtoUAFIHFvqk6Q .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-oNEtoUAFIHFvqk6Q :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 图标来源
- PM 查询安装的 App
queryIntentActivities()
每个 App 的启动 Activity
自动有图标
2. 用户手动创建
ShortcutManager
自定义快捷方式
3. Widget
AppWidgetManager
桌面小组件
Workspace
图标网格
来源一:扫 PM 找到的启动 Activity:
java
// Launcher3 ------ LauncherModel 加载图标
List<LauncherActivityInfo> activities =
launcherApps.getActivityList(packageName, userHandle);
for (LauncherActivityInfo info : activities) {
// 拿到每个 App 的图标、名称、启动 Intent
Drawable icon = info.getIcon(0);
CharSequence label = info.getLabel();
Intent intent = new Intent(Intent.ACTION_MAIN)
.addCategory(Intent.CATEGORY_LAUNCHER)
.setComponent(info.getComponentName());
}
来源二:ShortcutManager 快捷方式:
java
// 你的 App 里创建快捷方式
ShortcutManager shortcutManager = getSystemService(ShortcutManager.class);
ShortcutInfo shortcut = new ShortcutInfo.Builder(context, "id1")
.setShortLabel("一键支付")
.setLongLabel("打开支付页面")
.setIcon(Icon.createWithResource(context, R.drawable.ic_pay))
.setIntent(new Intent(Intent.ACTION_VIEW, Uri.parse("app://pay")))
.build();
shortcutManager.addDynamicShortcuts(Arrays.asList(shortcut));
你长按微信图标弹出来的那些"扫一扫""收付款"选项------就是 ShortcutManager 的动态快捷方式。
七、多个 Launcher 怎么选
如果你手机上装了多个桌面 App(比如手机自带的、你后来装的第三方桌面),系统怎么决定启动哪个?
#mermaid-svg-rtZVcw6KjJVdKnPE{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-rtZVcw6KjJVdKnPE .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-rtZVcw6KjJVdKnPE .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-rtZVcw6KjJVdKnPE .error-icon{fill:#552222;}#mermaid-svg-rtZVcw6KjJVdKnPE .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-rtZVcw6KjJVdKnPE .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-rtZVcw6KjJVdKnPE .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-rtZVcw6KjJVdKnPE .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-rtZVcw6KjJVdKnPE .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-rtZVcw6KjJVdKnPE .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-rtZVcw6KjJVdKnPE .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-rtZVcw6KjJVdKnPE .marker{fill:#333333;stroke:#333333;}#mermaid-svg-rtZVcw6KjJVdKnPE .marker.cross{stroke:#333333;}#mermaid-svg-rtZVcw6KjJVdKnPE svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-rtZVcw6KjJVdKnPE p{margin:0;}#mermaid-svg-rtZVcw6KjJVdKnPE .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-rtZVcw6KjJVdKnPE .cluster-label text{fill:#333;}#mermaid-svg-rtZVcw6KjJVdKnPE .cluster-label span{color:#333;}#mermaid-svg-rtZVcw6KjJVdKnPE .cluster-label span p{background-color:transparent;}#mermaid-svg-rtZVcw6KjJVdKnPE .label text,#mermaid-svg-rtZVcw6KjJVdKnPE span{fill:#333;color:#333;}#mermaid-svg-rtZVcw6KjJVdKnPE .node rect,#mermaid-svg-rtZVcw6KjJVdKnPE .node circle,#mermaid-svg-rtZVcw6KjJVdKnPE .node ellipse,#mermaid-svg-rtZVcw6KjJVdKnPE .node polygon,#mermaid-svg-rtZVcw6KjJVdKnPE .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-rtZVcw6KjJVdKnPE .rough-node .label text,#mermaid-svg-rtZVcw6KjJVdKnPE .node .label text,#mermaid-svg-rtZVcw6KjJVdKnPE .image-shape .label,#mermaid-svg-rtZVcw6KjJVdKnPE .icon-shape .label{text-anchor:middle;}#mermaid-svg-rtZVcw6KjJVdKnPE .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-rtZVcw6KjJVdKnPE .rough-node .label,#mermaid-svg-rtZVcw6KjJVdKnPE .node .label,#mermaid-svg-rtZVcw6KjJVdKnPE .image-shape .label,#mermaid-svg-rtZVcw6KjJVdKnPE .icon-shape .label{text-align:center;}#mermaid-svg-rtZVcw6KjJVdKnPE .node.clickable{cursor:pointer;}#mermaid-svg-rtZVcw6KjJVdKnPE .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-rtZVcw6KjJVdKnPE .arrowheadPath{fill:#333333;}#mermaid-svg-rtZVcw6KjJVdKnPE .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-rtZVcw6KjJVdKnPE .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-rtZVcw6KjJVdKnPE .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-rtZVcw6KjJVdKnPE .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-rtZVcw6KjJVdKnPE .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-rtZVcw6KjJVdKnPE .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-rtZVcw6KjJVdKnPE .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-rtZVcw6KjJVdKnPE .cluster text{fill:#333;}#mermaid-svg-rtZVcw6KjJVdKnPE .cluster span{color:#333;}#mermaid-svg-rtZVcw6KjJVdKnPE div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-rtZVcw6KjJVdKnPE .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-rtZVcw6KjJVdKnPE rect.text{fill:none;stroke-width:0;}#mermaid-svg-rtZVcw6KjJVdKnPE .icon-shape,#mermaid-svg-rtZVcw6KjJVdKnPE .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-rtZVcw6KjJVdKnPE .icon-shape p,#mermaid-svg-rtZVcw6KjJVdKnPE .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-rtZVcw6KjJVdKnPE .icon-shape .label rect,#mermaid-svg-rtZVcw6KjJVdKnPE .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-rtZVcw6KjJVdKnPE .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-rtZVcw6KjJVdKnPE .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-rtZVcw6KjJVdKnPE :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 设了
没设
是
总是
AMS 构造 CATEGORY_HOME Intent
PMS.resolveActivity()
用户设了默认 Launcher?
直接用默认的
弹出 ResolverActivity
让用户选
用户选了 仅此一次?
这次用它
下次还问
把它设为默认
以后不再问
启动 Launcher Activity
怎么改默认桌面:
bash
# 清除默认桌面设置(下次点 Home 键会弹出选择框)
adb shell pm clear-default-app LAUNCHER_PACKAGE_NAME
# 或者去 设置 → 应用 → 默认应用 → 桌面 → 选另一个
这也是为什么你装完第三方桌面,第一次按 Home 键会弹一个选择框让你选。
八、实战:手写一个简易桌面
这个例子实现一个最简单的 Launcher------列出所有已安装可启动的 App,显示成图标列表,点击就能打开。
AndroidManifest.xml:
xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.simplelauncher">
<activity
android:name=".HomeActivity"
android:exported="true"
android:launchMode="singleTask"
android:stateNotNeeded="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.HOME" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
</manifest>
launchMode="singleTask"和stateNotNeeded="true"是两个关键标记------前者防止桌面上有多个实例,后者告诉 AMS 状态不重要可以自行重建。
布局 activity_home.xml:
xml
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- 可滑动的图标网格 -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_apps"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:padding="16dp" />
</FrameLayout>
HomeActivity.java:
java
public class HomeActivity extends AppCompatActivity {
private RecyclerView recyclerApps;
private AppListAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home);
recyclerApps = findViewById(R.id.recycler_apps);
recyclerApps.setLayoutManager(new GridLayoutManager(this, 4));
adapter = new AppListAdapter(this);
recyclerApps.setAdapter(adapter);
loadApps();
}
private void loadApps() {
PackageManager pm = getPackageManager();
Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
List<ResolveInfo> apps = pm.queryIntentActivities(mainIntent, 0);
adapter.setApps(apps);
}
static class AppListAdapter extends RecyclerView.Adapter<AppListAdapter.ViewHolder> {
private List<ResolveInfo> apps;
private Context context;
AppListAdapter(Context context) {
this.context = context;
this.apps = new ArrayList<>();
}
void setApps(List<ResolveInfo> apps) {
this.apps = apps;
notifyDataSetChanged();
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_app, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
ResolveInfo info = apps.get(position);
PackageManager pm = context.getPackageManager();
holder.icon.setImageDrawable(info.loadIcon(pm));
holder.name.setText(info.loadLabel(pm));
holder.itemView.setOnClickListener(v -> {
Intent intent = pm.getLaunchIntentForPackage(
info.activityInfo.packageName);
if (intent != null) {
context.startActivity(intent);
}
});
}
@Override
public int getItemCount() {
return apps.size();
}
static class ViewHolder extends RecyclerView.ViewHolder {
ImageView icon;
TextView name;
ViewHolder(View itemView) {
super(itemView);
icon = itemView.findViewById(R.id.iv_icon);
name = itemView.findViewById(R.id.tv_name);
}
}
}
}
item_app.xml(每个图标的布局):
xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical"
android:padding="12dp">
<ImageView
android:id="@+id/iv_icon"
android:layout_width="60dp"
android:layout_height="60dp" />
<TextView
android:id="@+id/tv_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="12sp"
android:maxLines="1"
android:ellipsize="end"
android:gravity="center"
android:layout_marginTop="4dp" />
</LinearLayout>
这个简易桌面 200 行代码搞定:列出所有 App → 点击启动 → 声明 CATEGORY_HOME 让自己成为桌面。按 Home 键就能回到这个界面。
九、Launcher 与 Widget
Launcher 除了放图标,还能放 Widget。Widget 通过 AppWidgetHost 管理:
java
// Launcher3 创建 AppWidgetHost 的简化逻辑
public class Launcher extends BaseActivity {
private AppWidgetHost appWidgetHost;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 创建 Widget 宿主------和 AppWidgetManager 通信
appWidgetHost = new AppWidgetHost(this, LauncherAppWidgetHostID);
appWidgetHost.startListening();
}
// 用户长按桌面 → 添加 Widget → 调 AppWidgetPickActivity
// → 选好 Widget → LauncherAppWidgetHostView 创建并添加到 Workspace
}
Widget 的通信链路:
#mermaid-svg-qd1T7Qzc0DHH0JxW{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-qd1T7Qzc0DHH0JxW .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-qd1T7Qzc0DHH0JxW .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-qd1T7Qzc0DHH0JxW .error-icon{fill:#552222;}#mermaid-svg-qd1T7Qzc0DHH0JxW .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-qd1T7Qzc0DHH0JxW .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-qd1T7Qzc0DHH0JxW .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-qd1T7Qzc0DHH0JxW .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-qd1T7Qzc0DHH0JxW .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-qd1T7Qzc0DHH0JxW .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-qd1T7Qzc0DHH0JxW .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-qd1T7Qzc0DHH0JxW .marker{fill:#333333;stroke:#333333;}#mermaid-svg-qd1T7Qzc0DHH0JxW .marker.cross{stroke:#333333;}#mermaid-svg-qd1T7Qzc0DHH0JxW svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-qd1T7Qzc0DHH0JxW p{margin:0;}#mermaid-svg-qd1T7Qzc0DHH0JxW .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-qd1T7Qzc0DHH0JxW .cluster-label text{fill:#333;}#mermaid-svg-qd1T7Qzc0DHH0JxW .cluster-label span{color:#333;}#mermaid-svg-qd1T7Qzc0DHH0JxW .cluster-label span p{background-color:transparent;}#mermaid-svg-qd1T7Qzc0DHH0JxW .label text,#mermaid-svg-qd1T7Qzc0DHH0JxW span{fill:#333;color:#333;}#mermaid-svg-qd1T7Qzc0DHH0JxW .node rect,#mermaid-svg-qd1T7Qzc0DHH0JxW .node circle,#mermaid-svg-qd1T7Qzc0DHH0JxW .node ellipse,#mermaid-svg-qd1T7Qzc0DHH0JxW .node polygon,#mermaid-svg-qd1T7Qzc0DHH0JxW .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-qd1T7Qzc0DHH0JxW .rough-node .label text,#mermaid-svg-qd1T7Qzc0DHH0JxW .node .label text,#mermaid-svg-qd1T7Qzc0DHH0JxW .image-shape .label,#mermaid-svg-qd1T7Qzc0DHH0JxW .icon-shape .label{text-anchor:middle;}#mermaid-svg-qd1T7Qzc0DHH0JxW .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-qd1T7Qzc0DHH0JxW .rough-node .label,#mermaid-svg-qd1T7Qzc0DHH0JxW .node .label,#mermaid-svg-qd1T7Qzc0DHH0JxW .image-shape .label,#mermaid-svg-qd1T7Qzc0DHH0JxW .icon-shape .label{text-align:center;}#mermaid-svg-qd1T7Qzc0DHH0JxW .node.clickable{cursor:pointer;}#mermaid-svg-qd1T7Qzc0DHH0JxW .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-qd1T7Qzc0DHH0JxW .arrowheadPath{fill:#333333;}#mermaid-svg-qd1T7Qzc0DHH0JxW .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-qd1T7Qzc0DHH0JxW .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-qd1T7Qzc0DHH0JxW .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-qd1T7Qzc0DHH0JxW .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-qd1T7Qzc0DHH0JxW .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-qd1T7Qzc0DHH0JxW .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-qd1T7Qzc0DHH0JxW .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-qd1T7Qzc0DHH0JxW .cluster text{fill:#333;}#mermaid-svg-qd1T7Qzc0DHH0JxW .cluster span{color:#333;}#mermaid-svg-qd1T7Qzc0DHH0JxW div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-qd1T7Qzc0DHH0JxW .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-qd1T7Qzc0DHH0JxW rect.text{fill:none;stroke-width:0;}#mermaid-svg-qd1T7Qzc0DHH0JxW .icon-shape,#mermaid-svg-qd1T7Qzc0DHH0JxW .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-qd1T7Qzc0DHH0JxW .icon-shape p,#mermaid-svg-qd1T7Qzc0DHH0JxW .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-qd1T7Qzc0DHH0JxW .icon-shape .label rect,#mermaid-svg-qd1T7Qzc0DHH0JxW .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-qd1T7Qzc0DHH0JxW .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-qd1T7Qzc0DHH0JxW .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-qd1T7Qzc0DHH0JxW :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Widget App
声明 AppWidgetProvider
AppWidgetManager
系统服务
Launcher.AppWidgetHost
在桌面创建 Widget 视图
LauncherAppWidgetHostView
显示在桌面上
Widget 本质上是一个 RemoteViews------Widget App 在自己的进程里跑,Launcher 跨进程渲染它的 RemoteViews。
十、常见踩坑记录
坑 1:Launcher 写了 CATEGORY_HOME 但是不生效
可能是其他 App 已经占了默认桌面,或者你的 Manifest 里少写了 CATEGORY_DEFAULT。
xml
<!-- ❌ 少了 DEFAULT------系统可能不认 -->
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.HOME" />
</intent-filter>
<!-- ✓ 正确 -->
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.HOME" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
坑 2:launchMode 没设 singleTask
如果 Launcher 的 launchMode 不设成 singleTask,连续按 Home 可能创建多个 Launcher 实例,桌面套桌面。
坑 3:桌面图标不自动更新
你装了新 App,桌面没有立刻出现图标。LauncherModel 需要监听 PACKAGE_ADDED 广播:
java
// 注册包安装/卸载广播
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_PACKAGE_ADDED);
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
filter.addDataScheme("package");
registerReceiver(mPackageReceiver, filter);
坑 4:内存不足时 Launcher 被系统杀了
Launcher 本质上也是一个普通 App 进程。内存紧张时 LMK 可能把它杀掉------然后用户按 Home 键桌面重建,体验会有一个白屏。
解决方法:在 AndroidManifest.xml 里加 android:persistent="true"(需要系统签名),或者让 Launcher 在 onSaveInstanceState 里存足够多的状态,重建时快点。
坑 5:Android 11+ QUERY_ALL_PACKAGES 权限
Android 11 之后 PM 的 queryIntentActivities 默认只能返回少量包。要遍历所有已安装 App,需要声明:
xml
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
且 Google Play 对这个权限审核严格,第三方桌面想上架比较难。
十一、总结
Launcher 启动主流程一张图:
#mermaid-svg-yEoJEjWPB23hU25y{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-yEoJEjWPB23hU25y .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-yEoJEjWPB23hU25y .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-yEoJEjWPB23hU25y .error-icon{fill:#552222;}#mermaid-svg-yEoJEjWPB23hU25y .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-yEoJEjWPB23hU25y .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-yEoJEjWPB23hU25y .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-yEoJEjWPB23hU25y .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-yEoJEjWPB23hU25y .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-yEoJEjWPB23hU25y .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-yEoJEjWPB23hU25y .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-yEoJEjWPB23hU25y .marker{fill:#333333;stroke:#333333;}#mermaid-svg-yEoJEjWPB23hU25y .marker.cross{stroke:#333333;}#mermaid-svg-yEoJEjWPB23hU25y svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-yEoJEjWPB23hU25y p{margin:0;}#mermaid-svg-yEoJEjWPB23hU25y .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-yEoJEjWPB23hU25y .cluster-label text{fill:#333;}#mermaid-svg-yEoJEjWPB23hU25y .cluster-label span{color:#333;}#mermaid-svg-yEoJEjWPB23hU25y .cluster-label span p{background-color:transparent;}#mermaid-svg-yEoJEjWPB23hU25y .label text,#mermaid-svg-yEoJEjWPB23hU25y span{fill:#333;color:#333;}#mermaid-svg-yEoJEjWPB23hU25y .node rect,#mermaid-svg-yEoJEjWPB23hU25y .node circle,#mermaid-svg-yEoJEjWPB23hU25y .node ellipse,#mermaid-svg-yEoJEjWPB23hU25y .node polygon,#mermaid-svg-yEoJEjWPB23hU25y .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-yEoJEjWPB23hU25y .rough-node .label text,#mermaid-svg-yEoJEjWPB23hU25y .node .label text,#mermaid-svg-yEoJEjWPB23hU25y .image-shape .label,#mermaid-svg-yEoJEjWPB23hU25y .icon-shape .label{text-anchor:middle;}#mermaid-svg-yEoJEjWPB23hU25y .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-yEoJEjWPB23hU25y .rough-node .label,#mermaid-svg-yEoJEjWPB23hU25y .node .label,#mermaid-svg-yEoJEjWPB23hU25y .image-shape .label,#mermaid-svg-yEoJEjWPB23hU25y .icon-shape .label{text-align:center;}#mermaid-svg-yEoJEjWPB23hU25y .node.clickable{cursor:pointer;}#mermaid-svg-yEoJEjWPB23hU25y .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-yEoJEjWPB23hU25y .arrowheadPath{fill:#333333;}#mermaid-svg-yEoJEjWPB23hU25y .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-yEoJEjWPB23hU25y .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-yEoJEjWPB23hU25y .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-yEoJEjWPB23hU25y .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-yEoJEjWPB23hU25y .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-yEoJEjWPB23hU25y .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-yEoJEjWPB23hU25y .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-yEoJEjWPB23hU25y .cluster text{fill:#333;}#mermaid-svg-yEoJEjWPB23hU25y .cluster span{color:#333;}#mermaid-svg-yEoJEjWPB23hU25y div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-yEoJEjWPB23hU25y .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-yEoJEjWPB23hU25y rect.text{fill:none;stroke-width:0;}#mermaid-svg-yEoJEjWPB23hU25y .icon-shape,#mermaid-svg-yEoJEjWPB23hU25y .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-yEoJEjWPB23hU25y .icon-shape p,#mermaid-svg-yEoJEjWPB23hU25y .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-yEoJEjWPB23hU25y .icon-shape .label rect,#mermaid-svg-yEoJEjWPB23hU25y .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-yEoJEjWPB23hU25y .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-yEoJEjWPB23hU25y .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-yEoJEjWPB23hU25y :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 触发
开机 / 按 Home 键
AMS.startHomeActivityLocked()
构造 CATEGORY_HOME Intent
PMS 解析
找默认 Launcher
Zygote fork 进程
(或复用已有)
Launcher Activity.onCreate()
LauncherModel.startLoader()
在子线程加载
所有可启动 App + 快捷方式 + Widget
回调主线程
bindWorkspace()
铺图标到桌面上
★ 桌面可见
关键源码位置:
| 角色 | 路径 |
|---|---|
| AMS.startHomeActivityLocked | frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java |
| AOSP Launcher3 | packages/apps/Launcher3/ |
| LauncherModel | packages/apps/Launcher3/src/.../LauncherModel.java |
| Workspace | packages/apps/Launcher3/src/.../Workspace.java |
| Launcher 布局数据库 | /data/data/com.android.launcher3/databases/launcher.db |
一句话总结: Launcher 就是一个声明了 CATEGORY_HOME 的普通 App。AMS 在开机完成后和每次按 Home 键时解析这个 Intent,找到默认桌面并启动它。桌面起来后查 PM 拿所有可启动的 App,一个一个把图标铺上去------你看到的工作就完成了。