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    Dateien lesen, schreiben und drucken Zur vorigen ÜberschriftZur nächsten Überschrift

Das Lesen und Schreiben von Dateien gehört seit jeher zu den Grundfunktionen vieler Apps. Wie dies unter Android funktioniert, zeige ich Ihnen in diesem Kapitel. Außerdem erfahren Sie, wie einfach Sie die Inhalte Ihrer Anwendung zu Papier bringen können.

Um Informationen längerfristig zu speichern, können Sie entweder Datenbanken oder klassische Dateien verwenden. Welche Variante Sie wählen, hängt von zahlreichen Faktoren ab. Termine und Kontakte sind sehr strukturierte Daten, d. h., jeder »Datensatz« hat denselben Aufbau. Deshalb lassen sich solche Informationen sehr gut in relationalen Datenbanken ablegen. Musikstücke oder Videoclips hingegen haben eine weniger offensichtliche Struktur. Sie fühlen sich in herkömmlichen Dateien wohler.

Auch die Frage der Weitergabe spielt eine wichtige Rolle. Noch immer haben zahlreiche Android-Geräte einen Steckplatz für Speicherkarten. Informationen, die auf einem solchen Medium abgelegt wurden, lassen sich sehr leicht transportieren und in einem anderen Smartphone oder Tablet weiterverwenden, sofern der Benutzer das Medium nicht verschlüsselt hat. Haben Sie beispielsweise mit der eingebauten Kamera einen tollen Schnappschuss gemacht, können Sie einfach die Speicherkarte entnehmen und vom Fotolabor einen Abzug anfertigen lassen.

 
Zum Seitenanfang

9.1    Grundlegende Dateioperationen Zur vorigen ÜberschriftZur nächsten Überschrift

Android erbt die Datei- und Verzeichnisoperationen von Java. Das Lesen und Schreiben von Dateien basiert also in weiten Teilen auf den Klassen und Interfaces des Pakets java.io. Wie Sie diese einsetzen, möchte ich Ihnen anhand der Beispiel-App FileDemo1 demonstrieren. Sie sehen sie in Abbildung 9.1. Das Programm besteht aus einem Eingabefeld sowie aus den drei Schaltflächen LADEN, SPEICHERN und LEEREN. Mit ihnen wird der eingegebene Text gespeichert, geladen bzw. gelöscht.

 
Zum Seitenanfang

9.1.1    Dateien lesen und schreiben Zur vorigen ÜberschriftZur nächsten Überschrift

Die für die beiden Schaltflächen LADEN und SPEICHERN registrierten OnClickListener rufen die privaten Methoden load() bzw. save() auf. Letztere erhält als einzigen Parameter die zu speichernde Zeichenkette. Der Dateiname ist in FILENAME abgelegt.

Die App »FileDemo1«

Abbildung 9.1    Die App »FileDemo1«

Daten werden in Ströme (Streams) geschrieben oder aus ihnen gelesen. Android stellt die Methoden openFileOutput() und openFileInput() zur Verfügung, um Ströme für das Schreiben oder Lesen von Dateien zu öffnen. openFileOutput() benötigt zwei Parameter. Neben dem Namen der zu schreibenden Datei geben Sie an, ob nur die eigene App sowie Apps mit derselben User-ID auf sie zugreifen (MODE_PRIVATE) oder ob auch Dritte lesen und schreiben dürfen (MODE_WORLD_READABLE und MODE_WORLD_WRITEABLE). Die beiden letztgenannten Konstanten gelten schon lange (seit API-Level 17) als veraltet und sollen nicht mehr verwendet werden. Google gibt als Grund an, dass es ein potenzielles Sicherheitsrisiko darstellt, wenn beliebige Apps auf eine Datei oder auf ein Verzeichnis zugreifen.

package com.thomaskuenneth.androidbuch.filedemo1

import android.content.Context
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*
import java.io.BufferedReader
import java.io.IOException
import java.io.InputStreamReader
import java.io.OutputStreamWriter

private val TAG = FileDemo1Activity::class.simpleName
private val FILENAME = "$TAG.txt"
class FileDemo1Activity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
clear.setOnClickListener { edit.setText("") }
load.setOnClickListener { load() }
save.setOnClickListener { save(edit.text.toString()) }
Log.d(TAG, "filesDir: ${filesDir.absolutePath}")
load()
}

