Android Content Provider

Wozu sind die noch mal gut? Ah ja, wenn man auf eine SQLite Datenbank aus einer App zugreift, kann bei einem direkten Zugriff nur die App auf die Datenbank zugreifen. Wird hingegen ein ContentProvider benutzt, können auch andere Anwendungen auf die Datenbank zugreifen. Als Beispiel müssen die Kontakte herhalten – Diese werden von diversen Apps benutzt.

Damit der Contentprovider funktioniert sind einige Vorgaben zu beachten:

  • Die eigene Contentprovider Klasse muss von ContentProvider abgeleitet werden. In der abgeleiteten Klasse müssen die Methoden onCreate, query, insert, update, delete und getType überschrieben werden. Sie werden quasi auf die Datenbank Methoden umgeleitet
  • Statt in der eigenen App direkt auf die Datenbank zuzugreifen, wird jetzt auf den ContentProvider zugegriffen.
  • Der Contentprovider muss in der Manifest Datei mit dem Tag <Provider … bekanntgemacht werden.

Um überhaupt auf Daten zugreifen zu können, benötigt man eine URI zu eben diesen Daten.  Die dabei verwendeten Uri’s erinnern mich start an das Erzeugen einer REST Schnittstelle. Um alle Daten der Tabelle products abzurufen, gibt man z.B.
content://de.stubbe_cs.database.provider.MyContentProvider/products
ein. Um an einen speziellen Rekord zu gelangen gibt man content://de.stubbe_cs.database.provider.MyContentProvider/products/6
ein, wobei die 6 die ID des Rekords bezeichnet.

Zum Bearbeiten der Uri’s kann man sehr gut die Klasse UriMatcher verwenden.:

    private static final String AUTHORITY = "de.stubbe_cs.database.provider.MyContentProvider";
    private static final String PRODUCTS_TABLE = "products";

    // Typ der URI. 1: ganze Tabelle, 2: Record
    public static final int PRODUCTS = 1;
    public static final int PRODUCTS_ID = 2;

    private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);

    static {
        sURIMatcher.addURI(AUTHORITY, PRODUCTS_TABLE, PRODUCTS);
        sURIMatcher.addURI(AUTHORITY, PRODUCTS_TABLE + "/#", PRODUCTS_ID);
    }
    ...
    ...
    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        // Typ der uri
        int uriType = sURIMatcher.match(uri);

Es wird ein Objekt der Klasse UriMatcher erzeugt und es werden Muster für Uri’s hinterlegt. Das erste Muster soll aus dem String AUTHORITY und dem String PRODUCTS_TABLE zusammengesetzt sein. Der Typ dieses Musters wird auf PRODUCTS festgelegt. Wird jetzt z.B. die

Uricontent://de.stubbe_cs.database.provider.MyContentProvider/products

gefunden, gibt sUriMatcher.match(uri) eine 1 zurück, was dem Typ PRODUCTS entspricht. Mit

uri.getLastPathSegment()

erhält man den String PRODUCTS_TABLE (hier also products).

SQLite unter Android mit Speicherung auf der SD Karte

Ich habe mich mit dem SQLite – Beispiel aus dem Buch “Android Studio Development“ von Neil Smyth beschäftigt. Zunächst habe ich das Beispiel ans Laufen gebracht, was aber mithilfe des Buches sehr einfach ist. Dann habe ich versucht mir auf meinem Smartphone die Datenbank anzusehen. Das klappte aber leider nicht, da dazu die Berechtigung fehlt. Also das Beispiel auf das virtuelle Smartphone gebracht. Jetzt hat man die Möglichkeit über eine Konsole (bei mir unter Windows 7) mit

adb -e shell

auf das virtuelle Smartphone zuzugreifen. Dort hat man dann auch die nötigen Rechte. Im Verzeichnis

/data/data/de.stubbe_cs.database/databases

befindet sich die Datenbank des Beispielprojektes. Man kann sie sich ansehen, indem man in die Konsole sqlite3 eingibt. Das startet ein SQLite Tool. Dort kann man wiederum .open ./<dbname> und dann .tables eingeben. Als Ergebnis werden dann die Tabellennamen der Datenbank angezeigt. Mit .schema erhält man auch Infos über die Felder in den Tabellen.

Da es scheinbar nur schwer möglich ist, die Datenbank auf einem Smartphone zu inspizieren, habe ich mir überlegt, die Datenbank auf der SD Karte meines Smartphones auszulagern. Dazu müssen allerdings einige Änderungen vorgenommen werden.

public class MyDBHandler extends SQLiteOpenHelper {
    private static final int DATABASE_VERSION = 1;
    //private static final String DATABASE_NAME = "procuctDB.db";
    private static final String DATABASE_NAME = "/sdcard/procuctDB.db";  // Neu
    public static final String TABLE_PRODUCTS = "products";
    public static final String COLUMN_ID = "_id";
    public static final String COLUMN_PRODUCTNAME = "productname";
    public static final String COLUMN_QUANTITY = "quantity";
    public MyDBHandler(Context context, String name,
                       SQLiteDatabase.CursorFactory factory, int version){
        super(context, DATABASE_NAME, factory, DATABASE_VERSION);
        SQLiteDatabase.openOrCreateDatabase(DATABASE_NAME, null); // neu
    }
    @Override
    public void onCreate(SQLiteDatabase db) {
        // Tabelle erzeugen
        String CREATE_PRODUCTS_TABLE = "CREATE TABLE " +
                TABLE_PRODUCTS + "(" + COLUMN_ID + " INTEGER PRIMARY KEY," +
                COLUMN_PRODUCTNAME + " TEXT," + COLUMN_QUANTITY +
                " INTEGER" + ")";
        db.execSQL(CREATE_PRODUCTS_TABLE);
    }

Dabei wurde in den Datenbankname der Pfad auf die SD Karte mit eingefügt und in den Konstruktor wurde  die Methode

SQLiteDatabase.openOrCreateDatabase

eingefügt. Dann muss noch in der Manifest Datei die Berechtigung zum Schreiben auf externe Medien gesetzt werden:

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

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"

Das passiert hier in Zeile 5 mit dem usespermission Tag.

Nach dem Start der App wurde die Datenbank auf die SD Karte ausgelagert. Aber wie überprüft man das? Im Emulator gab es zunächst Probleme. Wo steckt man die SD Karte rein? Das erledigte sich aber, denn beim Start der Datenbankanwendnung hat der Emulator angeboten, eine virtuelle SD Karte anzulegen. Ich habe die interne Variante gewählt und die virtuelle SD Karte formatiert. Dann klappte es immer noch nicht, da in den Einstellungen der App es keine Berechtigung für den Zugriff auf die SD Karte gab. Nachdem diese Probleme gelöst waren, konnte die App im Emulator gestartet werden. Das die Datenbank jetzt auf der SD Karte ist, kann man wierderum über die Konsole überprüfen. In der Shell wechselt man in das Verzeichnis /sdcard und findet dort dann (mit ls) die Datenbank Dateien.

Auf dem Smartphone klappte das genauso. Um die Datenbank in Ruhe inspizieren zu können, habe ich sie per FTP auf meinen PC gezogen und mit dem SQLite Browser untersucht.

Interessanter Weise wird mein Smartphone via USB Treiber in das Dateisystem meines Windows 7 PCs eingebunden. Die Datenbankdateien unter /sdcard sind dort allerdings nicht zu sehen !