Rheinwerk Computing < openbook >

 
Inhaltsverzeichnis
Vorwort
Teil I Grundlagen
1 Android – eine offene, mobile Plattform
2 Hallo Android!
3 Von der Idee zur Veröffentlichung
Teil II Elementare Anwendungsbausteine
4 Wichtige Grundbausteine von Apps
5 Benutzeroberflächen
6 Multitasking
Teil III Gerätefunktionen nutzen
7 Telefonieren und surfen
8 Sensoren, GPS und Bluetooth
Teil IV Dateien und Datenbanken
9 Dateien lesen, schreiben und drucken
10 Datenbanken
Teil V Multimedia und Produktivität
11 Multimedia
12 Kontakte und Organizer
A Einführung in Kotlin
B Jetpack Compose
C Häufig benötigte Codebausteine
D Literaturverzeichnis
E Die Begleitmaterialien
Stichwortverzeichnis

Ihre Meinung?
Spacer
<< zurück
Android 11 von Thomas Künneth
Das Praxisbuch für App-Entwickler
Buch: Android 11

Android 11
Pfeil 9 Dateien lesen, schreiben und drucken
Pfeil 9.1 Grundlegende Dateioperationen
Pfeil 9.1.1 Dateien lesen und schreiben
Pfeil 9.1.2 Mit Verzeichnissen arbeiten
Pfeil 9.2 Externe Speichermedien
Pfeil 9.2.1 Mit externem Speicher arbeiten
Pfeil 9.2.2 Storage Manager
Pfeil 9.3 Drucken
Pfeil 9.3.1 Druckgrundlagen
Pfeil 9.3.2 Eigene Dokumenttypen drucken
Pfeil 9.4 Zusammenfassung
 
Zum Seitenanfang

9.3    Drucken Zur vorigen ÜberschriftZur nächsten Überschrift

Wenn man sich vergegenwärtigt, wie lange es Android schon gibt, mag es verwunderlich erscheinen, dass die Plattform erst Ende Oktober 2013 mit API-Level 19 die Fähigkeit erhalten hat, Dokumente zu drucken. Apple hatte sein Betriebssystem iOS schon viel früher (Ende 2010) damit ausgestattet. Aber klassische Smartphone-Anwendungen (Telefonie, SMS, Kalender und Kontakte) brauchen nichts zu Papier zu bringen, und lange Zeit waren die Displays einfach zu klein, um Textdokumente oder Tabellenblätter zu bearbeiten. Warum hätte man sie also drucken sollen?

Ohne Frage hat der Tablet-Boom dazu beigetragen, die Grenzen der mobilen Betriebssysteme auszuloten. Die Anwender wollten plötzlich Tätigkeiten verrichten, für die früher ein PC nötig war. Die Anzeige eines durchschnittlich großen Tablets ist ausreichend, um Änderungen an einer Präsentation oder an einem Geschäftsbrief vorzunehmen. Klar, dass man das Dokument dann auch ausdrucken möchte.

 
Zum Seitenanfang

9.3.1    Druckgrundlagen Zur vorigen ÜberschriftZur nächsten Überschrift

Am Druckprozess sind drei Komponenten beteiligt:

  1. Die App kennt die Daten, die der Anwender zu Papier bringen möchte.

  2. Der Print Manager nimmt den Druckauftrag des Programms entgegen, zeigt einen Auswahl- und Konfigurationsdialog an und leitet die Daten an einen Print Service weiter.

  3. Ein Print Service nimmt die Druckinformationen entgegen und überträgt sie mit einem geeigneten Protokoll an einen Drucker. Print Services sind also im klassischen Sinne Druckertreiber. Sie werden üblicherweise von Geräteherstellern angeboten und können wie ganz normale Apps heruntergeladen, installiert und aktualisiert werden. Um ein bestimmtes Druckermodell nutzen zu können, müssen Sie den passenden Print Service einrichten.