private fun load() {
val sb = StringBuilder()
try {
openFileInput(FILENAME).use { fis ->
InputStreamReader(fis).use { isr ->
BufferedReader(isr).use { br ->
while (true) {
val line = br.readLine() ?: break
if (sb.isNotEmpty()) {
sb.append('\n')
}
sb.append(line)
}
}
}
}
} catch (ex: IOException) {
Log.e(TAG, "load()", ex)
}
edit.setText(sb.toString())
}

private fun save(s: String) {
try {
openFileOutput(FILENAME,
Context.MODE_PRIVATE).use { fos ->
OutputStreamWriter(fos).use { osw ->
osw.write(s)
}
}
} catch (ex: IOException) {
Log.e(TAG, "save()", ex)
}
}
}

Listing 9.1    Die Klasse »FileDemo1Activity«

Existiert die zu schreibende Datei noch nicht, dann wird sie angelegt. Ist sie bereits vorhanden, geht der alte Inhalt verloren, sofern Sie nicht den Modus MODE_APPEND wählen. In diesem Fall »wächst« die Datei. Die Methode openFileOutput() liefert eine Instanz der Klasse java.io.FileOutputStream. Diese bietet einige write()-Methoden an, die allerdings auf Bytes operieren. Android setzt bei Zeichenketten auf Unicode; einzelne Zeichen werden in jeweils einem Char abgelegt. Eine Umwandlung in Bytes ist mit der Methode getBytes() der Klasse String zwar prinzipiell möglich, allerdings kann es bei einer späteren Rückumwandlung Probleme geben, wenn der gespeicherte Block nicht in einem Stück eingelesen werden kann. Aus diesem Grund verlässt sich FileDemo1Activity auf die Klasse OutputStreamWriter. Sie enthält eine Implementierung von write(), die Strings richtig verarbeitet.

In welchem Verzeichnis wird eigentlich die durch openFileOutput() erzeugte Datei abgelegt? Ihr Name (FileDemo1Activity.txt) enthält ja keine Pfadangaben. Die Methode getFilesDir() der Klasse android.content.Context (in Kotlin einfach filesDir) liefert die gewünschte Information. Sie können im Werkzeugfenster Device File Explorer das Android-Dateisystem inspizieren.

Die private Methode load() lädt einen zuvor gespeicherten Text und zeigt ihn an. Analog zu openFileOutput() liefert auch openFileInput() einen Strom, allerdings eine Instanz von FileInputStream. Da die read()-Methoden dieser Klasse keine Strings kennen, greife ich auf java.io.InputStreamReader und java.io.BufferedReader als Hüllen zurück. load() liest die Datei zeilenweise (readLine()) ein. Die einzelnen Teile werden durch Zeilenumbrüche miteinander verbunden und in einem StringBuilder gespeichert. Erst zum Schluss wird mit toString() ein String erzeugt.

[+]  Tipp

Kotlins Klassenbibliothek enthält im Paket kotlin.io die beiden Erweiterungsfunktionen readText() und writeText(). Mit ihnen können Sie sehr kompakt Dateien lesen und schreiben.

Automatische Backups

Alle Apps mit targetSdkVersion 23 oder höher nehmen an dem automatischen, systemweiten Sicherungs- und Wiederherstellungsprozess Auto Backup teil, außer sie enthalten in ihrem Manifest die Zuweisung android:allowBackup="false". Damit votieren sie gegen diesen genialen Mechanismus. Dateien, die in anwendungsspezifischen Verzeichnissen abgelegt werden (getFilesDir(), getDatabasePath(), getDir() und getExternalFilesDir()), sowie Shared Preferences schreibt Android in regelmäßigen Abständen verschlüsselt in ein privates Verzeichnis des Google Drive des Benutzers, sofern dieser sich beim Einrichten des Geräts nicht dagegen entschieden oder die Sicherung später deaktiviert hat. Dateien in den Verzeichnissen getCacheDir(), getCodeCacheDir() und getNoBackupFilesDir() werden ignoriert. Pro App stehen 25 MB zur Verfügung. Backup-Daten werden nicht auf den dem Nutzer zur Verfügung stehenden Speicherplatz angerechnet.

[+]  Tipp

