هناك العديد من الطرق لتخزين بياناتك، مثلاً في قاعدة بيانات على الإنترنت أو في قاعدة بيانات SQLite محلية أو حتى في ملف نصي. الأمر متروك لك لتحديد الحلّ الأفضل لتطبيقك. يشرح هذا الدرس طريقة إنشاء جدول افتراضي بلغة SQLite يمكن أن يوفّر عملية بحث فعّالة عن النص الكامل. تتم تعبئة الجدول ببيانات من ملف نصي يتضمّن زوجًا من الكلمات والتعريف في كل سطر في الملف.
إنشاء الجدول الافتراضي
يعمل الجدول الافتراضي بالطريقة نفسها التي يعمل بها جدول SQLite، ولكنّه يقرأ عنصر في الذاكرة ويكتبه من خلال عمليات الاستدعاء، بدلاً من ملف قاعدة بيانات. لإنشاء جدول افتراضي، أنشئ فئة للجدول:
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); } }
إنشاء صف داخلي في DatabaseTable
يمتد إلى SQLiteOpenHelper
. تحدِّد الفئة SQLiteOpenHelper
طُرقًا مجرّدة يجب إلغاؤها بحيث يمكن إنشاء جدول قاعدة البيانات وترقيته عند الضرورة. على سبيل المثال، إليك بعض الرموز التي تتضمّن جدول قاعدة بيانات يحتوي على كلمات لتطبيق القاموس:
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); } } }
تعبئة الجدول الافتراضي
يحتاج الجدول الآن إلى بيانات للتخزين. ويوضّح الرمز التالي كيفية قراءة ملف نصي (في res/raw/definitions.txt
) يحتوي على الكلمات وتعريفاتها، وكيفية
تحليل هذا الملف، وطريقة إدراج كل سطر من هذا الملف كصف في الجدول الافتراضي. يتم
كل ذلك في سلسلة محادثات أخرى لمنع قفل واجهة المستخدم. أضِف الرمز التالي إلى الصف الداخلي في DatabaseOpenHelper
.
ملاحظة: ننصحك أيضًا بإعداد معاودة الاتصال لإرسال إشعار إلى نشاط واجهة المستخدم باكتمال سلسلة المحادثات هذه.
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); }
عليك استدعاء الطريقة loadDictionary()
حيثما كان ذلك مناسبًا لتعبئة الجدول. قد يكون المكان المناسب ضمن طريقة onCreate()
من الفئة DatabaseOpenHelper
بعد إنشاء الجدول مباشرةً:
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(); }
البحث عن طلب البحث
بعد إنشاء الجدول الافتراضي وتعبئته، استخدِم طلب البحث المقدَّم من SearchView
للبحث في البيانات. أضِف الطرق التالية إلى الفئة DatabaseTable
لإنشاء عبارة SQL تبحث عن طلب البحث:
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; }
يمكنك طلب البحث من خلال الاتصال بـ getWordMatches()
. يتم عرض أي نتائج مطابقة في Cursor
ويمكنك تكرارها أو استخدامها لإنشاء ListView
.
يستدعي هذا المثال getWordMatches()
باستخدام الطريقة handleIntent()
للنشاط القابل للبحث. تذكَّر أنّ النشاط القابل للبحث يتلقّى طلب البحث في هدف ACTION_SEARCH
كطلب إضافي، وذلك بسبب فلتر الأهداف الذي
أنشأته في السابق:
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 } }