天天看點

Android系統列印服務插件printservice開發一 簡介二 主要類介紹三 列印服務插件的工作流程四 系統列印服務輸出的資料五 列印服務插件初步編寫六 總結

一 簡介

從Android4.4開始,系統加入了列印相關的API,可以通過系統列印服務實作列印。對于需要使用列印功能的APP可參考官方教程接入列印服務。

Printing Content https://developer.android.com/training/printing/index.html

這不是本文的内容,本文介紹列印機廠商如何使自己的列印機接入android,即編寫自己的列印插件接入android列印服務。且僅介紹接入部分,與列印機如何連接配接不在本文範圍之内。

系統列印服務架構代碼位于

android.printservice

包中。系統并沒有實作具體列印功能,需要列印機廠商制作插件接入系統列印服務之後,自行實作。本文基于Android API Reference和以下兩個github上的開源項目研究而來。兩個參考項目如下:

  • android-print-plugin-cups

    首頁: https://github.com/pelya/android-print-plugin-cups

  • JfCupsPrintService

    首頁: https://github.com/mretallack/JfCupsPrintService

通過實驗,初步實作了系統列印服務的接入(添加列印機)和模拟列印(将要列印的檔案輸出)。

二 主要類介紹

android.printservice

中,我們可以知道主要有四個類:

  • PrintDocument

    表示待列印檔案,裡面存放有檔案的大小等資訊和檔案内容。
  • PrinterDiscoverySession

    用于發現列印機,整個發現列印機和列印機狀态更新的過程在該類裡進行。
  • PrintJob

    代表一個列印任務。
  • PrintService

    接入系統列印的關鍵Service。

PrinterDiscoverySession 由 PrintService 建立,通過 onCreatePrinterDiscoverySession() 函數傳回給系統。

PrintJob 由需要列印的APP建立,發送給 PrintService 。

PrintDocument 存放在 PrintJob 裡面,被一同發過來。

和列印相關的類的更多詳細參考見:Android_Printservice_API_部分翻譯.md 。

三 列印服務插件的工作流程

1 列印機發現過程

當使用者在設定裡開啟你的列印服務插件和進入系統列印服務界面時,系統會調用 PrinterDiscoverySession 裡的 onStartPrinterDiscovery(List priorityList) 函數,通知你的插件查找列印機。具體查找方式需要自己實作,可能是查找USB接口,可能是搜尋網絡。系統隻管結果,你通過調用其父類的 addPrinters() 方法将列印機添加進去。列印機是放在List數組裡傳入。

當使用者離開上述列印插件的界面時,系統會調用 onStopPrinterDiscovery() 函數,表示插件可以停止尋找列印機了。

另外,在自定義的 addPrintersActivity 中,系統不會自動觸發列印機尋找過程,需要自行處理。

2 列印機選擇過程

當使用者通過一些有列印功能的APP調用系統列印服務時,如果選擇了你的插件的列印機,那麼系統會調用 PrinterDiscoverySession 裡的 onStartPrinterStateTracking(PrinterId printerId) 方法。這裡系統主要希望得到列印機的

PrinterCapabilitiesInfo

和狀态,裡面包括列印機支援的紙張大小,以及色彩等詳細功能參數。

比如:如果沒有addMediaSize(PrintAttributes.MediaSize.ISO_A4, false),那麼使用者就不能選擇A4大小進行列印。後面的false表示是否設為預設值。

列印機有STATUS_BUSY、STATUS_IDLE、STATUS_UNAVAILABLE三種狀态,隻有列印機處于STATUS_IDLE時,系統才允許使用該列印機。

列印機參數直接展現在系統列印服務界面,隻可以選擇支援的參數,比如選擇紙張的大小為A4。

同樣,當使用者離開該界面或者選擇其他列印機時,系統會調用 onStopPrinterStateTracking(PrinterId printerId) 函數,來告訴插件不用再提供列印機的資訊了。

3 列印過程

當使用者在剛剛的系統列印服務界面點選右上角的列印按鈕時,系統會調用列印機所屬的 PrintService 裡的 onPrintJobQueued(PrintJob printJob) 方法,插件需要處理該 PrintJob 。首先需要通過 PrintJob.isQueued() 判斷,該PrintJob是否準備好列印,傳回true代表可以列印。然後可以通過 PrintJob.getDocument() 獲得要列印的文檔,這裡面的資料可以通過 PrintDocument.getData() 讀取。開始列印的時候,調用PrintJob.start()标記開始狀态。當列印成功時,調用 PrintJob.complete() 标記列印成功。或者列印失敗時,調用 PrintJob.fail( String) 标記失敗。

