欢迎光临
我们一直在努力

第 63 章:打印服务

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()方法执行逻辑:

  • 创建PrintDocumentAdapterDelegate代理用于跨进程通信
  • 通过 Binder IPC 向 PrintManagerImpl 发送打印请求
  • 读取系统通过EXTRA_PRINT_DIALOG_INTENT返回的 IntentSender,启动归属com.android.printspooler包的打印 UI 界面
  • 返回用于跟踪状态的 PrintJob 句柄;打印不可用时返回 null。
  • 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 () 方法校验:

  • Adapter 非空
  • 打印功能未被设备策略禁用
  • 调用用户为当前前台用户或其配置文件用户
  • 打印受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 注册:

  • 监听Settings.Secure.DISABLED_PRINT_SERVICES的 ContentObserver,跟踪用户在设置中禁用的打印服务。
  • 包监听器,检测打印服务应用的安装、卸载、更新。
  • 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;
    }

    关键实现要点:

  • 死亡跟踪:PrintJobForAppCache 监控 Adapter Binder;创建打印任务的应用进程死亡,任务随之清理。
  • PendingIntent:打印对话框通过 IntentSender 拉起,跨进程保证安全上下文。
  • 后台 Activity 限制:PendingIntent 设置MODE_BACKGROUND_ACTIVITY_START_DENIED,防止打印应用借该 Intent 拉起后台 Activity。
  • 初始状态:所有打印任务初始状态STATE_CREATED,默认份数 1。
  • 63.14.1 PrintJobForAppCache

    应用创建打印任务会存入以 appId 为 key 的缓存。两个作用:

  • 任务聚合:getPrintJobInfos()合并缓存与后台处理数据。后台处理程序不保存终态任务;缓存保留终态任务直到应用消亡。
  • 字段剥离:返回给应用时剔除内部标签、高级选项,这些字段仅打印服务可见。
  • // 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。

    赞(0)
    未经允许不得转载:171主机测评 » 第 63 章:打印服务
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址