Android 打印框架提供一套完整机制,用于发现打印机、渲染文档、管理打印任务队列,并将任务下发至物理或虚拟打印机。该框架采用分层架构:系统服务(PrintManagerService)维护每个用户的状态并协调各应用;打印后台处理进程维护打印队列;可插拔打印服务负责与特定打印机或协议通信。
本章从公开 API 讲起,深入剖析系统服务内部实现,涵盖打印任务生命周期、文档渲染、打印机发现以及后台处理程序架构。
63.1 架构概览
打印框架分为四大主要层级:
关键源文件:
| PrintManager.java | frameworks/base/core/java/android/print/,面向客户端 API |
| PrintDocumentAdapter.java | 同上目录,应用文档渲染契约 |
| PrintJobInfo.java | 同上目录,打印任务状态数据结构 |
| PrintJob.java | 同上目录,应用侧打印任务句柄 |
| PrintAttributes.java | 同上目录,页面尺寸、边距、色彩模式 |
| PrinterInfo.java | 同上目录,打印机描述(名称、状态、能力、配置 Intent) |
| flags/flags.aconfig | 同上目录,android.print.flags aconfig 配置声明 |
| PrintedPdfDocument.java | frameworks/base/core/java/android/print/pdf/,PDF 渲染辅助类 |
| PrintService.java | frameworks/base/core/java/android/printservice/,打印服务插件基类 |
| PrinterDiscoverySession.java | 同上目录,打印机发现生命周期管理 |
| PrintManagerService.java | frameworks/base/services/print/java/com/android/server/print/,系统服务入口 |
| UserState.java | 同上目录,单用户打印状态管理 |
| RemotePrintSpooler.java | 同上目录,后台处理进程代理 |
| RemotePrintService.java | 同上目录,打印服务进程代理 |
| PrintSpoolerService.java | frameworks/base/packages/PrintSpooler/src/com/android/printspooler/model/,后台处理进程任务存储 |
| flags/flags.aconfig | frameworks/base/packages/PrintSpooler/,com.android.printspooler.flags aconfig 配置声明 |
63.2 PrintManager — 客户端 API
PrintManager 是访问打印能力的系统服务访问类,注解为@SystemService,依赖PackageManager.FEATURE_PRINTING特性。
// frameworks/base/core/java/android/print/PrintManager.java
@SystemService(Context.PRINT_SERVICE)
@RequiresFeature(PackageManager.FEATURE_PRINTING)
public final class PrintManager {
public static final String PRINT_SPOOLER_PACKAGE_NAME = "com.android.printspooler";
63.2.1 发起打印任务
应用在 Activity 中调用PrintManager.print()发起打印:
// 应用代码
PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE);
PrintJob job = printManager.print("My Document", new MyPrintDocumentAdapter(), null);
print()方法执行逻辑:
Android 17 加固了该执行路径:print()构建ActivityOptions并设置MODE_BACKGROUND_ACTIVITY_START_ALLOWED,允许后台处理程序弹窗由打印请求拉起;同时捕获ActivityNotFoundException,当打印预览 Activity 无法解析时直接返回 null,而不向上抛出异常。
// frameworks/base/core/java/android/print/PrintManager.java
try {
ActivityOptions activityOptions = ActivityOptions.makeBasic()
.setPendingIntentBackgroundActivityStartMode(
ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
mContext.startIntentSender(intent, null, 0, 0, 0, activityOptions.toBundle());
return new PrintJob(printJob, this);
} catch (SendIntentException sie) {
Log.e(LOG_TAG, "Couldn't start print job config activity.", sie);
} catch (ActivityNotFoundException anfe) {
Log.e(LOG_TAG, "Print preview activity not found: ", anfe);
}
return null;
63.2.2 查询打印任务
应用仅可查询自身打印任务,无法访问其他应用任务:
// 获取本应用全部打印任务
List<PrintJob> jobs = printManager.getPrintJobs();
// 检查指定任务状态
for (PrintJob job : jobs) {
PrintJobInfo info = job.getInfo();
if (info.getState() == PrintJobInfo.STATE_COMPLETED) {
// 任务已成功完成
}
}
63.2.3 打印任务状态变更监听器
应用可以注册监听接收打印任务状态变更通知。
// frameworks/base/core/java/android/print/PrintManager.java
private static final int MSG_NOTIFY_PRINT_JOB_STATE_CHANGED = 1;
监听机制基于 Handler 回调,状态变更在主线程分发。
63.2.4 服务选择常量
// frameworks/base/core/java/android/print/PrintManager.java
public static final int ENABLED_SERVICES = 1 << 0;
public static final int DISABLED_SERVICES = 1 << 1;
public static final int ALL_SERVICES = ENABLED_SERVICES | DISABLED_SERVICES;
系统层调用方使用这些常量查询设置中启用 / 禁用的打印服务。
63.3 PrintDocumentAdapter — 渲染契约
PrintDocumentAdapter 是抽象类,由应用实现以提供打印内容,定义应用与打印框架之间严格的生命周期契约。
63.3.1 生命周期

生命周期回调:
// frameworks/base/core/java/android/print/PrintDocumentAdapter.java
public abstract class PrintDocumentAdapter {
public static final String EXTRA_PRINT_PREVIEW = "EXTRA_PRINT_PREVIEW";
// 1.打印开始时调用一次
public void onStart() { /* stub */ }
// 2.打印参数变更时调用(页面尺寸、分辨率等)
public abstract void onLayout(PrintAttributes oldAttributes,
PrintAttributes newAttributes,
CancellationSignal cancellationSignal,
LayoutResultCallback callback,
Bundle extras);
// 3.渲染指定页面输出PDF时调用
public abstract void onWrite(PageRange[] pages,
ParcelFileDescriptor destination,
CancellationSignal cancellationSignal,
WriteResultCallback callback);
// 4.打印结束时调用一次
public void onFinish() { /* stub */ }
}
63.3.2 Layout‑Write 协议
系统与适配器之间基于回调协议交互:

关键规则:
- onLayout()必须调用其中一个回调才算执行完成
- onWrite()必须调用其中一个回调才算执行完成
- 当前生命周期方法未完成前,不会调用下一个生命周期方法
- 适配器必须关闭onWrite()传入的ParcelFileDescriptor
- extras Bundle 中EXTRA_PRINT_PREVIEW标记当前是否为预览模式
63.3.3 取消机制
CancellationSignal参数用于系统请求取消操作:
cancellationSignal.setOnCancelListener(new OnCancelListener() {
@Override
public void onCancel() {
// 停止布局或写入工作
}
});
当用户在布局过程修改打印选项,系统会取消当前布局,发起一次全新布局。
63.3.4 PrintDocumentInfo
布局完成后适配器上报文档元数据:
PrintDocumentInfo info = new PrintDocumentInfo.Builder("document.pdf")
.setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT)
.setPageCount(pageCount)
.build();
callback.onLayoutFinished(info, contentChanged);
contentChanged标志非常关键;若为 false,系统可以复用之前渲染好的页面,跳过onWrite()调用。
63.4 打印任务生命周期
打印任务由 PrintJobInfo 维护 7 种状态流转。
63.4.1 状态常量
// frameworks/base/core/java/android/print/PrintJobInfo.java
public static final int STATE_CREATED = 1; // 打印UI创建任务中
public static final int STATE_QUEUED = 2; // 等待处理
public static final int STATE_STARTED = 3; // 正在打印
public static final int STATE_BLOCKED = 4; // 临时阻塞
public static final int STATE_COMPLETED = 5; // 打印成功(终态)
public static final int STATE_FAILED = 6; // 打印失败
public static final int STATE_CANCELED = 7; // 已取消(终态)
63.4.2 状态机

