Android 4.4 无需用户参与即可打印为 PDF

Posted

技术标签:

【中文标题】Android 4.4 无需用户参与即可打印为 PDF【英文标题】:Android 4.4 Print to PDF without User Involvement 【发布时间】:2015-01-11 05:04:04 【问题描述】:

我正在开发一个应用程序(android 4.4 -- API 20),我在其中生成 html 格式的报告。我使用 WebView 对象在我的应用程序中显示报告。

我想做的就是将此 WebView 转换为 pdf 文档。

我已经能够使用 PdfDocument 对其进行转换,并从 WebView 对象将 .draw 绘制到页面上。我保存了文件,这很有效,只是结果是一个单页文档。没有分页符。

        View content = (View) webView;
        PrintAttributes pdfPrintAttrs = new PrintAttributes.Builder().
                setColorMode(PrintAttributes.COLOR_MODE_MONOCHROME).
                setMediaSize(PrintAttributes.MediaSize.NA_LETTER.asLandscape()).
                setResolution(new Resolution("zooey", PRINT_SERVICE, 300, 300)).
                setMinMargins(PrintAttributes.Margins.NO_MARGINS).
                build();
        PdfDocument document = new PrintedPdfDocument(mContext,pdfPrintAttrs);
        PageInfo pageInfo = new PageInfo.Builder(webView.getMeasuredWidth(), webView.getContentHeight(), 1).create();
        Page page = document.startPage(pageInfo);
        content.draw(page.getCanvas());
        document.finishPage(page);

如果我将其更改为使用 PrintedPdfDocumet 并且不指定 PageInfo,我只会获得 WebView 对象的可见部分。

        View content = (View) webView;
        PrintAttributes pdfPrintAttrs = new PrintAttributes.Builder().
                setColorMode(PrintAttributes.COLOR_MODE_MONOCHROME).
                setMediaSize(PrintAttributes.MediaSize.NA_LETTER.asLandscape()).
                setResolution(new Resolution("zooey", PRINT_SERVICE, 300, 300)).
                setMinMargins(PrintAttributes.Margins.NO_MARGINS).
                build();
        PrintedPdfDocument document = new PrintedPdfDocument(mContext,pdfPrintAttrs);
        Page page = document.startPage(0);
        content.draw(page.getCanvas());
        document.finishPage(page);

如果我使用 PrintManager 并使用 createPrintDocumentAdapter 从 WebView 对象创建打印适配器,我可以选择“另存为 PDF”选项,并且生成的 pdf 文件具有我在原始网页的 CSS 中指定的分页符.

        PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE);
        PrintDocumentAdapter printAdapter = webView.createPrintDocumentAdapter();
        String jobName = getString(R.string.app_name) + " Report "
                + reportName;
        PrintAttributes printAttrs = new PrintAttributes.Builder().
                setColorMode(PrintAttributes.COLOR_MODE_MONOCHROME).
                setMediaSize(PrintAttributes.MediaSize.NA_LETTER.asLandscape()).
                setMinMargins(PrintAttributes.Margins.NO_MARGINS).
                build();
        PrintJob printJob = printManager.print(jobName, printAdapter,
                printAttrs);

我的问题是:我可以指定我希望 PrintManager 执行“另存为 PDF”并提供生成文件的名称和位置,这样就不会与用户交互吗?

或者:有没有办法可以将我的 WebView 对象转换为 PDF 并允许分页符。

【问题讨论】:

不是一个真正的答案,但是看看这个库,它在sourceforge.net/projects/itext之前帮助过我 我认为 iText 需要您购买许可证才能使用它。是这样吗? 不,仅当您想将其用于商业用途并出售您的应用程序等时。我在我的一两个项目中使用它,这只是为了我自己在办公室。 “一旦您开始活动,包括在您的产品中分发 iText 软件或在网络上部署它,而无需根据 AGPL 许可证披露您自己的应用程序的源代码,就必须购买商业许可证。”在此处查看其余内容itextpdf.com/agpl 我认为 iText 不会成为我的选择。即使我不打算出售我的应用程序,它也会用于商业环境。 @Tejasjain,VinginMedina,在下面查看我的答案。 【参考方案1】:

在花了很多时间解决这个问题之后,我使用 DexMaker 实现了非公共抽象回调并想出了这个:

@Override
protected void onPreExecute() 
    super.onPreExecute();
    printAdapter = webView.createPrintDocumentAdapter();


@Override
protected Void doInBackground(Void... voids) 

    File file = new File(pdfPath);
    if (file.exists()) 
        file.delete();
    
    try 
        file.createNewFile();

        // get file descriptor
        descriptor = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_WRITE);

        // create print attributes
        PrintAttributes attributes = new PrintAttributes.Builder()
                .setMediaSize(PrintAttributes.MediaSize.ISO_A4)
                .setResolution(new PrintAttributes.Resolution("id", PRINT_SERVICE, 300, 300))
                .setColorMode(PrintAttributes.COLOR_MODE_COLOR)
                .setMinMargins(new PrintAttributes.Margins(0, 0, 0, 0))
                .build();
        ranges = new PageRange[]new PageRange(1, numberPages);

        // dexmaker cache folder
        cacheFolder =  new File(context.getFilesDir() +"/etemp/");

        printAdapter.onStart();

        printAdapter.onLayout(attributes, attributes, new CancellationSignal(), getLayoutResultCallback(new InvocationHandler() 
            @Override
            public Object invoke(Object o, Method method, Object[] objects) throws Throwable 

                if (method.getName().equals("onLayoutFinished")) 
                    onLayoutSuccess();
                 else 
                    Log.e(TAG, "Layout failed");
                    pdfCallback.onPdfFailed();
                
                return null;
            
        , cacheFolder), new Bundle());
     catch (IOException e) 
        e.printStackTrace();
        Log.e(TAG, e != null ? e.getMessage() : "PrintPdfTask unknown error");
    
    return null;


private void onLayoutSuccess() throws IOException 
    PrintDocumentAdapter.WriteResultCallback callback = getWriteResultCallback(new InvocationHandler() 
        @Override
        public Object invoke(Object o, Method method, Object[] objects) throws Throwable 
            if (method.getName().equals("onWriteFinished")) 
                pdfCallback.onPdfCreated();
             else 
                Log.e(TAG, "Layout failed");
                pdfCallback.onPdfFailed();
            
            return null;
        
    , cacheFolder);
    printAdapter.onWrite(ranges, descriptor, new CancellationSignal(), callback);



/**
 * Implementation of non public abstract class LayoutResultCallback obtained via DexMaker
 * @param invocationHandler
 * @param dexCacheDir
 * @return LayoutResultCallback
 * @throws IOException
 */
public static PrintDocumentAdapter.LayoutResultCallback getLayoutResultCallback(InvocationHandler invocationHandler,
                                                                                File dexCacheDir) throws IOException 
    return ProxyBuilder.forClass(PrintDocumentAdapter.LayoutResultCallback.class)
            .dexCache(dexCacheDir)
            .handler(invocationHandler)
            .build();


/**
 * Implementation of non public abstract class WriteResultCallback obtained via DexMaker
 * @param invocationHandler
 * @param dexCacheDir
 * @return LayoutResultCallback
 * @throws IOException
 */
public static PrintDocumentAdapter.WriteResultCallback getWriteResultCallback(InvocationHandler invocationHandler,
                                                                              File dexCacheDir) throws IOException 
    return ProxyBuilder.forClass(PrintDocumentAdapter.WriteResultCallback.class)
            .dexCache(dexCacheDir)
            .handler(invocationHandler)
            .build();

【讨论】:

【参考方案2】:

这可能是一个迟到的答案,但到目前为止,我还需要与 Print Framework 类似的解决方案,并且我使用以下代码将 Pdf 文档拆分为页面。

据我所知,您不能真正使 WebView 或 Pdf 文档以智能方式将您的 pdf 文件拆分为页面(而不是剪切文本或图像)。但我们可以做的是创建 A4 或 Letter 大小的页面,这样它就可以适应打印出来的纸张格式。