Lange Zeit war auf den meisten Geräten Google Cloud Print vorinstalliert. Dieser Print Service wurde nicht für ein bestimmtes Modell oder eine spezielle Geräteklasse entwickelt. Vielmehr registrieren Sie beliebig viele Drucker, auf die Sie an unterschiedlichen Orten Zugriff haben. Bei einem Druckvorgang wählen Sie dann das gewünschte Gerät aus und erhalten Ihren Ausdruck an dem Ort, an dem Sie sich gerade befinden. Der Dienst hat seinen Ursprung im Unternehmensumfeld, denn gerade in Firmen mit vielen Standorten kann es aufwendig sein, »überall« drucken zu können. Allerdings hat Google den Dienst zum Jahresende 2020 abgekündigt. Welche Alternativen sich durchsetzen werden, ist derzeit schwer abzuschätzen.

Druckerauswahl mit Seitenvorschau

Abbildung 9.6    Druckerauswahl mit Seitenvorschau

Lassen Sie uns nun anhand des Beispiels DruckDemo1 einen ersten Blick darauf werfen, wie Apps einen Druck auslösen können. Das Programm erzeugt eine einfache HTML-Seite mit Grafik, zeigt sie mit loadDataWithBaseURL() an und übergibt sie an den Print Manager. Der daraufhin erscheinende Konfigurationsdialog ist in Abbildung 9.6 zu sehen. Mit ihm kann der Benutzer die Zahl der Kopien sowie das Papierformat einstellen und den zu verwendenden Drucker auswählen.

Der Quelltext der Klasse DruckDemo1Activity ist in Listing 9.10 dargestellt. Da eine HTML-Seite gedruckt werden soll, erzeuge ich als Erstes eine WebView-Instanz und setze dann mit webViewClient = ein geeignetes Callback-Objekt. Erst nach dem vollständigen Laden der Seite wird onPageFinished() aufgerufen, deshalb finden dort alle weiteren Aktionen statt.

[»]  Hinweis

Das WebView-Objekt muss einer Instanzvariablen zugewiesen werden, um zu verhindern, dass der Garbage Collector es zu früh freigibt. Andernfalls kann der Druckvorgang scheitern.

Durch den Aufruf von getSystemService(PrintManager::class.java) wird eine PrintManager-Instanz ermittelt. Anschließend erstelle ich durch Aufruf der WebView-Methode createPrintDocumentAdapter() ein Objekt des Typs PrintDocumentAdapter, welches das Dokument für den Druck beschreibt. Sie werden die Callback-Methoden dieses Objekts später noch genauer kennenlernen. Schließlich wird ein Druckauftrag erstellt und abgeschickt. Einige Druckeigenschaften wie Farbe, Ränder und Auflösung lassen sich mit einem PrintAttributes.Builder konfigurieren. In meiner Implementierung belasse ich es bei den Standardeinstellungen.

package com.thomaskuenneth.androidbuch.druckdemo1

import android.os.Bundle
import android.print.PrintAttributes
import android.print.PrintJob
import android.print.PrintManager
import android.util.Log
import android.webkit.*
import androidx.appcompat.app.AppCompatActivity

private val TAG = DruckDemo1Activity::class.simpleName
class DruckDemo1Activity : AppCompatActivity() {
private lateinit var webView: WebView

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// WebView für den Druck instanziieren
webView = WebView(this)
webView.webViewClient = object : WebViewClient() {
override fun shouldOverrideUrlLoading(view: WebView,
request: WebResourceRequest): Boolean {
return false
}

override fun onPageFinished(view: WebView, url: String) {
// PrintManager-Instanz ermitteln
getSystemService(PrintManager::class.java)?.let {
// Der Adapter stellt den Dokumentinhalt bereit
val adapter =
webView.createPrintDocumentAdapter("Dokumentname")
// Druckauftrag erstellen und übergeben
val jobName = getString(R.string.app_name) + " Dokument"
val attributes = PrintAttributes.Builder().build()
val printJob: PrintJob = it.print(jobName, adapter, attributes)
Log.d(TAG, printJob.info.toString())
}
}
}
val htmlDocument = """
<html><body>
<h1>Hallo Android</h1>
<p><img src="ic_launcher.png" />
<br />Ein Test</p>
</body></html>
""".trimIndent()
webView.
loadDataWithBaseURL("file:///android_asset/",
htmlDocument, "text/html", "UTF-8", null)
}
}

Listing 9.10    Die Klasse »DruckDemo1Activity«