63.4.3 内部状态分组
系统使用聚合状态常量用于过滤:
| STATE_ANY | 全部状态 | 不过滤 |
| STATE_ANY_VISIBLE_TO_CLIENTS | 除 CREATED 之外全部 | 对创建应用可见 |
| STATE_ANY_ACTIVE | CREATED, QUEUED, STARTED, BLOCKED | 非终态 |
| STATE_ANY_SCHEDULED | QUEUED, STARTED, BLOCKED | 已下发打印服务 |
63.4.4 PrintJob 包装类
PrintJob 为应用提供便捷封装:
// frameworks/base/core/java/android/print/PrintJob.java
public final class PrintJob {
private final @NonNull PrintManager mPrintManager;
private @NonNull PrintJobInfo mCachedInfo;
public void cancel() {
final int state = getInfo().getState();
if (state == PrintJobInfo.STATE_QUEUED
|| state == PrintJobInfo.STATE_STARTED
|| state == PrintJobInfo.STATE_BLOCKED
|| state == PrintJobInfo.STATE_FAILED) {
mPrintManager.cancelPrintJob(mCachedInfo.getId());
}
}
}
对于活跃任务,每次调用getInfo()都会刷新缓存 PrintJobInfo;对于终态(完成 / 取消)任务直接返回缓存,状态不会再变更。
63.5 PrintAttributes — 打印输出描述
PrintAttributes 封装打印内容的格式化参数。
63.5.1 纸张尺寸
通过标准 MediaSize 类定义页面尺寸。
// frameworks/base/core/java/android/print/PrintAttributes.java
// 标准尺寸示例:
MediaSize.ISO_A4 // 210 x 297mm
MediaSize.NA_LETTER // 8.5 x 11 inches
MediaSize.NA_LEGAL // 8.5 x 14 inches
MediaSize.JIS_B5 // 182 x 257mm
内部存储单位为密耳(千分之一英寸)。
63.5.2 色彩与双面模式
// 色彩模式
public static final int COLOR_MODE_MONOCHROME = 1; // 黑白
public static final int COLOR_MODE_COLOR = 2; // 彩色
// 双面模式
public static final int DUPLEX_MODE_NONE = 1; // 单面
public static final int DUPLEX_MODE_LONG_EDGE = 2; // 长边翻转(书本样式)
public static final int DUPLEX_MODE_SHORT_EDGE = 4; // 短边翻转(便签本样式)
63.5.3 分辨率与边距
分辨率定义水平、垂直 DPI(每英寸点数)。边距定义四边最小边距,单位密耳。
63.6 PrintedPdfDocument 实现 PDF 渲染
PrintedPdfDocument 是辅助类,基于 Android Canvas 图形 API 生成 PDF 输出。
// frameworks/base/core/java/android/print/pdf/PrintedPdfDocument.java
public class PrintedPdfDocument extends PdfDocument {
private static final int MILS_PER_INCH = 1000;
private static final int POINTS_IN_INCH = 72;
private final int mPageWidth;
private final int mPageHeight;
private final Rect mContentRect;
63.6.1 坐标体系
完成两套单位转换: 
以 8.5×11 英寸 Letter 纸张为例:
- 宽度:8500 密耳 → 612 点
- 高度:11000 密耳 → 792 点
63.6.2 使用示例
// PrintDocumentAdapter中典型实现
@Override
public void onWrite(PageRange[] pages, ParcelFileDescriptor destination,
CancellationSignal cancel, WriteResultCallback callback) {
PrintedPdfDocument document = new PrintedPdfDocument(context, printAttributes);
for (int pageNum : pagesToWrite) {
PdfDocument.Page page = document.startPage(pageNum);
// 获取Canvas绘制内容
Canvas canvas = page.getCanvas();
drawPageContent(canvas, pageNum);
document.finishPage(page);
}
// 写入文件描述符
document.writeTo(new FileOutputStream(destination.getFileDescriptor()));
document.close();
callback.onWriteFinished(new PageRange[] { PageRange.ALL_PAGES });
}
63.6.3 ContentRect 内容矩形
内容矩形扣除边距,得到实际可绘制区域:
// frameworks/base/core/java/android/print/pdf/PrintedPdfDocument.java
Margins minMargins = attributes.getMinMargins();
final int marginLeft = (int) (((float) minMargins.getLeftMils() / MILS_PER_INCH)
* POINTS_IN_INCH);
// top、right、bottom计算逻辑相同
mContentRect = new Rect(marginLeft, marginTop,
mPageWidth – marginRight, mPageHeight – marginBottom);
63.7 PrintManagerService — 系统服务
PrintManagerService 封装 PrintManagerImpl Binder 服务,对接 SystemService 生命周期。
// frameworks/base/services/print/java/com/android/server/print/PrintManagerService.java
public final class PrintManagerService extends SystemService {
private final PrintManagerImpl mPrintManagerImpl;
@Override
public void onStart() {
publishBinderService(Context.PRINT_SERVICE, mPrintManagerImpl);
}
@Override
public void onUserUnlocking(@NonNull TargetUser user) {
mPrintManagerImpl.handleUserUnlocked(user.getUserIdentifier());
}
@Override
public void onUserStopping(@NonNull TargetUser user) {
mPrintManagerImpl.handleUserStopped(user.getUserIdentifier());
}
}
63.7.1 多用户架构
每个用户拥有独立 UserState 实例,管理打印服务、后台处理程序连接、打印机发现。 PrintManagerImpl 内部持有SparseArray<UserState> mUserStates,以 userId 作为 key。

UserState 在用户解锁时创建,用户停止销毁。
class PrintManagerImpl extends IPrintManager.Stub {
private static final int BACKGROUND_USER_ID = -10;
private final SparseArray<UserState> mUserStates = new SparseArray<>();
63.7.2 权限校验
PrintManagerImpl 的 print () 方法校验:
打印受UserManager.DISALLOW_PRINTING用户限制管控,设备管理员可通过 DevicePolicyManager 关闭打印。
private boolean isPrintingEnabled() {
return !mUserManager.hasUserRestriction(UserManager.DISALLOW_PRINTING,
Binder.getCallingUserHandle());
}
打印被禁用时,print () 通过DevicePolicyManagerInternal.getPrintingDisabledReasonForUser()获取提示文本弹出 Toast;调用 adapter 的 start/finish 释放应用资源,不创建任务直接返回 null。
63.7.3 ContentObserver 与广播接收器
PrintManagerImpl 注册:
63.8 UserState — 单用户打印管理
UserState 是单用户核心协调类,实现三套回调接口:
final class UserState implements
PrintSpoolerCallbacks, // 后台处理生命周期事件
PrintServiceCallbacks, // 打印服务事件
RemotePrintServiceRecommendationServiceCallbacks { // 推荐服务回调
63.8.1 内部状态
// 已绑定活跃打印服务
private final ArrayMap<ComponentName, RemotePrintService> mActiveServices;
// 本机全部已安装打印服务包
private final List<PrintServiceInfo> mInstalledServices;
// 被禁用打印服务集合
private final Set<ComponentName> mDisabledServices;
// 对应用户可见打印任务缓存
private final PrintJobForAppCache mPrintJobForAppCache;
// 打印机发现会话管理器
private PrinterDiscoverySessionMediator mPrinterDiscoverySession;
// 后台处理程序连接代理
private final RemotePrintSpooler mSpooler;
63.8.2 服务发现
用户解锁后,UserState 通过 PackageManager 查询 action 为android.printservice.PrintService的服务组件。
private final Intent mQueryIntent =
new Intent(android.printservice.PrintService.SERVICE_INTERFACE);
Android N 及之后系统持久化存储禁用服务集合而非启用集合:所有已安装打印服务默认启用,组件名出现在Settings.Secure.DISABLED_PRINT_SERVICES(冒号分隔字符串)才视为禁用。 readDisabledPrintServicesLocked()解析配置写入mDisabledServices;writeDisabledPrintServicesLocked()持久化。
旧配置Settings.Secure.ENABLED_PRINT_SERVICES仅用于升级迁移:upgradePersistentStateIfNeeded()读取旧启用列表,换算得到禁用集合,写入禁用配置后清空旧字段,迁移仅执行一次。
63.8.3 服务生命周期管理
服务由 RemotePrintService 代理管理:

打印服务进程崩溃时,RemotePrintService 收到死亡通知,延迟 500ms 调度重启。
// frameworks/base/services/print/java/com/android/server/print/UserState.java
private static final int SERVICE_RESTART_DELAY_MILLIS = 500;
63.9 PrintService — 插件 API
PrintService 是打印服务插件基类。第三方应用(HP 打印服务、Mopria 打印服务等)继承该类,实现对特定打印机支持。
63.9.1 服务声明
打印服务必须在 Manifest 声明权限与 Intent‑Filter:
<service android:name=".MyPrintService"
android:permission="android.permission.BIND_PRINT_SERVICE">
<intent-filter>
<action android:name="android.printservice.PrintService" />
</intent-filter>
<meta-data android:name="android.printservice"
android:resource="@xml/printservice" />
</service>
BIND_PRINT_SERVICE权限保证只有系统可以绑定该服务。
63.9.2 核心回调
// frameworks/base/core/java/android/printservice/PrintService.java
public abstract class PrintService extends Service {
// 系统需要发现打印机时调用
protected abstract PrinterDiscoverySession onCreatePrinterDiscoverySession();
// 打印任务入队就绪可以处理时调用
protected abstract void onPrintJobQueued(PrintJob printJob);
// 用户请求取消打印任务
protected abstract void onRequestCancelPrintJob(PrintJob printJob);
// 系统完成绑定后调用
protected void onConnected() { }
// 系统解绑之前调用
protected void onDisconnected() { }
}
63.9.3 打印任务处理流程

63.9.4 读取打印数据
打印服务通过 PrintDocument 读取后台处理完成的文档:
@Override
protected void onPrintJobQueued(PrintJob printJob) {
printJob.start();
PrintDocument document = printJob.getDocument();
InputStream data = new FileInputStream(
document.getData().getFileDescriptor());
// data为PDF文件,发送给打印机
sendToPrinter(data, printJob.getInfo());
printJob.complete();
}
无论原始内容格式,最终交付给打印服务的数据始终是 PDF。
63.10 打印机发现
打印机发现由 PrinterDiscoverySession 管理,生命周期独立于打印服务。
63.10.1 发现生命周期

63.10.2 关键方法
// frameworks/base/core/java/android/printservice/PrinterDiscoverySession.java
public abstract class PrinterDiscoverySession {
// 系统请求开始发现打印机
public abstract void onStartPrinterDiscovery(List<PrinterId> priorityList);
// 系统请求停止发现
public abstract void onStopPrinterDiscovery();
// 系统要求校验指定打印机
public abstract void onValidatePrinters(List<PrinterId> printerIds);
// 系统需要监听该打印机实时状态
public abstract void onStartPrinterStateTracking(PrinterId printerId);
// 系统不再需要实时状态
public abstract void onStopPrinterStateTracking(PrinterId printerId);
// 会话销毁
public abstract void onDestroy();
// 服务调用上报打印机列表
public final void addPrinters(List<PrinterInfo> printers);
public final void removePrinters(List<PrinterId> printerIds);
}
63.10.3 PrinterInfo 与能力集
PrinterInfo 描述打印机实例:
PrinterInfo printer = new PrinterInfo.Builder(printerId, "My Printer",
PrinterInfo.STATUS_IDLE)
.setDescription("Color Laser Printer")
.setCapabilities(capabilities)
.build();
PrinterCapabilitiesInfo描述打印机硬件能力:纸张尺寸、分辨率、色彩模式、双面模式、最小边距。

63.10.4 priorityList 优先列表
onStartPrinterDiscovery()传入 priorityList,存放用户近期使用打印机,打印服务可优先对这些设备做网络发现。
63.10.5 打印机状态跟踪
用户在打印 UI 选中打印机,系统调用onStartPrinterStateTracking()。打印服务需要持续上报该打印机实时状态(空闲、忙碌、不可用)与能力信息。该懒加载机制避免启动时批量查询全部打印机。
63.11 打印后台处理程序
打印后台处理程序(com.android.printspooler)是独立系统进程,维护打印队列,承载打印预览 UI 界面。
63.11.1 RemotePrintSpooler
RemotePrintSpooler 是系统服务侧访问后台处理进程的代理。
// frameworks/base/services/print/java/com/android/server/print/RemotePrintSpooler.java
final class RemotePrintSpooler {
private static final long BIND_SPOOLER_SERVICE_TIMEOUT =
(Build.IS_ENG) ? 120000 : 10000;
private final ServiceConnection mServiceConnection = new MyServiceConnection();
private IPrintSpooler mRemoteInstance;
63.11.2 带超时远程调用
打印后台服务中需要返回值的调用(获取 / 设置打印任务信息、任务状态、标签,以及自定义打印机图标相关操作),会通过 TimedRemoteCaller 实例执行,用于强制设置调用超时:
// 每种操作各自独立的限时调用器
private final GetPrintJobInfosCaller mGetPrintJobInfosCaller;
private final GetPrintJobInfoCaller mGetPrintJobInfoCaller;
private final SetPrintJobStateCaller mSetPrintJobStatusCaller;
private final SetPrintJobTagCaller mSetPrintJobTagCaller;
而发送后无需应答的单向操作(例如 createPrintJob()、writePrintJobData()、setStatus())不会走限时调用器逻辑,而是直接远程调用 IPrintSpooler 接口实例。
绑定超时时间规则:正式版本为 10 秒;工程调试版本为 120 秒(预留足够时间挂载调试器)。
63.11.3 后台处理绑定生命周期

63.11.4 后台处理回调
后台处理程序通过 PrintSpoolerCallbacks 向系统服务推送任务状态变更。
// frameworks/base/services/print/java/com/android/server/print/RemotePrintSpooler.java
public static interface PrintSpoolerCallbacks {
public void onPrintJobQueued(PrintJobInfo printJob);
public void onAllPrintJobsForServiceHandled(ComponentName printService);
public void onPrintJobStateChanged(PrintJobInfo printJob);
}
63.12 RemotePrintService — 打印服务进程代理
RemotePrintService 管理绑定状态打印服务完整生命周期,实现 DeathRecipient 监听进程死亡。
// frameworks/base/services/print/java/com/android/server/print/RemotePrintService.java
final class RemotePrintService implements DeathRecipient {
private final List<Runnable> mPendingCommands = new ArrayList<>();
private IPrintService mPrintService;
private boolean mBinding;
private boolean mHasActivePrintJobs;
private boolean mHasPrinterDiscoverySession;
63.12.1 延迟命令队列
命令到达时,如果服务尚未完成绑定,存入mPendingCommands队列;绑定成功后批量执行全部积压命令。

63.12.2 进程死亡处理
打印服务进程异常终止时:
// frameworks/base/services/print/java/com/android/server/print/RemotePrintService.java
// 实现 DeathRecipient 死亡监听接口
RemotePrintService 感知进程死亡事件,通过 PrintServiceCallbacks.onServiceDied() 通知 UserState,随后 UserState 延迟 500 毫秒调度重启该打印服务。
63.12.3 被跟踪的打印机
代理对象维护正在被实时监听的打印机列表:
@GuardedBy("mLock")
private List<PrinterId> mTrackedPrinterList;
依靠该列表,服务重启后代理会重新发起打印机状态监听请求,实现服务崩溃后的无感恢复。
63.13 完整打印全流程

63.14 print () 方法内部实现
UserState.print () 实现任务创建核心逻辑:
public Bundle print(@NonNull String printJobName, @NonNull IPrintDocumentAdapter adapter,
@Nullable PrintAttributes attributes, @NonNull String packageName, int appId) {
// 创建打印任务占位对象
final PrintJobInfo printJob = new PrintJobInfo();
printJob.setId(new PrintJobId());
printJob.setAppId(appId);
printJob.setLabel(printJobName);
printJob.setAttributes(attributes);
printJob.setState(PrintJobInfo.STATE_CREATED);
printJob.setCopies(1);
printJob.setCreationTime(System.currentTimeMillis());
// 跟踪任务,创建方进程死亡时清理任务
if (!mPrintJobForAppCache.onPrintJobCreated(adapter.asBinder(), appId, printJob)) {
return null; // 客户端进程已消亡
}
Intent intent = new Intent(PrintManager.ACTION_PRINT_DIALOG);
intent.setData(Uri.fromParts("printjob", printJob.getId().flattenToString(), null));
intent.putExtra(PrintManager.EXTRA_PRINT_DOCUMENT_ADAPTER, adapter.asBinder());
intent.putExtra(PrintManager.EXTRA_PRINT_JOB, printJob);
intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName);
ActivityOptions activityOptions = ActivityOptions.makeBasic()
.setPendingIntentCreatorBackgroundActivityStartMode(
ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED);
IntentSender intentSender = PendingIntent.getActivityAsUser(
mContext, 0, intent, PendingIntent.FLAG_ONE_SHOT
| PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE,
activityOptions.toBundle(), new UserHandle(mUserId)).getIntentSender();
Bundle result = new Bundle();
result.putParcelable(PrintManager.EXTRA_PRINT_JOB, printJob);
result.putParcelable(PrintManager.EXTRA_PRINT_DIALOG_INTENT, intentSender);
return result;
}
关键实现要点:
63.14.1 PrintJobForAppCache
应用创建打印任务会存入以 appId 为 key 的缓存。两个作用:
// frameworks/base/services/print/java/com/android/server/print/UserState.java
public List<PrintJobInfo> getPrintJobInfos(int appId) {
List<PrintJobInfo> cachedPrintJobs = mPrintJobForAppCache.getPrintJobs(appId);
// Note that the print spooler is not storing print jobs that
// are in a terminal state as it is non-trivial to properly update
// the spooler state for when to forget print jobs in terminal state.
// Therefore, we fuse the cached print jobs for running apps (some
// jobs are in a terminal state) with the ones that the print
// spooler knows about (some jobs are being processed).
63.14.2 取消与重启流程
取消打印任务需要协同后台处理程序与打印服务:

失败任务重启逻辑,仅将状态切回 QUEUED:
public void restartPrintJob(@NonNull PrintJobId printJobId, int appId) {
PrintJobInfo printJobInfo = getPrintJobInfo(printJobId, appId);
if (printJobInfo == null || printJobInfo.getState() != PrintJobInfo.STATE_FAILED) {
return;
}
mSpooler.setPrintJobState(printJobId, PrintJobInfo.STATE_QUEUED, null);
}
63.14.3 任务路由分发
后台处理回调onPrintJobQueued()时,UserState 通过打印机 ID 携带的 ComponentName 路由任务到对应打印服务:
// frameworks/base/services/print/java/com/android/server/print/UserState.java
@Override
public void onPrintJobQueued(PrintJobInfo printJob) {
ComponentName printServiceName = printJob.getPrinterId().getServiceName();
RemotePrintService service = mActiveServices.get(printServiceName);
if (service != null) {
service.onPrintJobQueued(printJob);
} else {
// The service is no longer enabled
mSpooler.setPrintJobState(printJob.getId(), PrintJobInfo.STATE_FAILED,
mContext.getString(R.string.reason_service_unavailable));
}
}
用户选择打印机之后,如果打印服务被禁用,任务直接失败提示服务不可用。
63.15 PrintManagerImpl Binder 服务
PrintManagerImpl 内部类处理全部 Binder 调用,做严格安全校验。
63.15.1 用户解析
每一次 API 调用都会解析调用用户并校验权限。
// frameworks/base/services/print/java/com/android/server/print/PrintManagerService.java
final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId);
final int resolvedAppId;
final UserState userState;
synchronized (mLock) {
// Only the current group members can start new print jobs.
if (resolveCallingProfileParentLocked(resolvedUserId) != getCurrentUserId()) {
return null;
}
resolvedAppId = resolveCallingAppEnforcingPermissions(appId);
resolvedPackageName = resolveCallingPackageNameEnforcingSecurity(packageName);
userState = getOrCreateUserStateLocked(resolvedUserId, false);
}
63.15.2 自定义打印机图标安全校验
打印服务提供的自定义打印机图标做用户边界校验,防止跨用户信息泄露。
// frameworks/base/services/print/java/com/android/server/print/PrintManagerService.java
private Icon validateIconUserBoundary(Icon icon, int resolvedCallingId) {
if (icon != null && (icon.getType() == Icon.TYPE_URI
|| icon.getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP)) {
final int iconUserId = ContentProvider.getUserIdFromAuthority(
icon.getUri().getAuthority(), resolvedCallingId);
synchronized (mLock) {
if (resolveCallingProfileParentLocked(iconUserId) != getCurrentUserId()) {
return null; // Block cross-user icon access
}
}
}
return icon;
}
63.15.3 查询打印服务
枚举打印服务需要READ_PRINT_SERVICES权限。
// frameworks/base/services/print/java/com/android/server/print/PrintManagerService.java
public List<PrintServiceInfo> getPrintServices(int selectionFlags, int userId) {
Preconditions.checkFlagsArgument(selectionFlags,
PrintManager.DISABLED_SERVICES | PrintManager.ENABLED_SERVICES);
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.READ_PRINT_SERVICES, null);
63.16 打印服务推荐
Android 提供打印服务推荐系统,当已安装打印服务无法匹配发现的打印机时,打印 UI 展示可安装的打印服务推荐。RemotePrintServiceRecommendationService负责和推荐服务建立连接。
// frameworks/base/services/print/java/com/android/server/print/
// RemotePrintServiceRecommendationService.java
63.17 AIDL 接口
打印框架定义大量 AIDL 接口用于跨进程通信:
| IPrintManager | 应用 → 系统 | 创建、查询、取消打印任务 |
| IPrintDocumentAdapter | 系统 → 应用 | layout、write 回调 |
| IPrintDocumentAdapterObserver | 系统 → 应用 | Adapter 生命周期通知 |
| IPrintSpooler | 系统 → 后台处理 | 后台处理任务管理 |
| IPrintSpoolerCallbacks | 后台处理 → 系统 | 任务状态变更回调 |
| IPrintSpoolerClient | 系统 → 后台处理 | 客户端注册 |
| IPrintService | 系统 → 打印服务 | 打印服务控制 |
| IPrintServiceClient | 打印服务 → 系统 | 打印机、任务更新上报 |
| IPrintJobStateChangeListener | 系统 → 应用 | 任务状态通知 |
| IPrintServicesChangeListener | 系统 → 应用 | 打印服务列表变更通知 |
| IPrinterDiscoveryObserver | 系统 → 应用 | 打印机发现事件 |
| ILayoutResultCallback | 应用 → 系统 | layout 结果返回 |
| IWriteResultCallback | 应用 → 系统 | write 结果返回 |
63.17.1 客户端监听接口
PrintManager 暴露三组监听接口:
// frameworks/base/core/java/android/print/PrintManager.java
// Notified when any print job state changes
public interface PrintJobStateChangeListener {
void onPrintJobStateChanged(PrintJobId printJobId);
}
// Notified when the set of print services changes
@SystemApi
public interface PrintServicesChangeListener {
void onPrintServicesChanged();
}
// Notified when print service recommendations change
@SystemApi
public interface PrintServiceRecommendationsChangeListener {
void onPrintServiceRecommendationsChanged();
}
监听器封装为 Binder 兼容包装类,消息通过主线程 Handler 分发。
// frameworks/base/core/java/android/print/PrintManager.java
mHandler = new Handler(context.getMainLooper(), null, false) {
@Override
public void handleMessage(Message message) {
switch (message.what) {
case MSG_NOTIFY_PRINT_JOB_STATE_CHANGED: {
SomeArgs args = (SomeArgs) message.obj;
PrintJobStateChangeListenerWrapper wrapper =
(PrintJobStateChangeListenerWrapper) args.arg1;
PrintJobStateChangeListener listener = wrapper.getListener();
if (listener != null) {
PrintJobId printJobId = (PrintJobId) args.arg2;
listener.onPrintJobStateChanged(printJobId);
}
args.recycle();
} break;
}
}
};
63.17.2 PrintManager 内部扩展字段
PrintManager 与打印对话框 Activity 通信使用隐藏 Intent 扩展字段:
// frameworks/base/core/java/android/print/PrintManager.java
public static final String ACTION_PRINT_DIALOG = "android.print.PRINT_DIALOG";
public static final String EXTRA_PRINT_DIALOG_INTENT =
"android.print.intent.extra.EXTRA_PRINT_DIALOG_INTENT";
public static final String EXTRA_PRINT_JOB =
"android.print.intent.extra.EXTRA_PRINT_JOB";
public static final String EXTRA_PRINT_DOCUMENT_ADAPTER =
"android.print.intent.extra.EXTRA_PRINT_DOCUMENT_ADAPTER";
public static final int APP_ID_ANY = -2;
APP_ID_ANY用于getGlobalPrintManagerForUser(),创建可以访问全部打印任务的 PrintManager 实例,不受应用 ID 限制。
63.18 PrintFileDocumentAdapter
针对打印已有文件场景,Android 提供PrintFileDocumentAdapter。该适配器读取文件输出打印后台处理程序,应用不需要完整实现 PrintDocumentAdapter 全部契约。 文件路径:frameworks/base/core/java/android/print/PrintFileDocumentAdapter.java
63.19 线程模型
打印框架线程划分,避免阻塞 UI:
| PrintManager 回调 | 主线程 | 向应用分发状态变更 |
| PrintDocumentAdapter.onLayout() | 主线程 | 应用页面布局 |
| PrintDocumentAdapter.onWrite() | 主线程 | 应用 PDF 渲染 |
| PrintManagerImpl 业务 | Binder 线程 | 处理服务侧请求 |
| RemotePrintSpooler 调用 | 后台线程 | 后台处理 IPC,可能阻塞 |
| RemotePrintService 绑定 | 后台线程 | 服务绑定操作 |
| UserState 状态管理 | mLock 同步保护 | 线程安全访问状态 |
文档明确警告:RemotePrintSpooler 调用存在阻塞;持有主线程锁的情况下禁止调用,否则会死锁。
63.20 安全模型
打印框架设置多层安全边界。
63.20.1 权限要求
| BIND_PRINT_SERVICE | 仅系统可以绑定打印服务 |
| INTERACT_ACROSS_USERS_FULL | 跨用户打印管理 |
| FEATURE_PRINTING | 设备硬件特性,设备必须支持打印 |
63.20.2 应用隔离
应用仅能查看自身打印任务。UserState 中PrintJobForAppCache按应用 ID 隔离缓存任务。
// frameworks/base/services/print/java/com/android/server/print/UserState.java
private final PrintJobForAppCache mPrintJobForAppCache = new PrintJobForAppCache();
63.20.3 设备策略集成
企业设备管理员通过设置UserManager.DISALLOW_PRINTING用户限制关闭打印。isPrintingEnabled()校验该限制;打印被禁用时,print()不会创建任务;同时通过DevicePolicyManagerInternal读取管理员填写的禁用提示文本弹出 Toast。
// frameworks/base/services/print/java/com/android/server/print/PrintManagerService.java
if (!isPrintingEnabled()) {
DevicePolicyManagerInternal dpmi =
LocalServices.getService(DevicePolicyManagerInternal.class);
CharSequence disabledMessage = dpmi.getPrintingDisabledReasonForUser(callingUserId);
if (disabledMessage != null) {
Toast.makeText(mContext, Looper.getMainLooper(), disabledMessage,
Toast.LENGTH_LONG).show();
}
// Drive the adapter through start()/finish() and return null.
}
63.21 打印服务调试
63.21.1 Shell 命令
PrintShellCommand 类实现了两条 cmd print 子命令,二者均用于控制系统是否可以绑定由即时应用发布的打印服务:
# 读取某个用户的即时应用绑定策略(默认:系统用户 USER_SYSTEM)
$ adb shell cmd print get-bind-instant-service-allowed [–user <USER_ID>]
# 设置即时应用绑定策略
$ adb shell cmd print set-bind-instant-service-allowed [–user <USER_ID>] true|false
想要完整查看打印运行时状态,可以使用 dumpsys,它会输出每个 UserState 下已安装服务、活跃服务、打印后台处理程序绑定信息以及缓存打印任务:
# 输出打印管理器状态(文本格式)
$ adb shell dumpsys print
# 以 Protobuf 格式导出,用于结构化分析
$ adb shell dumpsys print –proto
PrintManagerService 中的 dumpsys print 处理逻辑(见 63.7 节)会在 mLock 锁保护下快照每个用户的 UserState 列表,再通过 DualDumpOutputStream 完成数据输出;输出目标可以是 IndentingPrintWriter(文本模式)或是 ProtoOutputStream(–proto 参数模式)。
63.21.2 日志
打印框架的类不会调用 Log.isLoggable(),因此常用命令 adb shell setprop log.tag.<TAG> VERBOSE 对其无效。 详细日志由编译期常量控制:PrintManager、RemotePrintSpooler、RemotePrintService、UserState 各自定义 DEBUG 标记,默认编译值为 false;PrintManagerService 完全没有调试开关。
// frameworks/base/core/java/android/print/PrintManager.java
private static final boolean DEBUG = false;
如果要输出额外调试日志,需要修改目标类的 DEBUG = true,重新编译框架。 正式固件下可用两种排查手段:dumpsys print(63.21.1 小节),以及过滤 logcat 中框架永久输出的错误、警告日志。
63.21.3 Proto 结构化导出
打印框架支持基于 protobuf 的数据导出,用于结构化解析:
// frameworks/base/services/print/java/com/android/server/print/UserState.java
// 使用 PrintUserStateProto、CachedPrintJobProto、InstalledPrintServiceProto、
// PrinterDiscoverySessionProto 完成结构化转储
63.22 关键常量参考
| PRINT_SPOOLER_PACKAGE_NAME | com.android.printspooler | PrintManager.java |
| BIND_SPOOLER_SERVICE_TIMEOUT | 10 000 ms(eng 版本:120 000 ms) | RemotePrintSpooler.java |
| SERVICE_RESTART_DELAY_MILLIS | 500 ms | UserState.java |
| MILS_PER_INCH | 1000 | PrintedPdfDocument.java |
| POINTS_IN_INCH | 72 | PrintedPdfDocument.java |
| COMPONENT_NAME_SEPARATOR | : | UserState.java |
| BACKGROUND_USER_ID | -10 | PrintManagerImpl |
| Service action | android.printservice.PrintService | PrintService.java |
| Meta‑data key | android.printservice | PrintService.java |
63.23 打印机设置界面(Android 17)
Android 17 允许打印服务为打印机提供设置 Activity,在此之前仅支持信息 Activity。该特性的典型场景:框架可以发现打印机,但用户必须完成一次性设置操作后才能打印(例如认领打印机、填写凭证、安装厂商配置文件)。该功能由 aconfig 标志 android.print.flags.enable_setup_activity 控制。
# frameworks/base/core/java/android/print/flags/flags.aconfig
flag {
name: "enable_setup_activity"
namespace: "printing"
description: "Enable PrintService implementations to provide a printer setup activity"
is_exported: true
}
63.23.1 PrinterInfo 中的设置 Intent
PrinterInfo 新增可空成员 mSetupIntent(PendingIntent),与已有的 mInfoIntent 并列。打印服务通过 PrinterInfo.Builder.setSetupIntent() 赋值:
// frameworks/base/core/java/android/print/PrinterInfo.java
@FlaggedApi(Flags.FLAG_ENABLE_SETUP_ACTIVITY)
public @NonNull Builder setSetupIntent(@NonNull PendingIntent setupIntent) {
mSetupIntent = Objects.requireNonNull(setupIntent);
return this;
}
所有涉及该字段的代码路径 —— 构造函数、Parcel 序列化反序列化、hashCode()、equals()、toString()—— 均包裹在 if (Flags.enableSetupActivity()) 判断内。只有该标志开启时,这个额外的 PendingIntent 才会写入 / 读出 Parcel,保证与标志关闭时编译的打印服务保持跨进程兼容。访问器 getSetupIntent() 被标记为 @hide:仅框架内部打印 UI 允许拉起设置界面;第三方应用通过 API 拿到 PrinterInfo 对象后,无法启动该设置界面。
63.23.2 打印后台处理程序如何拦截打印直到设置完成
打印对话框(打印后台处理程序内的 PrintActivity)将携带设置 Intent 的打印机视为不可打印状态。仅当功能标志开启,并且选中打印机携带设置 Intent 时,needsSetup() 返回 true。
// frameworks/base/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
private static boolean needsSetup(PrinterInfo printer) {
return android.print.flags.Flags.enableSetupActivity()
&& printer != null
&& printer.getSetupIntent() != null;
}
当用户发起打印,setupAndPrint() 首先调用 needsSetup()。如果需要设置,则以带返回结果的方式启动打印服务提供的设置 PendingIntent(而不是直接确认打印任务);打印后台处理程序使用 MODE_BACKGROUND_ACTIVITY_START_ALLOWED 权限拉起该 Activity。只有无需设置、或者设置流程完成后,才会进入 confirmPrint()。

一次成功的配置结果并不会直接提交打印任务至后台服务。onSetupActivityResult() 仅接收配置页面返回的打印机对象(用户在配置流程中有可能更换为另一台备选打印机),基于该打印机的能力更新打印参数,随后返回打印弹窗界面。 之后用户再次点击打印;此时打印机无需再执行配置流程,打印假脱机服务会直接进入 confirmPrint() 完成任务确认提交。
63.24 打印遥测(Android 17)
Android 17 在打印后台处理程序中新增基于 statsd 的结构化指标,平台可采集打印结果、设备发现行为、UI 交互数据。全部日志输出受标志 com.android.printspooler.flags.printing_telemetry 控制。
# frameworks/base/packages/PrintSpooler/flags/flags.aconfig
flag {
name: "printing_telemetry"
namespace: "printing"
description: "Metrics tracking final print job status, printer discovery, printer capabilities, and major UI actions."
}
63.24.1 Statsd 原子数据项
原子定义放在独立扩展 proto 文件,由 printspooler 模块输出埋点:
# frameworks/proto_logging/stats/atoms/printing/printing_extension_atoms.proto
FrameworkPrintJob (1071) – 任务最终状态 + 属性
FrameworkPrinterDiscovery (1072) – 发现的打印机 + 能力集
FrameworkMainPrintUiLaunched (1073) – 打印对话框打开
FrameworkAdvancedOptionsUiLaunched (1074) – 高级选项界面打开
FrameworkPrintJob 记录任务终态(完成 / 失败 / 取消)、色彩模式、纸张尺寸、水平 / 垂直 DPI、纸张方向、双面模式、文档类型、是否输出 PDF、页数、打印服务 UID。 FrameworkPrinterDiscovery 记录发现服务 UID,以及打印机支持的色彩模式、纸张尺寸、双面模式。 另外 4 个 Bips* 原子(1075‑1078)来自内置打印服务 builtinprintservice,而非打印后台处理程序。
63.24.2 事件埋点位置
打印任务到达最终状态时,PrintSpoolerService.logPrintJobFinalState() 输出 FrameworkPrintJob。该方法解析打印服务 UID,读取 PrintAttributes、PrintDocumentInfo 可选属性,交由异步日志器完成上报。
// frameworks/base/packages/PrintSpooler/src/com/android/printspooler/model/PrintSpoolerService.java
private void logPrintJobFinalState(PrinterId printerId, PrintJobInfo printJob) {
if (!Flags.printingTelemetry()) {
return;
}
// … 解析 serviceUId,读取 PrintAttributes + PrintDocumentInfo …
StatsAsyncLogger.INSTANCE.PrintJob(serviceUId, state, colorMode, size,
resolution, duplexMode, docType, savedPdf, pageCount);
}
Proto 注释标明各原子对应的源码位置:FrameworkPrintJob 在 PrintSpoolerService.java,FrameworkPrinterDiscovery 在 PrinterDiscoverySession.java,两个 UI 埋点在 PrintActivity.java。Android17 新增若干供日志器使用的访问接口,用于从 PrintAttributes、PrintDocumentInfo 获取纸张尺寸、文档类型。
63.25 打印后台处理程序不再全用户预装(Android 17)
旧版本系统默认每个用户都存在 com.android.printspooler。Android17 修改预装策略,改用 install‑in‑user‑type 白名单:打印后台处理程序仅安装在白名单用户类型(完整用户、克隆配置文件、托管配置文件、私有配置文件),不再无条件对全部用户安装。
<!– build/make/target/product/sysconfig/preinstalled-packages-platform-handheld-system.xml –>
<install-in-user-type package="com.android.printspooler">
<install-in user-type="FULL" />
<install-in user-type="android.os.usertype.profile.CLONE" />
<install-in user-type="android.os.usertype.profile.MANAGED" />
<install-in user-type="android.os.usertype.profile.PRIVATE" />
</install-in-user-type>
既然部分用户下打印后台处理程序可能不存在,所有和它交互的代码必须做兼容处理。典型例子是 dumpsys print:旧实现会对每个用户状态调用 RemotePrintSpooler.dump(),当该用户未安装打印后台处理程序时会报错。Android17 增加安装状态检查,条件不满足时直接跳过转储。
// frameworks/base/services/print/java/com/android/server/print/RemotePrintSpooler.java
public void dump(@NonNull DualDumpOutputStream dumpStream) {
synchronized (mLock) {
if (!isInstalled()) {
return;
}
// … 写入 is_destroyed / is_bound …
}
}
private boolean isInstalled() {
try {
mContext.createPackageContextAsUser(PRINT_SPOOLER_PACKAGE_NAME, 0, mUserHandle);
return true;
} catch (PackageManager.NameNotFoundException e) {
return false;
} catch (Exception e) {
Slog.e(LOG_TAG, "Failed to check if print spooler is installed", e);
return false;
}
}
isInstalled() 使用 createPackageContextAsUser() 在当前代理所属用户 mUserHandle 探测打印后台处理程序包是否存在。返回 false 时跳过该用户的转储逻辑,因此即使用户空间缺少打印后台处理程序,adb shell dumpsys print 命令依旧可以正常执行。
实操练习
使用搭载 Android 17 的真机或模拟器观察打印框架行为。
1. 查看实时打印状态
打开任意支持打印的应用(Chrome、文件管理、相册),发起一次打印操作,导出框架运行状态:
adb shell dumpsys print
adb shell dumpsys print –proto > print_state.pb
查看按用户隔离的 UserState 数据段、已安装 / 正在运行的打印服务,以及缓存的打印任务。 在存在多用户的设备上执行该命令:即便部分用户下没有运行打印后台服务,这条指令也不会执行失败(参见 63.25 小节)。
2. 观测禁用型服务管理机制
列出打印服务,在系统设置中启用 / 关闭某一项打印服务,读取持久保存该配置的安全设置项:
adb shell settings get secure disabled_print_services
在设置页禁用一个打印服务,可以看到对应的组件名会出现在冒号分隔的字符串列表中;启用列表保持为空(参见 63.8.2 小节)。
3. 追踪单个打印任务完整生命周期
跟踪打印任务从 STATE_CREATED → STATE_QUEUED,直至最终结束状态。 打印框架的详细日志由编译期常量 DEBUG = false 控制(63.21.2 小节),setprop log.tag.* 无法开启调试日志。在任务流转过程中轮询打印状态,观察缓存任务的状态字段变更,同时查看框架无条件输出的 logcat 日志:
adb shell dumpsys print | grep -A3 "print jobs"
adb logcat | grep -i print
如果使用自行编译的固件,修改 UserState.java 与 RemotePrintSpooler.java 将 DEBUG 设置为 true,即可打印每一次状态切换日志。
4. 切换 Android 17 新增功能开关
查看打印模块配置项的启用状态:
adb shell device_config get printing android.print.flags.enable_setup_activity
adb shell device_config get printing com.android.printspooler.flags.printing_telemetry
aconfig 功能开关对应的 DeviceConfig 键值需要携带包命名空间(android.print.flags、com.android.printspooler.flags)。新版固件可以直接使用下面命令过滤打印相关开关:
adb shell aflags list | grep printing
开启 printing_telemetry 后执行一次完整打印,校验系统是否上报 FrameworkPrintJob 埋点事件(63.24 小节)。
5. 开发最简自定义打印服务
实现一个 PrintService 的派生类: 在 onCreatePrinterDiscoverySession() 上报一台虚拟打印机,在 onPrintJobQueued() 完成打印任务。 通过 PrinterInfo.Builder.setSetupIntent() 绑定配置跳转意图,观察打印弹窗会强制进入配置流程,校验完成后才允许提交打印任务(63.23 小节)。
小结
Android 打印框架分为四层:客户端 API(PrintManager)、系统服务(PrintManagerService,内部维护每个用户独立 UserState)、打印后台处理程序进程 com.android.printspooler、可插拔打印服务 PrintService。
应用与打印框架之间的 PrintDocumentAdapter 约定保证内容可以按照打印参数重新排版,框架始终要求输出 PDF。打印任务存在七态生命周期:CREATED → QUEUED → STARTED → COMPLETED/FAILED/CANCELED,BLOCKED 作为中间状态,精细追踪打印进度。
打印机发现由 PrinterDiscoverySession 实现,支持懒加载能力集、实时更新打印机状态。会话机制保证打印服务仅在系统需要时执行高开销的网络发现。
打印后台处理程序与打印服务代理(RemotePrintSpooler、RemotePrintService)封装跨进程通信复杂逻辑:绑定生命周期、超时、崩溃恢复、延迟任务入队。多用户架构实现用户之间完全隔离,同时共享底层框架基础设施。
Android17 对各层做了增强:打印服务可以为单台打印机提供设置 Activity(enable_setup_activity),打印后台处理程序拉起设置界面,设置完成前拦截打印;新增遥测模块(printing_telemetry)输出 statsd 结构化原子,记录任务结果、打印机发现、打印对话框交互行为。服务启用禁用状态依旧保存在 Settings.Secure.DISABLED_PRINT_SERVICES,旧版启用列表仅用于一次性数据迁移。打印后台处理程序不再预装于全部用户空间;因此 dumpsys print 等框架代码增加安装状态判断;打印对话框无法解析时,PrintManager.print() 返回 null,不再抛出 ActivityNotFoundException。