但我还面临另一个问题。下面的代码在 Android 4.4 中按预期工作,但在更高版本中没有。在 Android-L 中,只有 WebView 的可见部分被绘制到 Pdf 文件中,而 WebView 中的其余 HTML 为白色空白页。

根据documentation,

public static void enableSlowWholeDocumentDraw ()

对于面向 L 版本的应用,WebView 具有新的默认行为,可通过智能地选择 HTML 文档中需要绘制的部分来减少内存占用并提高性能。这些优化对开发人员是透明的。但是,在某些情况下,应用开发者可能希望禁用它们:

当应用使用 onDraw(Canvas) 进行自己的绘图并访问页面可见部分之外的页面部分时。 当应用程序使用 capturePicture() 捕获非常大的 HTML 文档时。请注意,capturePicture 是一个已弃用的 API。

启用绘制整个 HTML 文档会导致显着的性能成本。应该在创建任何 WebView 之前调用此方法。

我创建了一个Bug Report,并评论了一个类似的错误报告HERE,但到目前为止没有任何回应。但在那之前,您可以使用下面的代码。

/**
 * Creates a PDF Multi Page Document depending on the Ratio of Letter Size.
 * This method does not close the Document. It should be Closed after writing Pdf Document to a File.
 *
 * @return
 */
private PdfDocument createMultiPagePdfDocument(int webViewWidth, int webViewHeight) 

    /* Find the Letter Size Height depending on the Letter Size Ratio and given Page Width */
    int letterSizeHeight = getLetterSizeHeight(webViewWidth);

    PdfDocument document = new PrintedPdfDocument(getActivity(), getPrintAttributes());

    final int numberOfPages = (webViewHeight/letterSizeHeight) + 1;

    for (int i = 0; i < numberOfPages; i++) 

        int webMarginTop = i*letterSizeHeight;

        PdfDocument.PageInfo pageInfo = new PdfDocument.PageInfo.Builder(webViewWidth, letterSizeHeight, i+1).create();
        PdfDocument.Page page = document.startPage(pageInfo);

        /* Scale Canvas */
        page.getCanvas().translate(0, -webMarginTop);
        mWebView.draw(page.getCanvas());

        document.finishPage(page);
    

    return document;



/**
 * Calculates the Letter Size Paper's Height depending on the LetterSize Dimensions and Given width.
 *
 * @param width
 * @return
 */
private int getLetterSizeHeight(int width) 
    return (int)((float)(11*width)/8.5);

【讨论】:

+1 用于解决方案,但是否找到任何解决方法?我的意思是 Android L 及以上实现中的错误? 并非如此。它应该仍在等待中。 你好。 Android pdf 文件为空。能否请您提供一些解决方案? @DaleYY 对不起,我很久没用这个了。【参考方案3】:

不确定这是否能解决您的分页问题,​​但您是否考虑过使用开源 wkHTMLtoPDF 库 (http://wkhtmltopdf.org/) 将 HTML 转换为 PDF?我们通过创建一个微服务来广泛使用它,我们将 HTML 代码传递给该微服务,然后让该服务将其转换为 PDF 并将链接返回到 PDF,或者让它返回 PDF(取决于大小)。我知道使用外部服务进行转换可能会很痛苦(或者您可能无法从设备访问互联网),但如果这不是问题,那么这可能是一个选择。可能还有其他 API 可用于执行此转换。一种这样的 API 是 Neutrino API。还有很多其他的 - 您可以使用以下 API 搜索引擎之一搜索 API:

    apis.io Progammable Web Public APIs

【讨论】:

以上是关于Android 4.4 无需用户参与即可打印为 PDF的主要内容,如果未能解决你的问题,请参考以下文章

无需用户干预即可自动更新 Android 应用程序

无需物理设备即可检查 Android 应用程序的速度?

Android - 无需在地图 api v2 中按住即可拖动标记

索尼google android 4.4 (kitkat)有啥功能

Firebase无需登录即可创建用户[重复]

iOS 和 Android 无需连接即可访问的蓝牙设备是不是有唯一标识符?