Android 4.4 Печать в PDF без участия пользователя

Я работаю над приложением (Android 4.4 – API 20), где я создаю отчет в формате HTML. Я использую объект WebView для отображения отчета в своем приложении.

То, что я хотел бы сделать, это преобразовать этот WebView в PDF-документ.

Я смог преобразовать его с помощью PdfDocument и сделать .draw на странице из объекта 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(); 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 и создаю адаптер печати из объекта WebView с помощью createPrintDocumentAdapter, я могу выбрать опцию «Сохранить как 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 и разрешить разрывы страниц.

Solutions Collecting From Web of "Android 4.4 Печать в PDF без участия пользователя"

Возможно, это был поздний ответ, но до сих пор мне также нужно было найти решение с Print Framework, и я разделил документ Pdf на страницы с помощью кода ниже.

Насколько я могу судить, вы не можете заставить WebView или Pdf Document разбивать ваш pdf-файл на страницы по-разному (не сокращая текст или изображение). Но мы можем сделать, чтобы создать страницы в формате A4 или Letter, чтобы он мог вписываться в формат бумаги для печати.

Но есть еще одна проблема, с которой я сталкиваюсь. Следующий код работает как ожидается в Android 4.4, но не в более поздних версиях. В Android-L только видимая часть WebView втягивается в файл Pdf, но белые пустые страницы для остальной части HTML в WebView.

Согласно документации ,

Public static void enableSlowWholeDocumentDraw ()

Для приложений, ориентированных на выпуск L, WebView имеет новое поведение по умолчанию, которое уменьшает объем памяти и повышает производительность, разумно выбирая часть HTML-документа, который нужно нарисовать. Эти оптимизации прозрачны для разработчиков. Однако при определенных обстоятельствах разработчик приложения может отключить их:

  • Когда приложение использует onDraw (Canvas) для создания собственного чертежа и доступа к частям страницы, которая находится вне видимой части страницы.
  • Когда приложение использует capturePicture () для захвата очень большого HTML-документа. Обратите внимание, что capturePicture – устаревший API.

Включение чертежа всего документа HTML имеет значительную стоимость. Этот метод следует вызывать до создания любых WebView.

Я создал отчет об ошибках и прокомментировал аналогичный отчет об ошибках ЗДЕСЬ , но ответа пока нет. Но до тех пор вы можете использовать код ниже.

 /** * 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); } 

Не уверен, что это решит ваши проблемы с разрывом страницы, но рассмотрели ли вы использование библиотеки wkHTMLtoPDF с открытым исходным кодом ( http://wkhtmltopdf.org/ ) для преобразования из HTML в PDF? Мы использовали его широко, создав микросервис, которому мы передаем HTML-код, а затем передадим его в PDF-файл и вернем ссылку на PDF-файл или, наоборот, вернем PDF-файл (в зависимости от размера). Я знаю, что использование внешней службы для преобразования может быть болью (или, может быть, у вас нет доступа к интернету с устройства), но если это не проблема, то это может быть вариант. Для этого можно использовать и другие API. Одним из таких API является Neutrino API . Есть много других – вы можете искать API-интерфейсы, используя одну из этих поисковых систем API:

  1. apis.io
  2. Прогромная сеть
  3. Публичные API

У меня была та же проблема, и я решил ее вручную пережить жизненный цикл PrintDocumentAdapter (onStart, onLayout, onWrite и onFinish). Это немного сложно, поскольку вам нужны части скрытого Android API.

Я планирую создать библиотеку этого, но это займет несколько недель, пока у меня не будет достаточно времени. Я буду обновлять свой ответ, когда библиотека будет закончена.

Проведя огромное количество времени с этой проблемой, я использовал 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(); }