注意:一定要對PrintJob進行狀态标記,包括開始或者成功失敗。如果什麼都不标記,系統會一直在工作列提示該任務列印中,并且該列印機不可列印其他任務,處于準備中。如果任務結束不标記成功或者失敗,一段時間之後,系統會自動将該任務标記為失敗,并且列印機狀态自動變為不可用。

四 系統列印服務輸出的資料

通過編寫DEMO測試,發現android系統列印服務輸出的資料是pdf 1.4的格式,無論檔案内容是照片還是文檔,都會統一轉換為pdf 1.4。

五 列印服務插件初步編寫

1 列印服務插件的聲明

一個列印服務和其他任何服務一樣,需要在AndroidManifest.xml裡聲明。但是它還必須處理action為android.printservice.PrintService的Intent。這個intent聲明失敗會導緻系統忽略該列印服務。另外,一個列印服務必須請求android.permission.BIND_PRINT_SERVICE權限,來保證隻有系統能綁定(bind)它。聲明這個失敗會導緻系統忽略這個列印服務。

一個列印服務可通過自定義設定頁面(setting activity)進行配置,該activity提供自定義設定功能。還有一個添加列印機的activity可以手動添加列印機,供應商名稱等等。系統負責在适當的時候啟動設定和添加列印機的activities。

一個列印服務在聲明的時候,要在mainfest裡提供一條 android:name=”android.printservice” 的 meta-data,這是指定上述activities的方式。

AndroidManifest.xml檔案如下:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.testprintservice">

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.START_PRINT_SERVICE_CONFIG_ACTIVITY" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <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>

        <activity
            android:name=".SettingsActivity"
            android:exported="true"
            android:label="@string/settings_activity_label" />
        <activity
            android:name=".AddPrintersActivity"
            android:exported="true"
            android:label="@string/add_pritners_activity_label">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name=".AdvancedPrintOptionsActivity"
            android:label="@string/advanced_print_options_activity_label"
            android:exported="true"></activity>
    </application>

</manifest>
           

android:resource="@xml/printservice"

對應的檔案為printservice.xml。

這裡面指定的settingsActivity在列印插件開啟界面右上角的菜單裡,用于配置插件。

addPrintersActivity除了在列印插件開啟界面的菜單裡,在列印檔案時添加列印機裡也會被觸發,這個activity用來自定義添加列印機。

advancedPrintOptionsActivity則是在列印檔案的界面上點選更多箭頭裡出現的MORE OPTIONS選項觸發,這個activity用配置列印機的跟多資訊。當然這是可選的操作,也可以沒有這個activity。

printservice.xml檔案内容如下所示:

<?xml version="1.0" encoding="utf-8"?>
<print-service xmlns:android="http://schemas.android.com/apk/res/android"
    android:vendor="example"
    android:settingsActivity="com.example.testprintservice.SettingsActivity"
    android:addPrintersActivity="com.example.testprintservice.AddPrintersActivity"
     android:advancedPrintOptionsActivity="com.example.testprintservice.AdvancedPrintOptionsActivity"
    >
</print-service>
           

2 PrintService實作類編寫

在這裡的 onPrintJobQueued 方法中,直接将需要列印的資料輸出為檔案。存放在APP根目錄裡的files檔案夾。

package com.example.testprintservice;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.print.PrintJobInfo;
import android.printservice.PrintDocument;
import android.printservice.PrintJob;
import android.printservice.PrintService;
import android.printservice.PrinterDiscoverySession;
import android.util.Log;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;

public class MyPrintService extends PrintService {

    private static final String TAG = "MyPrintService";

    @Override
    protected PrinterDiscoverySession onCreatePrinterDiscoverySession() {
        Log.d(TAG, "onCreatePrinterDiscoverySession()");
        return new MyPrintDiscoverySession(this);
    }

    @Override
    protected void onRequestCancelPrintJob(PrintJob printJob) {
        Log.d(TAG, "onRequestCancelPrintJob()");
        printJob.cancel();
    }