Weitere Informationen (zum Beispiel Backup-Intervalle) finden Sie auf der Seite Auto Backup for Apps.[ 15 ](https://developer.android.com/guide/topics/data/autobackup.html) Dort ist auch beschrieben, wie Ihre App mit dem Manifestattribut android:fullBackupContent und einer XML-Datei Einfluss darauf nehmen kann, welche Daten gesichert bzw. ausgelassen werden.

Wird eine App deinstalliert und später erneut heruntergeladen (zum Beispiel nach dem Zurücksetzen auf die Werkseinstellungen) oder auf einem neuen Gerät des Benutzers installiert, sichert die Plattform automatisch die gespeicherten Daten zurück. Genial, nicht wahr? Übrigens setzen die meisten meiner Beispiele android:allowBackup auf false, um keinen unnötigen »Müll« auf einem von Ihnen möglicherweise konfigurierten Google Drive zu hinterlassen. Sie produzieren schließlich keine sicherungswürdigen Daten.

FileDemo1 nimmt an der Sicherung teil, damit ich Ihnen den Backup-Restore-Zyklus zeigen kann. Geben Sie hierzu in der App einen beliebigen Text ein, und klicken Sie auf SPEICHERN. Anschließend prüfen Sie bitte, ob in den Einstellungen Ihres Geräts bzw. Emulators unter System • Backup das Backup aktiviert und mit einem Google-Konto verknüpft ist. Wie das aussehen kann, ist in Abbildung 9.2 zu sehen.

Backup-Konto im Emulator

Abbildung 9.2    Backup-Konto im Emulator

Bitte klicken Sie nicht auf Back up now, sondern tippen Sie danach im Terminal-Fenster von Android Studio die folgenden Anweisungen ein:

  • adb shell bmgr list transports
    Damit können Sie die Liste der Transporte abfragen. Ohne einen solchen Backup Transport funktionieren Backup und Restore nicht. Ein Sternchen kennzeichnet den Standard.

  • adb shell bmgr transport ...
    aktiviert einen Transport. Sollte die Wiederherstellung mit der Voreinstellung nicht funktionieren, kann es lohnen, einen der anderen gelisteten Transporte auszuprobieren.

  • adb shell bmgr enabled
    prüft, ob der Backup Manager aktiv ist.

  • adb shell bmgr enable true
    aktiviert ihn. Bitte beachten Sie nochmals, dass Sie im Emulator bzw. auf dem Gerät die Backup-Funktionalität mit einem Google-Konto verknüpft haben müssen.

  • adb shell bmgr help
    Mit diesem Befehl erhalten Sie Hilfestellung.

Jetzt können Sie die Sicherung auslösen:

adb shell bmgr backupnow com.thomaskuenneth.androidbuch.filedemo1

Die App wird automatisch geschlossen. Um die Wiederherstellung auszuprobieren, ist es nicht nötig, sie zu deinstallieren. Klicken Sie stattdessen in FileDemo1 zuerst auf LEEREN, dann auf SPEICHERN. Der Wiederherstellungsvorgang startet nach der Eingabe des Kommandos

adb shell bmgr restore <...> com.thomaskuenneth.androidbuch.filedemo1

Der Platzhalter <...> repräsentiert ein Token. Leider ist das Ermitteln dieses Tokens etwas mühsam. Bitte geben Sie die folgende Anweisung ein, woraufhin recht viele Informationen angezeigt werden:

adb shell dumpsys backup

Das Token ist die Hexadezimalzahl nach Current:.

[»]  Hinweis

Die hier skizzierten Komponenten Backup Manager und Transport sind seit Android 2.2 vorhanden. Vor Marshmallow mussten Apps aber selbstständig sogenannte Backup Agents implementieren (ein nicht ganz triviales Unterfangen) und bei Bedarf die BackupManager-Methode dataChanged() aufrufen. Dies ist zum Glück nicht mehr nötig. Informationen zu dieser alten, Key/Value Backup genannten Variante finden Sie im gleichnamigen Dokument.[ 16 ](https://developer.android.com/guide/topics/data/keyvaluebackup.html)

Datei- und Verzeichnisfunktionen

Da Android die Datei- und Verzeichnisfunktionen von Java erbt, ist es sehr einfach, die Länge einer Datei zu ermitteln oder sie zu löschen. Wie das funktioniert, zeige ich Ihnen anhand der App FileDemo2, die Sie in Abbildung 9.3 sehen. Die Hauptklasse FileDemo2Activity (Listing 9.2) ist recht kurz. In der Methode onCreate() lege ich zunächst zehn Dateien an, deren Namen aus dem Präfix Datei_ und einer Zahl zwischen 1 und 10 bestehen. Diese Zahl gibt auch die Länge in Bytes an. Datei_7 ist also 7 Bytes groß. Die zu speichernden Daten werden in einem ByteArray gesammelt. Alle Elemente haben den gleichen Inhalt, nämlich eine Zahl, die der Länge des Feldes und damit der Datei entspricht. Das Feld wird durch Aufruf der FileOutputStream-Methode write() geschrieben.

Die App »FileDemo2«

Abbildung 9.3    Die App »FileDemo2«

Sie können mit filesDir (die Methode getFilesDir() gehört zur Klasse android.content.Context) den Pfad des Verzeichnisses erfragen, in dem die mit openFileOutput() erzeugten Dateien abgelegt werden. Deren Namen erfahren Sie mit fileList(). Aus diesen beiden Informationen lässt sich ein java.io.File-Objekt bauen, um beispielsweise die Länge einer Datei zu ermitteln (mit length()) oder die Datei zu löschen. Der Rückgabewert von delete() signalisiert, ob das Löschen erfolgreich war. Dateien und Verzeichnisse einer App, die mit openFileOutput() bzw. getDir() erzeugt wurden, liegen unterhalb eines anwendungsspezifischen Basisverzeichnisses. Im Rahmen der Deinstallation werden sie gelöscht.

package com.thomaskuenneth.androidbuch.filedemo2

import android.content.Context
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*
import java.io.File
import java.io.IOException

class FileDemo2Activity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
tv.text = ""
// 10 Dateien mit unterschiedlicher Länge anlegen
for (i in 1..10) {
val name = "Datei_$i"
try {
openFileOutput(name, Context.MODE_PRIVATE).use { fos ->
// ein Feld der Länge i mit dem Wert i füllen
val bytes = ByteArray(i) { i.toByte() }
fos.write(bytes)
}
} catch (t: IOException) {
tv.append("$name: ${t.message}")
}
}
// Dateien ermitteln
val files = fileList()
// Verzeichnis ermitteln
for (name in files) {
val f = File(filesDir, name)
// Länge in Bytes ermitteln
tv.append("Länge von $name in Byte: ${f.length()}\n")
// Datei löschen
tv.append("Löschen ${if (!f.delete())
"
nicht " else ""}erfolgreich\n")
}
}
}

