Android Launcher启动过程

你按 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;} 图标来源

  1. 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,一个一个把图标铺上去------你看到的工作就完成了。

相关推荐
Java面试题总结2 小时前
MySQL EXISTS 详解:存在性判断、NOT EXISTS 与实战示例
android·数据库·mysql
_李小白2 小时前
【android opencv学习笔记】Day 30: 滤波算法之拉普拉斯算子
android·opencv·学习
NiceCloud喜云11 小时前
Opus 4.8 的 Effort Control 怎么选:Low 到 Max 五档策略
android·java·大数据·前端·c++·python·spring
日光明媚14 小时前
一步生成视频!One-Forcing:DMD + 零成本 GAN,训练 200 步超越多步 SOTA
android·开发语言·kotlin
帅次15 小时前
Android 17 开发者实战:核心更新与应用场景落地指南
android·java·ios·android studio·iphone·android jetpack·webview
大鹏说大话15 小时前
SQL 排序与分组实战:解决“分组后取最新数据“
android·java·数据库
搜狐技术产品小编202318 小时前
破局与重构:纯端侧 Android 自动化引擎的尝试与未来推演
android·运维·重构·自动化
码云骑士19 小时前
Android SystemServer启动过程
android·systemserver
weiggle20 小时前
第三篇:可组合函数(Composable)——Compose 的基石
android·前端