    @Override
    protected void onPrintJobQueued(PrintJob printJob) {
        Log.d(TAG, "onPrintJobQueued()");
        PrintJobInfo printjobinfo = printJob.getInfo();
        PrintDocument printdocument = printJob.getDocument();
        if (printJob.isQueued()) {
            return;
        }
        printJob.start();

        String filename = "docu.pdf";
        File outfile = new File(this.getFilesDir(), filename);
        outfile.delete();
        FileInputStream file = new ParcelFileDescriptor.AutoCloseInputStream(printdocument.getData());
        //建立一個長度為1024的記憶體空間
        byte[] bbuf = new byte[];
        //用于儲存實際讀取的位元組數
        int hasRead = ;
        //使用循環來重複讀取資料
        try {
            FileOutputStream outStream = new FileOutputStream(outfile);
            while ((hasRead = file.read(bbuf)) > ) {
                //将位元組數組轉換為字元串輸出
                //System.out.print(new String(bbuf, 0, hasRead));
                outStream.write(bbuf);
            }
            outStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            //關閉檔案輸出流,放在finally塊裡更安全
            try {
                file.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        printJob.complete();
    }
}
           

3 PrinterDiscoverySession實作類編寫

package com.example.testprintservice;

import android.print.PrintAttributes;
import android.print.PrinterCapabilitiesInfo;
import android.print.PrinterId;
import android.print.PrinterInfo;
import android.printservice.PrinterDiscoverySession;
import android.util.Log;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by bboxh on 2016/3/14.
 */
public class MyPrintDiscoverySession extends PrinterDiscoverySession {
    private static final String TAG = "MyPrintDiscoverySession";
    private final MyPrintService myPrintService;

    public MyPrintDiscoverySession(MyPrintService myPrintService) {
        Log.d(TAG, "MyPrintDiscoverySession()");
        this.myPrintService = myPrintService;
    }

    @Override
    public void onStartPrinterDiscovery(List<PrinterId> priorityList) {
        Log.d(TAG, "onStartPrinterDiscovery()");
        List<PrinterInfo> printers = this.getPrinters();
        String name = "printer1";
        PrinterInfo myprinter = new PrinterInfo
                .Builder(myPrintService.generatePrinterId(name), name, PrinterInfo.STATUS_IDLE)
                .build();
        printers.add(myprinter);
        addPrinters(printers);
    }

    @Override
    public void onStopPrinterDiscovery() {
        Log.d(TAG, "onStopPrinterDiscovery()");
    }

    /**
     * 确定這些列印機存在
     * @param printerIds
     */
    @Override
    public void onValidatePrinters(List<PrinterId> printerIds) {
        Log.d(TAG, "onValidatePrinters()");
    }

    /**
     * 選擇列印機時調用該方法更新列印機的狀态,能力
     * @param printerId
     */
    @Override
    public void onStartPrinterStateTracking(PrinterId printerId) {
        Log.d(TAG, "onStartPrinterStateTracking()");
        PrinterInfo printer = findPrinterInfo(printerId);
        if (printer != null) {
            PrinterCapabilitiesInfo capabilities =
                    new PrinterCapabilitiesInfo.Builder(printerId)
                            .setMinMargins(new PrintAttributes.Margins(, , , ))
                            .addMediaSize(PrintAttributes.MediaSize.ISO_A4, true)
                            //.addMediaSize(PrintAttributes.MediaSize.ISO_A5, false)
                            .addResolution(new PrintAttributes.Resolution("R1", "200x200", , ), false)
                            .addResolution(new PrintAttributes.Resolution("R2", "300x300", , ), true)
                            .setColorModes(PrintAttributes.COLOR_MODE_COLOR
                                            | PrintAttributes.COLOR_MODE_MONOCHROME,
                                    PrintAttributes.COLOR_MODE_MONOCHROME)
                            .build();

            printer = new PrinterInfo.Builder(printer)
                    .setCapabilities(capabilities)
                    .setStatus(PrinterInfo.STATUS_IDLE)
            //        .setDescription("fake print 1!")
                    .build();
            List<PrinterInfo> printers = new ArrayList<PrinterInfo>();

            printers.add(printer);
            addPrinters(printers);
        }
    }

    @Override
    public void onStopPrinterStateTracking(PrinterId printerId) {
        Log.d(TAG, "onStopPrinterStateTracking()");
    }

    @Override
    public void onDestroy() {
        Log.d(TAG, "onDestroy()");
    }

    private PrinterInfo findPrinterInfo(PrinterId printerId) {
        List<PrinterInfo> printers = getPrinters();
        final int printerCount = getPrinters().size();
        for (int i = ; i < printerCount; i++) {
            PrinterInfo printer = printers.get(i);
            if (printer.getId().equals(printerId)) {
                return printer;
            }
        }
        return null;
    }

}
           

六 總結

學習了該部分知識之後,已經可以初步從系統列印服務接入列印機,并取得要列印的檔案。之後根據使用情況,适時地跟進細節即可。