Listing 9.2    Die Klasse »FileDemo2Activity«

Erzeugt eine App nur wenige Dateien, ist das Ablegen in verschiedenen Verzeichnissen nicht nötig. Spielen Dateien jedoch eine zentrale Rolle, kann es sich lohnen, sie in unterschiedlichen Ordnern zu speichern. Sehen wir uns an, wie dies funktioniert.

 
Zum Seitenanfang

9.1.2    Mit Verzeichnissen arbeiten Zur vorigen ÜberschriftZur nächsten Überschrift

android.content.Context enthält die Methode getDir(), die als ersten Parameter den Namen eines Verzeichnisses erwartet. Sofern dieses noch nicht existiert, wird es automatisch erzeugt. Das zweite Argument steuert, wer darauf zugreifen darf. Sie kennen diesen Parameter schon aus den vorherigen Abschnitten. Da der Zugriff durch Dritte nicht mehr erlaubt ist, übergeben Sie bitte stets MODE_PRIVATE. Die App FileDemo3 demonstriert, wie Sie getDir() verwenden. FileDemo3Activity (Listing 9.3) erzeugt die Dateien A und B im Standardverzeichnis der App. Sie können den Pfad mit filesDir ermitteln. Außerdem werden die beiden Ordner audio und video angelegt. Beide erhalten jeweils zwei Dateien, C und D bzw. E und F.

package com.thomaskuenneth.androidbuch.filedemo3

import android.content.Context
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import java.io.*

private val TAG = FileDemo3Activity::class.simpleName
class FileDemo3Activity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// zwei leere Dateien erzeugen
createFile(filesDir, "A")
createFile(filesDir, "B")
// Verzeichnis audio erstellen
val dirAudio = getDir("audio", Context.MODE_PRIVATE)
// zwei leere Dateien erzeugen
createFile(dirAudio, "C")
createFile(dirAudio, "D")
// Verzeichnis video erstellen
val dirVideo = getDir("video", Context.MODE_PRIVATE)
// zwei leere Dateien erzeugen
createFile(dirVideo, "E")
createFile(dirVideo, "F")
// temporäre Datei anlegen
try {
Log.d(TAG, "java.io.tmpdir: ${
System.getProperty("java.io.tmpdir")}")
File.createTempFile("Datei_", ".txt").apply {
Log.d(TAG, "---> $absolutePath")
}
} catch (e: IOException) {
Log.e(TAG, " createTempFile()", e)
}
// temporäre Datei im Cache-Verzeichnis
Log.d(TAG, "cacheDir: ${cacheDir.absolutePath}")
try {
File.createTempFile("Datei_", ".txt", cacheDir).apply {
Log.d(TAG, "---> $absolutePath")
}
} catch (e: IOException) {
Log.e(TAG, " createTempFile()", e)
}
}

