Es gibt viele Möglichkeiten, Ihre Daten zu speichern, z. B. in einer Online-Datenbank, in einer lokalen SQLite-Datenbank oder sogar in einer Textdatei. Sie müssen selbst entscheiden, welche die beste Lösung für Ihre Anwendung ist. In dieser Lektion erfahren Sie, wie Sie eine virtuelle SQLite-Tabelle erstellen, die eine zuverlässige Volltextsuche bietet. Die Tabelle wird mit Daten aus einer Textdatei gefüllt, die in jeder Zeile der Datei ein Wort/Definition-Paar enthält.
Virtuellen Tisch erstellen
Eine virtuelle Tabelle verhält sich ähnlich wie eine SQLite-Tabelle, liest und schreibt jedoch über Callbacks in ein Objekt im Arbeitsspeicher und nicht in eine Datenbankdatei. Um eine virtuelle Tabelle zu erstellen, legen Sie eine Klasse für die Tabelle an:
Kotlin
class DatabaseTable(context: Context) { private val databaseOpenHelper = DatabaseOpenHelper(context) }
Java
public class DatabaseTable { private final DatabaseOpenHelper databaseOpenHelper; public DatabaseTable(Context context) { databaseOpenHelper = new DatabaseOpenHelper(context); } }
Erstellen Sie eine innere Klasse in DatabaseTable
, die SQLiteOpenHelper
erweitert. Die Klasse SQLiteOpenHelper
definiert abstrakte Methoden, die überschrieben werden müssen, damit Ihre Datenbanktabelle bei Bedarf erstellt und aktualisiert werden kann. Hier sehen Sie beispielsweise Code, der eine Datenbanktabelle deklariert, die Wörter für eine Wörterbuchanwendung enthält:
Kotlin
private const val TAG = "DictionaryDatabase" // The columns we'll include in the dictionary table const val COL_WORD = "WORD" const val COL_DEFINITION = "DEFINITION" private const val DATABASE_NAME = "DICTIONARY" private const val FTS_VIRTUAL_TABLE = "FTS" private const val DATABASE_VERSION = 1 private const val FTS_TABLE_CREATE = "CREATE VIRTUAL TABLE $FTS_VIRTUAL_TABLE USING fts3 ($COL_WORD, $COL_DEFINITION)" class DatabaseTable(context: Context) { private val databaseOpenHelper: DatabaseOpenHelper init { databaseOpenHelper = DatabaseOpenHelper(context) } private class DatabaseOpenHelper internal constructor(private val helperContext: Context) : SQLiteOpenHelper(helperContext, DATABASE_NAME, null, DATABASE_VERSION) { private lateinit var mDatabase: SQLiteDatabase override fun onCreate(db: SQLiteDatabase) { mDatabase = db mDatabase.execSQL(FTS_TABLE_CREATE) } override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { Log.w( TAG, "Upgrading database from version $oldVersion to $newVersion , which will " + "destroy all old data" ) db.execSQL("DROP TABLE IF EXISTS $FTS_VIRTUAL_TABLE") onCreate(db) } } }
Java
public class DatabaseTable { private static final String TAG = "DictionaryDatabase"; // The columns we'll include in the dictionary table public static final String COL_WORD = "WORD"; public static final String COL_DEFINITION = "DEFINITION"; private static final String DATABASE_NAME = "DICTIONARY"; private static final String FTS_VIRTUAL_TABLE = "FTS"; private static final int DATABASE_VERSION = 1; private final DatabaseOpenHelper databaseOpenHelper; public DatabaseTable(Context context) { databaseOpenHelper = new DatabaseOpenHelper(context); } private static class DatabaseOpenHelper extends SQLiteOpenHelper { private final Context helperContext; private SQLiteDatabase mDatabase; private static final String FTS_TABLE_CREATE = "CREATE VIRTUAL TABLE " + FTS_VIRTUAL_TABLE + " USING fts3 (" + COL_WORD + ", " + COL_DEFINITION + ")"; DatabaseOpenHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); helperContext = context; } @Override public void onCreate(SQLiteDatabase db) { mDatabase = db; mDatabase.execSQL(FTS_TABLE_CREATE); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { Log.w(TAG, "Upgrading database from version " + oldVersion + " to " + newVersion + ", which will destroy all old data"); db.execSQL("DROP TABLE IF EXISTS " + FTS_VIRTUAL_TABLE); onCreate(db); } } }
Virtuellen Tisch ausfüllen
Die Tabelle benötigt jetzt Daten zum Speichern. Mit dem folgenden Code wird gezeigt, wie Sie eine Textdatei (unter res/raw/definitions.txt
) lesen, die Wörter und ihre Definitionen enthält, wie Sie diese Datei parsen und wie Sie jede Zeile dieser Datei als Zeile in die virtuelle Tabelle einfügen. Dies wird alles in einem anderen Thread durchgeführt, um zu verhindern, dass die UI gesperrt wird. Fügen Sie der inneren Klasse DatabaseOpenHelper
den folgenden Code hinzu.
Tipp: Sie können auch einen Callback einrichten, der Ihre UI-Aktivität über den Abschluss dieses Threads benachrichtigt.
Kotlin
private fun loadDictionary() { Thread(Runnable { try { loadWords() } catch (e: IOException) { throw RuntimeException(e) } }).start() } @Throws(IOException::class) private fun loadWords() { val inputStream = helperContext.resources.openRawResource(R.raw.definitions) BufferedReader(InputStreamReader(inputStream)).use { reader -> var line: String? = reader.readLine() while (line != null) { val strings: List<String> = line.split("-").map { it.trim() } if (strings.size < 2) continue val id = addWord(strings[0], strings[1]) if (id < 0) { Log.e(TAG, "unable to add word: ${strings[0]}") } line = reader.readLine() } } } fun addWord(word: String, definition: String): Long { val initialValues = ContentValues().apply { put(COL_WORD, word) put(COL_DEFINITION, definition) } return database.insert(FTS_VIRTUAL_TABLE, null, initialValues) }
Java
private void loadDictionary() { new Thread(new Runnable() { public void run() { try { loadWords(); } catch (IOException e) { throw new RuntimeException(e); } } }).start(); } private void loadWords() throws IOException { final Resources resources = helperContext.getResources(); InputStream inputStream = resources.openRawResource(R.raw.definitions); BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); try { String line; while ((line = reader.readLine()) != null) { String[] strings = TextUtils.split(line, "-"); if (strings.length < 2) continue; long id = addWord(strings[0].trim(), strings[1].trim()); if (id < 0) { Log.e(TAG, "unable to add word: " + strings[0].trim()); } } } finally { reader.close(); } } public long addWord(String word, String definition) { ContentValues initialValues = new ContentValues(); initialValues.put(COL_WORD, word); initialValues.put(COL_DEFINITION, definition); return database.insert(FTS_VIRTUAL_TABLE, null, initialValues); }
Rufen Sie gegebenenfalls die Methode loadDictionary()
auf, um die Tabelle zu füllen. Ein guter Ort wäre in der Methode onCreate()
der Klasse DatabaseOpenHelper
, direkt nachdem du die Tabelle erstellt hast:
Kotlin
override fun onCreate(db: SQLiteDatabase) { database = db database.execSQL(FTS_TABLE_CREATE) loadDictionary() }
Java
@Override public void onCreate(SQLiteDatabase db) { database = db; database.execSQL(FTS_TABLE_CREATE); loadDictionary(); }
Nach der Abfrage suchen
Wenn Sie die virtuelle Tabelle erstellt und ausgefüllt haben, verwenden Sie die von SearchView
bereitgestellte Abfrage, um die Daten zu durchsuchen. Fügen Sie der Klasse DatabaseTable
die folgenden Methoden hinzu, um eine SQL-Anweisung zu erstellen, die nach der Abfrage sucht:
Kotlin
fun getWordMatches(query: String, columns: Array<String>?): Cursor? { val selection = "$COL_WORD MATCH ?" val selectionArgs = arrayOf("$query*") return query(selection, selectionArgs, columns) } private fun query( selection: String, selectionArgs: Array<String>, columns: Array<String>? ): Cursor? { val cursor: Cursor? = SQLiteQueryBuilder().run { tables = FTS_VIRTUAL_TABLE query(databaseOpenHelper.readableDatabase, columns, selection, selectionArgs, null, null, null) } return cursor?.run { if (!moveToFirst()) { close() null } else { this } } ?: null }
Java
public Cursor getWordMatches(String query, String[] columns) { String selection = COL_WORD + " MATCH ?"; String[] selectionArgs = new String[] {query+"*"}; return query(selection, selectionArgs, columns); } private Cursor query(String selection, String[] selectionArgs, String[] columns) { SQLiteQueryBuilder builder = new SQLiteQueryBuilder(); builder.setTables(FTS_VIRTUAL_TABLE); Cursor cursor = builder.query(databaseOpenHelper.getReadableDatabase(), columns, selection, selectionArgs, null, null, null); if (cursor == null) { return null; } else if (!cursor.moveToFirst()) { cursor.close(); return null; } return cursor; }
Suchen Sie nach einer Abfrage, indem Sie getWordMatches()
aufrufen. Alle übereinstimmenden Ergebnisse werden in einem Cursor
zurückgegeben, das Sie iterieren oder zum Erstellen eines ListView
verwenden können.
In diesem Beispiel wird getWordMatches()
in der Methode handleIntent()
der durchsuchbaren Aktivität aufgerufen. Denken Sie daran, dass die durchsuchbare Aktivität die Abfrage im ACTION_SEARCH
-Intent aufgrund des zuvor erstellten Intent-Filters zusätzlich erhält:
Kotlin
private val db = DatabaseTable(this) ... private fun handleIntent(intent: Intent) { if (Intent.ACTION_SEARCH == intent.action) { val query = intent.getStringExtra(SearchManager.QUERY) val c = db.getWordMatches(query, null) // process Cursor and display results } }
Java
DatabaseTable db = new DatabaseTable(this); ... private void handleIntent(Intent intent) { if (Intent.ACTION_SEARCH.equals(intent.getAction())) { String query = intent.getStringExtra(SearchManager.QUERY); Cursor c = db.getWordMatches(query, null); // process Cursor and display results } }