Die Nutzung des Android-Druck-Frameworks reduziert sich auf wenige Zeilen Quelltext, sofern Sie die Daten Ihrer Anwendung in HTML verpacken können. Dann profitieren Sie von der WebView-Methode createPrintDocumentAdapter(). Sie liefert einen fertigen PrintDocumentAdapter, der alle Informationen enthält, die das System für den Druck eines Dokuments braucht. Wie Sie ihn selbst implementieren, zeige ich Ihnen als Nächstes.

 
Zum Seitenanfang

9.3.2    Eigene Dokumenttypen drucken Zur vorigen ÜberschriftZur nächsten Überschrift

Das Projekt DruckDemo2 generiert Dokumente, die je nach Einstellung der Seitenorientierung (Hochkant- bzw. Quermodus) aus einer oder zwei Seiten bestehen. Die erste Seite zeigt eine Sinus-, die zweite eine Cosinuskurve. In Abbildung 9.7 ist der Android-Druckdialog mit der Vorschau eines solchen Dokuments dargestellt.

Druckdialog mit dem Dokument aus »DruckDemo2«

Abbildung 9.7    Druckdialog mit dem Dokument aus »DruckDemo2«

Lassen Sie uns einen kurzen Blick auf Listing 9.11 werfen, das die Hauptaktivität der App, die Klasse DruckDemo2Activity, zeigt. Als Erstes wird wieder mit getSystemService(PrintManager::class.java) eine PrintManager-Instanz ermittelt. Anschließend erzeugt print() einen Druckauftrag und übergibt ihn an das System. Alle Informationen, die Android für das Drucken des Dokuments benötigt, werden über die Methoden der Klasse DemoPrintDocumentAdapter abgefragt. Wie Sie gleich sehen werden, ist PDF das Standardformat für das Drucken unter Android. Das bedeutet, dass Apps sauber paginierte PDF-Dokumente erzeugen und an das Druck-Framework übermitteln müssen. Das hört sich aber komplizierter an, als es ist. Denn glücklicherweise stellt die Plattform hierfür Klassen zur Verfügung, die leicht einsetzbar sind und das Erstellen von PDFs fast schon zu einem Kinderspiel machen.

package com.thomaskuenneth.androidbuch.druckdemo2

import android.os.Bundle
import android.print.PrintManager
import androidx.appcompat.app.AppCompatActivity

class DruckDemo2Activity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
getSystemService(PrintManager::class.java)?.let {
val jobName = "${getString(R.string.app_name)} Document"
it.print(
jobName,
DemoPrintDocumentAdapter(this), null
)
}
}
}

Listing 9.11    Die Klasse »DruckDemo2Activity«

Bei dem Dokumenterstellungsprozess geht es darum, Inhalt und Struktur eines Dokuments (also das, was der Anwender Ihrer App zu Papier bringen möchte) so umzuformen, dass es in ein PDF gepackt und auf Seiten verteilt werden kann. Hierzu ein Beispiel: Nehmen Sie an, ein Text besteht aus 2.500 Wörtern. Wie viele davon auf eine Seite passen, hängt unter anderem vom eingestellten Papierformat und der Randgröße ab. Natürlich sind auch Schriftart und -größe wichtig, aber beide sind fester Bestandteil des Dokuments. Anders verhält es sich mit den erstgenannten Werten, die vom Anwender unmittelbar vor dem Druck vorgegeben werden können.

In Listing 9.12 sehen Sie die Klasse DemoPrintDocumentAdapter, die von PrintDocumentAdapter ableitet. Letztere definiert die Callback-Methoden onStart(), onFinish(), onLayout() und onWrite(). Die ersten beiden Methoden können Sie überschreiben. Sie werden einmal zu Beginn bzw. am Ende des Druckprozesses aufgerufen. Es bietet sich an, sie für Initialisierungs- und Aufräumarbeiten zu nutzen. Ich habe onFinish() überschrieben, um mit disposePdf() meine PrintedPdfDocument-Instanz freigeben zu können. Leider kann ich sie nicht in onStart() erzeugen, weil der Konstruktor einen Parameter erwartet, der dort nicht zur Verfügung steht (newAttributes: PrintAttributes).