private fun createFile(dir: File, name: String) {
val file = File(dir, name)
try {
FileOutputStream(file).use { fos ->
Log.d(TAG, file.absolutePath)
fos.write("Hallo".toByteArray())
}
} catch (e: IOException) {
Log.e(TAG, "createFile()", e)
}
}
}

Listing 9.3    Die Klasse »FileDemo3Activity«

Die Verzeichnisstruktur nach dem Start von FileDemo3 ist in Abbildung 9.4 zu sehen. Sie können sie im Werkzeugfenster Device File Explorer überprüfen. Die Pfade werden in Logcat ausgegeben. Allerdings müssen Sie aufpassen: Bitte suchen Sie nicht nach /data/user/0, sondern /data/data. Die Ordner audio und video werden kurioserweise nicht innerhalb desselben Verzeichnisses (files) erzeugt, in dem auch A und B liegen. Sie finden die Verzeichnisse stattdessen eine Ebene weiter oben, also in com.thomaskuenneth.filedemo3. Bitte beachten Sie in beiden Fällen das automatisch hinzugefügte Präfix app_. Hierbei handelt es sich allerdings um ein Android-internes Detail, das auf Ihre Programmierung keinen Einfluss hat und auf das Sie sich auch nicht verlassen sollten.

Von »FileDemo3« erzeugte Dateien und Verzeichnisse

Abbildung 9.4    Von »FileDemo3« erzeugte Dateien und Verzeichnisse

Meine private Methode createFile() nutzt übrigens nicht die aus dem vorherigen Abschnitt bekannte Methode openFileOutput(), weil dieser Methode kein Verzeichnisname (bzw. kein Pfad) übergeben werden kann. Stattdessen wird direkt ein FileOutputStream-Objekt erzeugt. Hierzu verwende ich eine Instanz der Klasse java.io.File, der ein Verzeichnis sowie der Dateiname übergeben werden.

Temporäre Dateien und Caches

Es ist häufig nötig, temporäre Dateien zu erzeugen. Denken Sie an eine App, die Newsfeeds anzeigt: Um die Nachrichten darstellen zu können, muss die korrespondierende Datei zuerst von einem Server geladen werden. Sobald sie geparst wurde, wird sie jedoch nicht mehr benötigt. Solche kurzlebigen Dateien sollten nicht im Applikationsverzeichnis abgelegt werden. Java enthält in der Klasse java.io.File die statische Methode createTempFile(). Sie erleichtert das Erzeugen von temporären Dateien. Zwischen dem mindestens drei Zeichen langen Präfix (in meinem Beispiel ist das Datei_) und dem Suffix .txt fügt das System einen automatisch erzeugten Teil (eine zufällige ganze Zahl) ein. Das Suffix kann null sein, in diesem Fall wird .tmp verwendet.

In welchem Verzeichnis die Datei angelegt wird, ergibt sich aus einem optionalen dritten Parameter, den die in meinem Beispiel verwendete Zweiparametervariante auf null setzt. Das führt dazu, dass die Java-System-Property java.io.tmpdir ausgewertet wird. Ich gebe den Wert in Logcat aus. Wenn Sie sich nicht auf die System Property verlassen möchten, übergeben Sie als dritten Parameter von createTempFile() einfach das Ergebnis des Aufrufes getCacheDir() bzw. cacheDir (ebenfalls in der Klasse Context enthalten).

[+]  Tipp

Grundsätzlich kann das System Verzeichnisse für temporäre Dateien bei Bedarf (zum Beispiel bei Speichermangel) selbstständig leeren. Allerdings sollten Sie als Entwickler sorgsam mit unter Umständen knappen Ressourcen umgehen und deshalb nicht mehr benötigte Dateien möglichst schnell wieder löschen.

 


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