Die letzten zwei Callback-Methoden sind abstrakt und müssen deshalb implementiert werden. onLayout() wird immer nach Änderungen von Druckeinstellungen aufgerufen, die Auswirkungen auf die Ausgabe haben, beispielsweise Drucker, Papierart und Seitenorientierung. Die Aufgabe Ihrer App ist es, das Layout der Seiten zu berechnen. Dabei müssen Sie die erwartete Seitenzahl zurückliefern. onWrite() schließlich rendert die zu druckenden Seiten in eine Datei. Diese Methode kann nach einem onLayout() ein- oder mehrmals aufgerufen werden.

[»]  Hinweis

Alle Adapter-Methoden werden auf dem Mainthread der App aufgerufen. Zeitintensive Berechnungen sowie das Nachladen von Daten sollten Sie deshalb auf jeden Fall in einen separaten Thread oder eine Koroutine auslagern.

package com.thomaskuenneth.androidbuch.druckdemo2

import android.content.Context
import android.graphics.*
import android.graphics.pdf.PdfDocument
import android.os.*
import android.print.PageRange
import android.print.PrintAttributes
import android.print.PrintDocumentAdapter
import android.print.PrintDocumentInfo
import android.print.pdf.PrintedPdfDocument
import java.io.*
import kotlin.math.cos
import kotlin.math.sin

class DemoPrintDocumentAdapter(private val context: Context) :
PrintDocumentAdapter() {
private var numPages = 0
private var pdf: PrintedPdfDocument? = null

override fun onLayout(
oldAttributes: PrintAttributes?,
newAttributes: PrintAttributes,
cancellationSignal: CancellationSignal,
callback: LayoutResultCallback,
extras: Bundle?
) {
// sofern vorhanden, altes freigeben
disposePdf()
// neues PDF-Dokument mit den gewünschten Attributen erzeugen
pdf = PrintedPdfDocument(context, newAttributes)
// auf Abbruchwunsch reagieren
if (cancellationSignal.isCanceled) {
callback.onLayoutCancelled()
disposePdf()
return
}
// erwartete Seitenzahl berechnen
numPages = computePageCount(newAttributes)
if (numPages > 0) {
// Informationen an das Print-Framework zurückliefern
val info = PrintDocumentInfo.Builder("sin_cos.pdf")
.setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT)
.setPageCount(numPages)
.build()
callback.onLayoutFinished(info, true)
} else {
// einen Fehler melden
callback.onLayoutFailed("Fehler beim Berechnen der Seitenzahl")
}
}

override fun onWrite(
pages: Array<PageRange?>?,
destination: ParcelFileDescriptor,
cancellationSignal: CancellationSignal,
callback: WriteResultCallback
) {
pdf?.let { pdf ->
// über alle Seiten des Dokuments iterieren
for (i in 0 until numPages) {
// Abbruch?
if (cancellationSignal.isCanceled) {
callback.onWriteCancelled()
disposePdf()
return
}
val page = pdf.startPage(i)
drawPage(page)
pdf.finishPage(page)
}
// PDF-Dokument schreiben
try {
FileOutputStream(
destination.fileDescriptor
).let { stream ->
pdf.writeTo(stream)
}
} catch (e: IOException) {
callback.onWriteFailed(e.toString())
return
}
try {
destination.close()
} catch (e: IOException) {
callback.onWriteFailed(e.toString())
return
}
}
callback.onWriteFinished(pages)
}

override fun onFinish() {
disposePdf()
}

private fun computePageCount(
printAttributes: PrintAttributes): Int {
val size = printAttributes.mediaSize
return if (size == null || !size.isPortrait) 2 else 1
}

// Einheiten entsprechen 1/72 Zoll
private fun drawPage(page: PdfDocument.Page) {
val nr = page.info.pageNumber.toFloat()
// Breite und Höhe
val w = page.canvas.width.toFloat()
val h = page.canvas.height.toFloat()
// Mittelpunkt
val cx = w / 2f
val cy = h / 2f
val paint = Paint()
paint.strokeWidth = 3f
paint.color = Color.BLUE
page.canvas.drawLine(cx, 0f, cx, h - 1f, paint)
page.canvas.drawLine(0f, cy, w - 1f, cy, paint)
paint.color = Color.BLACK
var i = 0f
while (i < w) {
val y = if (nr == 0f) {
(sin(i * (2f * Math.PI / w)) * cy + cy).toFloat()
} else {
(cos(i * (2f * Math.PI / w)) * cy + cy).toFloat()
}
page.canvas.drawPoint(i, y, paint)
i++
}
}

private fun disposePdf() {
pdf?.close()
pdf = null
}
}

Listing 9.12    Die Klasse »DemoPrintDocumentAdapter«

Es ist die Hauptaufgabe von onLayout(), das Layout des Dokuments an die aktuellen Druckeinstellungen anzupassen und dabei die erwartete Seitenzahl zu ermitteln. Meine Implementierung ruft hierfür die Methode computePageCount() auf. Diese führt natürlich keine echte Berechnung durch, sondern liefert je nach Seitenorientierung immer 1 oder 2. Das Ergebnis des Layoutvorgangs teilen Sie dem System mit, indem Sie eine der Methoden des LayoutResultCallback-Objekts aufrufen, das an onLayout() übergeben wurde. Dabei kann es drei Situationen geben:

  • Der Vorgang war erfolgreich. In diesem Fall rufen Sie onLayoutFinished() auf und übergeben hierbei in einem Objekt des Typs PrintDocumentInfo die Seitenanzahl und den Typ des Dokuments. Ein Flag kennzeichnet, ob sich das Layout inhaltlich geändert hat. Es ist wichtig, diesen Wert sorgsam zu setzen, denn Android nutzt ihn für die Steuerung von onWrite()-Aufrufen.

  • Es ist ein Fehler aufgetreten. Die Seitenzahl konnte nicht berechnet werden. In diesem Fall übergeben Sie eine Fehlermeldung an onLayoutFailed().

  • Der Druckvorgang soll abgebrochen werden. In diesem Fall müssen Sie onLayoutCancelled() aufrufen. Aufforderungen, den Druck abzubrechen, werden an onLayout() und onWrite() mit einem CancellationSignal-Objekt (isCanceled) übergeben.

Ein Objekt des Typs PrintedPdfDocument repräsentiert das zu druckende Dokument während seiner Erstellung. Es wird in onLayout() auf Basis der aktuellen Druckparameter instanziiert und in onWrite() bestückt. Die Hauptaufgabe dieser Methode ist es, jede angeforderte Seite zu rendern und in eine Datei zu schreiben. Falls der Druckvorgang abgebrochen werden soll, rufen Sie die Methode onWriteCancelled() des übergebenen WriteResultCallback-Objekts auf. Haben Sie alle angeforderten Seiten erzeugt, schreiben Sie das Ergebnis mit writeTo() in eine Datei. Tritt hierbei ein Fehler auf, müssen Sie dies dem System mit onWriteFailed() mitteilen. Die Methode onWriteFinished() signalisiert einen erfolgreichen Rendering-Vorgang. Bitte denken Sie daran, den an onWrite() übergebenen ParcelFileDescriptor durch Aufruf von close() zu schließen.

[»]  Hinweis

Um den Quelltext kurz zu halten, ignoriert meine Implementierung etwaige an onWrite() übergebene Seitenbereiche.

Ich habe den eigentlichen Seitenaufbau in die Methode drawPage() ausgelagert. Sie erhält ein PdfDocument.Page-Objekt, das die aktuell zu rendernde Seite repräsentiert. Das Zeichnen erfolgt auf ein ganz normales Canvas-Objekt. Sie erhalten es mit canvas. Die Auflösung dieser Zeichenfläche entspricht 1/72 Zoll, was ein im Satzbereich lange bekannter Wert ist.

 


Ihre Meinung?

Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de

<< zurück
Zur Rheinwerk-Konferenz für Kotlin
 Buchempfehlungen
Zum Rheinwerk-Shop: Kotlin

Kotlin


Zum Rheinwerk-Shop: Praxisbuch Usability und UX

Praxisbuch Usability und UX


Zum Rheinwerk-Shop: Flutter und Dart

Flutter und Dart


Zum Rheinwerk-Shop: App-Design

App-Design


 Lieferung
Versandkostenfrei bestellen in Deutschland, Österreich und in die Schweiz
InfoInfo

 
 


Copyright © Rheinwerk Verlag GmbH 2023
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das Openbook denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt.
Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.

 
[Rheinwerk Computing]

Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de

Cookie-Einstellungen ändern