七、使用数据库

在前一章中,我们获得了访问安卓系统功能所需的关键权限。在我们的例子中,我们获得了位置权限。在本章中,我们将继续向数据库中插入数据。我们将插入从安卓位置提供商那里获得的位置数据。为此,我们将定义适当的数据库模式和管理类。我们还将定义类来访问位置提供者以获取位置数据。

在本章中,我们将涵盖以下主题:

  • SQLite 简介
  • 描述数据库
  • CRUD 操作

SQLite 简介

为了给我们的应用保存数据,我们需要一个数据库。在安卓中,对于离线数据存储,可以使用 SQLite。

SQLite 是开箱即用支持的,这意味着它包含在安卓框架中。

利益

SQLite 的好处是它强大、快速、可靠,拥有使用它的庞大社区。如果你发现任何问题,很容易找到解决方案,因为社区中的某个人很可能已经解决了这些问题。SQLite 是一个自包含、嵌入式、全功能、公共域的 SQL 数据库引擎。

我们将使用 SQLite 来存储我们所有的 Todos 和 Notes。为此,我们将定义我们的数据库、访问它的机制和数据管理。我们不会直接公开一个裸露的数据库实例,但是我们会适当地包装它,以便于插入、更新、查询或移除数据。

描述我们的数据库

我们要做的第一件事是通过用正确的数据类型定义表和列来描述我们的数据库。我们还将定义简单的模型来表示我们的数据。为此,创建一个名为database的新包:

     com.journaler.database 

然后,创建一个名为DbModel的新 Kotlin 类。DbModel类将代表我们应用的所有数据库模型的矩阵,并将只包含标识,因为标识是一个强制字段,将用作主键。确保你的DbModel课是这样的:

    package com.journaler.database 

    abstract class DbModel { 
      abstract var id: Long 
    } 

现在,当我们定义起点时,我们将定义实际包含数据的数据类。在我们现有的名为model的包中,创建新的类- DbEntryNoteTodoNoteTodo将延伸Entry,这就延伸了DbModel等级。

Entry类代码如下:

    package com.journaler.model 

    import android.location.Location 
    import com.journaler.database.DbModel 

    abstract class Entry( 
      var title: String, 
      var message: String, 
      var location: Location 
    ) : DbModel() 
    Note class: 
    package com.journaler.model 

    import android.location.Location 

    class Note( 
      title: String, 
      message: String, 
      location: Location 
    ) : Entry( 
        title, 
        message, 
        location 
        ) { 
         override var id = 0L 
        } 

您会注意到,我们将当前地理位置作为信息与titlemessage内容一起存储在笔记中。我们还覆盖了标识。由于新实例化的note尚未存储到数据库中,其标识将为零。在我们存储它之后,它将被更新为从数据库中获得的 ID 值。

Todo类:

    package com.journaler.model 

    import android.location.Location 

    class Todo( 
      title: String, 
      message: String, 
      location: Location, 
      var scheduledFor: Long 
    ) : Entry( 
        title, 
        message, 
        location 
    ) { 
    override var id = 0L 
    } 

在安排todo的时间内,Todo类将比Note类- timestamp多一个字段。

现在,在我们定义了数据模型之后,我们将描述我们的数据库。我们必须定义负责数据库初始化的数据库助手类。数据库助手类必须扩展 Android 的SQLiteOpenHelper类。创建DbHelper类,并确保它扩展了SQLiteOpenHelper类:

    package com.journaler.database 

    import android.database.sqlite.SQLiteDatabase 
    import android.database.sqlite.SQLiteOpenHelper 
    import android.util.Log 
    import com.journaler.Journaler 

    class DbHelper(val dbName: String, val version: Int) :   
    SQLiteOpenHelper( 
      Journaler.ctx, dbName, null, version 
    ) { 

      companion object { 
        val ID: String = "_id" 
        val TABLE_TODOS = "todos" 
        val TABLE_NOTES = "notes" 
        val COLUMN_TITLE: String = "title" 
        val COLUMN_MESSAGE: String = "message" 
        val COLUMN_SCHEDULED: String = "scheduled" 
        val COLUMN_LOCATION_LATITUDE: String = "latitude" 
        val COLUMN_LOCATION_LONGITUDE: String = "longitude" 
      } 

      private val tag = "DbHelper" 

      private val createTableNotes =  """ 
        CREATE TABLE if not exists $TABLE_NOTES 
           ( 
             $ID integer PRIMARY KEY autoincrement, 
             $COLUMN_TITLE text, 
             $COLUMN_MESSAGE text, 
             $COLUMN_LOCATION_LATITUDE real, 
             $COLUMN_LOCATION_LONGITUDE real 
           ) 
          """ 

      private val createTableTodos =  """ 
        CREATE TABLE if not exists $TABLE_TODOS 
           ( 
              $ID integer PRIMARY KEY autoincrement, 
              $COLUMN_TITLE text, 
              $COLUMN_MESSAGE text, 
              $COLUMN_SCHEDULED integer, 
              $COLUMN_LOCATION_LATITUDE real, 
              $COLUMN_LOCATION_LONGITUDE real 
           ) 
         """ 

       override fun onCreate(db: SQLiteDatabase) { 
        Log.d(tag, "Database [ CREATING ]") 
        db.execSQL(createTableNotes) 
        db.execSQL(createTableTodos) 
        Log.d(tag, "Database [ CREATED ]") 
       } 

      override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int,
      newVersion: Int) { 
        // Ignore for now. 
      } 

    } 

我们的类companion对象包含表和列名的定义。我们还为表创建定义了 SqL。最后,在onCreate()方法中执行 SQL。在下一节中,我们将进一步讨论数据库管理,最后插入一些数据。

CRUD 操作

CRUD 操作是用于创建、更新、选择或移除数据的操作。它们是用名为Crud的接口定义的,并且是通用的。在database包中创建新界面。确保它涵盖所有 CRUD 操作:

     interface Crud<T> where T : DbModel { 

       companion object { 
        val BROADCAST_ACTION = "com.journaler.broadcast.crud" 
        val BROADCAST_EXTRAS_KEY_CRUD_OPERATION_RESULT = "crud_result" 
       } 

      /** 
       * Returns the ID of inserted item. 
       */ 
      fun insert(what: T): Long 

      /** 
       * Returns the list of inserted IDs. 
       */ 
      fun insert(what: Collection<T>): List<Long> 

      /** 
      * Returns the number of updated items. 
      */ 
      fun update(what: T): Int 

      /** 
      * Returns the number of updated items. 
      */ 
      fun update(what: Collection<T>): Int 

      /** 
      * Returns the number of deleted items. 
      */ 
      fun delete(what: T): Int 

      /** 
      * Returns the number of deleted items. 
      */ 
      fun delete(what: Collection<T>): Int 

      /** 
      * Returns the list of items. 
      */ 
      fun select(args: Pair<String, String>): List<T> 

      /** 
      * Returns the list of items. 
      */ 
      fun select(args: Collection<Pair<String, String>>): List<T> 

      /** 
      * Returns the list of items. 
      */ 
      fun selectAll(): List<T> 

    } 

对于执行 CRUD 操作,有两种方法版本。第一个版本接受实例集合,第二个版本接受单个项目。让我们通过创建一个名为Db的 Kotlin 对象来创建 CRUD 具体化。创建一个对象使我们的具体化成为一个完美的单例。Db对象必须实现Crud界面:

     package com.journaler.database 

     import android.content.ContentValues 
     import android.location.Location 
     import android.util.Log 
     import com.journaler.model.Note 
     import com.journaler.model.Todo 

     object Db { 

      private val tag = "Db" 
      private val version = 1 
      private val name = "students" 

      val NOTE = object : Crud<Note> { 
        // Crud implementations 
      } 

      val TODO = object : Crud<NoteTodo { 
         // Crud implementations 
      } 
    }  

插入 CRUD 操作

insert操作将向数据库中添加新数据。下面是它的实现:

    val NOTE = object : Crud<Note> { 
      ... 
      override fun insert(what: Note): Long { 
        val inserted = insert(listOf(what)) 
        if (!inserted.isEmpty()) return inserted[0] 
        return 0 
      } 

     override fun insert(what: Collection<Note>): List<Long> { 
       val db = DbHelper(name, version).writableDatabase 
       db.beginTransaction() 
       var inserted = 0 
       val items = mutableListOf<Long>() 
       what.forEach { item -> 
         val values = ContentValues() 
         val table = DbHelper.TABLE_NOTES 
         values.put(DbHelper.COLUMN_TITLE, item.title) 
         values.put(DbHelper.COLUMN_MESSAGE, item.message) 
         values.put(DbHelper.COLUMN_LOCATION_LATITUDE,
           item.location.latitude) 
         values.put(DbHelper.COLUMN_LOCATION_LONGITUDE,
           item.location.longitude) 
         val id = db.insert(table, null, values) 
           if (id > 0) { 
             items.add(id) 
             Log.v(tag, "Entry ID assigned [ $id ]") 
               inserted++ 
             } 
           } 
           val success = inserted == what.size 
           if (success) { 
                db.setTransactionSuccessful() 
           } else { 
                items.clear() 
           } 
            db.endTransaction() 
            db.close() 
            return items 
          } 
          ... 
    } 
    ... 
    val TODO = object : Crud<Todo> { 
      ... 
      override fun insert(what: Todo): Long { 
        val inserted = insert(listOf(what)) 
        if (!inserted.isEmpty()) return inserted[0] 
        return 0 
      } 

      override fun insert(what: Collection<Todo>): List<Long> { 
        val db = DbHelper(name, version).writableDatabase 
        db.beginTransaction() 
        var inserted = 0 
        val items = mutableListOf<Long>() 
        what.forEach { item -> 
          val table = DbHelper.TABLE_TODOS 
          val values = ContentValues() 
          values.put(DbHelper.COLUMN_TITLE, item.title) 
          values.put(DbHelper.COLUMN_MESSAGE, item.message) 
          values.put(DbHelper.COLUMN_LOCATION_LATITUDE,
          item.location.latitude) 
          values.put(DbHelper.COLUMN_LOCATION_LONGITUDE,
          item.location.longitude) 
          values.put(DbHelper.COLUMN_SCHEDULED, item.scheduledFor) 
            val id = db.insert(table, null, values) 
            if (id > 0) { 
              item.id = id 
              Log.v(tag, "Entry ID assigned [ $id ]") 
              inserted++ 
            } 
           } 
           val success = inserted == what.size 
           if (success) { 
                db.setTransactionSuccessful() 
           } else { 
               items.clear() 
           } 
           db.endTransaction() 
           db.close() 
           return items 
          } 
         ... 
     } 
     ... 

更新 CRUD 操作

update操作将更新我们数据库中的现有数据。下面是它的实现:

    val NOTE = object : Crud<Note> { 
       ... 
       override fun update(what: Note) = update(listOf(what)) 

       override fun update(what: Collection<Note>): Int { 
         val db = DbHelper(name, version).writableDatabase 
         db.beginTransaction() 
         var updated = 0 
         what.forEach { item -> 
           val values = ContentValues() 
           val table = DbHelper.TABLE_NOTES 
           values.put(DbHelper.COLUMN_TITLE, item.title) 
           values.put(DbHelper.COLUMN_MESSAGE, item.message) 
           values.put(DbHelper.COLUMN_LOCATION_LATITUDE,
           item.location.latitude) 
           values.put(DbHelper.COLUMN_LOCATION_LONGITUDE,
           item.location.longitude) 
           db.update(table, values, "_id = ?", 
           arrayOf(item.id.toString())) 
                updated++ 
           } 
           val result = updated == what.size 
           if (result) { 
             db.setTransactionSuccessful() 
           } else { 
             updated = 0 
           } 
           db.endTransaction() 
           db.close() 
           return updated 
          } 
          ... 
        } 
        ... 
      val TODO = object : Crud<Todo> { 
        ... 
        override fun update(what: Todo) = update(listOf(what)) 

        override fun update(what: Collection<Todo>): Int { 
          val db = DbHelper(name, version).writableDatabase 
          db.beginTransaction() 
          var updated = 0 
          what.forEach { item -> 
             val table = DbHelper.TABLE_TODOS 
             val values = ContentValues() 
             values.put(DbHelper.COLUMN_TITLE, item.title) 
             values.put(DbHelper.COLUMN_MESSAGE, item.message) 
             values.put(DbHelper.COLUMN_LOCATION_LATITUDE,
             item.location.latitude) 
            values.put(DbHelper.COLUMN_LOCATION_LONGITUDE,
            item.location.longitude) 
            values.put(DbHelper.COLUMN_SCHEDULED, item.scheduledFor) 
            db.update(table, values, "_id = ?",  
            arrayOf(item.id.toString())) 
               updated++ 
            } 
            val result = updated == what.size 
            if (result) { 
              db.setTransactionSuccessful() 
            } else { 
              updated = 0 
            } 
            db.endTransaction() 
            db.close() 
            return updated 
            } 
           ... 
      } 
     ...  

删除 CRUD 操作

delete操作将从数据库中删除现有数据。下面是它的实现:

    val NOTE = object : Crud<Note> { 
      ... 
      override fun delete(what: Note): Int = delete(listOf(what)) 
         override fun delete(what: Collection<Note>): Int { 
         val db = DbHelper(name, version).writableDatabase 
         db.beginTransaction() 
         val ids = StringBuilder() 
         what.forEachIndexed { index, item -> 
         ids.append(item.id.toString()) 
           if (index < what.size - 1) { 
              ids.append(", ") 
           } 
         } 
         val table = DbHelper.TABLE_NOTES 
         val statement = db.compileStatement( 
           "DELETE FROM $table WHERE ${DbHelper.ID} IN ($ids);" 
         ) 
         val count = statement.executeUpdateDelete() 
         val success = count > 0 
         if (success) { 
           db.setTransactionSuccessful() 
           Log.i(tag, "Delete [ SUCCESS ][ $count ][ $statement ]") 
         } else { 
            Log.w(tag, "Delete [ FAILED ][ $statement ]") 
         } 
          db.endTransaction() 
          db.close() 
          return count 
        } 
        ... 
     } 
     ... 
     val TODO = object : Crud<Todo> { 
       ... 
       override fun delete(what: Todo): Int = delete(listOf(what)) 
       override fun delete(what: Collection<Todo>): Int { 
         val db = DbHelper(name, version).writableDatabase 
         db.beginTransaction() 
         val ids = StringBuilder() 
         what.forEachIndexed { index, item -> 
         ids.append(item.id.toString()) 
            if (index < what.size - 1) { 
                ids.append(", ") 
            } 
        } 
        val table = DbHelper.TABLE_TODOS 
        val statement = db.compileStatement( 
          "DELETE FROM $table WHERE ${DbHelper.ID} IN ($ids);" 
        ) 
        val count = statement.executeUpdateDelete() 
        val success = count > 0 
        if (success) { 
           db.setTransactionSuccessful() 
           Log.i(tag, "Delete [ SUCCESS ][ $count ][ $statement ]") 
        } else { 
           Log.w(tag, "Delete [ FAILED ][ $statement ]") 
        } 
         db.endTransaction() 
         db.close() 
         return count 
        } 
        ... 
    } 
    ...  

选择 CRUD 操作

select操作将从数据库中读取并返回数据。下面是它的实现:

     val NOTE = object : Crud<Note> { 
        ... 
        override fun select( 
            args: Pair<String, String> 
        ): List<Note> = select(listOf(args)) 

        override fun select(args: Collection<Pair<String, String>>):
        List<Note> { 
          val db = DbHelper(name, version).writableDatabase 
          val selection = StringBuilder() 
          val selectionArgs = mutableListOf<String>() 
          args.forEach { arg -> 
              selection.append("${arg.first} == ?") 
              selectionArgs.add(arg.second) 
          } 
          val result = mutableListOf<Note>() 
          val cursor = db.query( 
              true, 
              DbHelper.TABLE_NOTES, 
              null, 
              selection.toString(), 
              selectionArgs.toTypedArray(), 
              null, null, null, null 
          ) 
          while (cursor.moveToNext()) { 
          val id = cursor.getLong(cursor.getColumnIndexOrThrow
          (DbHelper.ID)) 
          val titleIdx = cursor.getColumnIndexOrThrow
          (DbHelper.COLUMN_TITLE) 
          val title = cursor.getString(titleIdx) 
          val messageIdx = cursor.getColumnIndexOrThrow
          (DbHelper.COLUMN_MESSAGE) 
          val message = cursor.getString(messageIdx) 
          val latitudeIdx = cursor.getColumnIndexOrThrow( 
             DbHelper.COLUMN_LOCATION_LATITUDE 
          ) 
          val latitude = cursor.getDouble(latitudeIdx) 
          val longitudeIdx = cursor.getColumnIndexOrThrow( 
             DbHelper.COLUMN_LOCATION_LONGITUDE 
          ) 
          val longitude = cursor.getDouble(longitudeIdx) 
          val location = Location("") 
          location.latitude = latitude 
          location.longitude = longitude 
          val note = Note(title, message, location) 
          note.id = id 
          result.add(note) 
        } 
          cursor.close() 
          return result 
       } 

       override fun selectAll(): List<Note> { 
         val db = DbHelper(name, version).writableDatabase 
         val result = mutableListOf<Note>() 
         val cursor = db.query( 
            true, 
            DbHelper.TABLE_NOTES, 
            null, null, null, null, null, null, null 
         ) 
         while (cursor.moveToNext()) { 
                val id = cursor.getLong(cursor.getColumnIndexOrThrow
               (DbHelper.ID)) 
                val titleIdx = cursor.getColumnIndexOrThrow
                (DbHelper.COLUMN_TITLE) 
                val title = cursor.getString(titleIdx) 
                val messageIdx = cursor.getColumnIndexOrThrow
                (DbHelper.COLUMN_MESSAGE) 
                val message = cursor.getString(messageIdx) 
                val latitudeIdx = cursor.getColumnIndexOrThrow( 
                  DbHelper.COLUMN_LOCATION_LATITUDE 
                ) 
                val latitude = cursor.getDouble(latitudeIdx) 
                val longitudeIdx = cursor.getColumnIndexOrThrow( 
                   DbHelper.COLUMN_LOCATION_LONGITUDE 
                ) 
                val longitude = cursor.getDouble(longitudeIdx) 
                val location = Location("") 
                location.latitude = latitude 
                location.longitude = longitude 
                val note = Note(title, message, location) 
                note.id = id 
                result.add(note) 
              } 
             cursor.close() 
             return result 
            } 
            ... 
          } 
          ... 
       val TODO = object : Crud<Todo> { 
        ... 
        override fun select(args: Pair<String, String>): List<Todo> =
        select(listOf(args)) 

        override fun select(args: Collection<Pair<String, String>>): 
        List<Todo> { 
          val db = DbHelper(name, version).writableDatabase 
          val selection = StringBuilder() 
          val selectionArgs = mutableListOf<String>() 
          args.forEach { arg -> 
             selection.append("${arg.first} == ?") 
             selectionArgs.add(arg.second) 
          } 
          val result = mutableListOf<Todo>() 
          val cursor = db.query( 
             true, 
             DbHelper.TABLE_NOTES, 
             null, 
             selection.toString(), 
             selectionArgs.toTypedArray(), 
             null, null, null, null 
            ) 
            while (cursor.moveToNext()) { 
                val id = cursor.getLong(cursor.getColumnIndexOrThrow
                (DbHelper.ID)) 
                val titleIdx = cursor.getColumnIndexOrThrow
                (DbHelper.COLUMN_TITLE) 
                val title = cursor.getString(titleIdx) 
                val messageIdx = cursor.getColumnIndexOrThrow
                (DbHelper.COLUMN_MESSAGE) 
                val message = cursor.getString(messageIdx) 
                val latitudeIdx = cursor.getColumnIndexOrThrow( 
                    DbHelper.COLUMN_LOCATION_LATITUDE 
                ) 
                val latitude = cursor.getDouble(latitudeIdx) 
                val longitudeIdx = cursor.getColumnIndexOrThrow( 
                    DbHelper.COLUMN_LOCATION_LONGITUDE 
                ) 
                val longitude = cursor.getDouble(longitudeIdx) 
                val location = Location("") 
                val scheduledForIdx = cursor.getColumnIndexOrThrow( 
                    DbHelper.COLUMN_SCHEDULED 
                ) 
                val scheduledFor = cursor.getLong(scheduledForIdx) 
                location.latitude = latitude 
                location.longitude = longitude 
                val todo = Todo(title, message, location, scheduledFor) 
                todo.id = id 
                result.add(todo) 
               } 
              cursor.close() 
              return result 
            } 

            override fun selectAll(): List<Todo> { 
            val db = DbHelper(name, version).writableDatabase 
            val result = mutableListOf<Todo>() 
            val cursor = db.query( 
              true, 
              DbHelper.TABLE_NOTES, 
              null, null, null, null, null, null, null 
            ) 
            while (cursor.moveToNext()) { 
                val id = cursor.getLong(cursor.getColumnIndexOrThrow
                (DbHelper.ID)) 
                val titleIdx = cursor.getColumnIndexOrThrow
                (DbHelper.COLUMN_TITLE) 
                val title = cursor.getString(titleIdx) 
                val messageIdx = cursor.getColumnIndexOrThrow
                (DbHelper.COLUMN_MESSAGE) 
                val message = cursor.getString(messageIdx) 
                val latitudeIdx = cursor.getColumnIndexOrThrow( 
                    DbHelper.COLUMN_LOCATION_LATITUDE 
                ) 
                val latitude = cursor.getDouble(latitudeIdx) 
                val longitudeIdx = cursor.getColumnIndexOrThrow( 
                    DbHelper.COLUMN_LOCATION_LONGITUDE 
                ) 
                val longitude = cursor.getDouble(longitudeIdx) 
                val location = Location("") 
                val scheduledForIdx = cursor.getColumnIndexOrThrow( 
                    DbHelper.COLUMN_SCHEDULED 
                ) 
                val scheduledFor = cursor.getLong(scheduledForIdx) 
                location.latitude = latitude 
                location.longitude = longitude 
                val todo = Todo(title, message, location, scheduledFor) 
                todo.id = id 
                result.add(todo) 
              } 
              cursor.close() 
               return result 
             } 
             ... 
        } 
        ... 

每个 CRUD 操作将通过使用我们的DbHelper类获得一个数据库实例。我们不会直接公开它,而是通过我们的 CRUD 机制来利用它。每次操作后,数据库都将关闭。我们只能通过访问writableDatabase获得一个可读的数据库,或者像我们的例子一样,一个WritableDatabase实例。每个 CRUD 操作都是作为一个 SQL 事务来执行的。这意味着我们将通过在数据库实例上调用beginTransaction()来启动它。通过致电endTransaction()完成交易。如果我们不在此之前调用setTransactionSuccessful(),则不会有任何更改。正如我们已经提到的,每个 CRUD 操作都有两个版本——一个包含主要实现,第二个只是将实例传递给另一个。要在数据库中执行插入,需要注意的是,我们将在数据库实例上使用insert()方法,该方法接受我们要插入的表名和表示数据的内容值(ContentValues类)。updatedelete操作类似。我们使用update()delete()方法。在我们的例子中,对于数据删除,我们使用了包含删除 SQL 查询的compileStatement()

The code we provided here is a bit complex. We directly pointed only to database related matter. So, be patient, read the code slowly, and take your time to investigate it. We encourage you to create your own version of database management classes in your own way by utilizing the Android database classes we have already mentioned.

把东西绑在一起

我们还有一步!这是我们的数据库类和执行 CRUD 操作的实际应用。我们将扩展应用来创建注释,并将重点放在插入上。

在我们向数据库中插入任何东西之前,我们必须提供一种机制来获取当前用户的位置,因为它对于notestodos都是必需的。创建一个名为LocationProvider的新类,并将其放入location包中,如下所示:

     object LocationProvider { 
       private val tag = "Location provider" 
       private val listeners =   CopyOnWriteArrayList
       <WeakReference<LocationListener>>() 

       private val locationListener = object : LocationListener { 
       ... 
       } 

      fun subscribe(subscriber: LocationListener): Boolean { 
        val result = doSubscribe(subscriber) 
        turnOnLocationListening() 
        return result 
      } 

      fun unsubscribe(subscriber: LocationListener): Boolean { 
        val result = doUnsubscribe(subscriber) 
        if (listeners.isEmpty()) { 
            turnOffLocationListening() 
        } 
        return result 
      } 

      private fun turnOnLocationListening() { 
      ... 
      } 

      private fun turnOffLocationListening() { 
      ... 
      } 

      private fun doSubscribe(listener: LocationListener): Boolean { 
      ... 
      } 

      private fun doUnsubscribe(listener: LocationListener): Boolean { 
       ... 
      } 
    } 

我们暴露了LocationProvider物体的主体结构。让我们看看实现的其余部分:

locationListener实例代码如下:

     private val locationListener = object : LocationListener { 
        override fun onLocationChanged(location: Location) { 
            Log.i( 
                    tag, 
                    String.format( 
                            Locale.ENGLISH, 
                            "Location [ lat: %s ][ long: %s ]",
                            location.latitude, location.longitude 
                    ) 
            ) 
            val iterator = listeners.iterator() 
            while (iterator.hasNext()) { 
                val reference = iterator.next() 
                val listener = reference.get() 
                listener?.onLocationChanged(location) 
            } 
         } 

        override fun onStatusChanged(provider: String, status: Int,
        extras: Bundle) { 
            Log.d( 
                    tag, 
                    String.format(Locale.ENGLISH, "Status changed [ %s
                    ][ %d ]", provider, status) 
            ) 
            val iterator = listeners.iterator() 
            while (iterator.hasNext()) { 
                val reference = iterator.next() 
                val listener = reference.get() 
                listener?.onStatusChanged(provider, status, extras) 
            } 
        } 

        override fun onProviderEnabled(provider: String) { 
            Log.i(tag, String.format("Provider [ %s ][ ENABLED ]",
            provider)) 
            val iterator = listeners.iterator() 
            while (iterator.hasNext()) { 
                val reference = iterator.next() 
                val listener = reference.get() 
                listener?.onProviderEnabled(provider) 
            } 
        } 

        override fun onProviderDisabled(provider: String) { 
            Log.i(tag, String.format("Provider [ %s ][ ENABLED ]",
            provider)) 
            val iterator = listeners.iterator() 
            while (iterator.hasNext()) { 
                val reference = iterator.next() 
                val listener = reference.get() 
                listener?.onProviderDisabled(provider) 
            } 
          } 
         } 

LocationListener是安卓的界面,目的是在location事件上执行。我们创建了我们的具体化,将基本上通知所有订阅方关于这些事件。对我们来说最重要的是onLocationChanged():

    turnOnLocationListening(): 

    private fun turnOnLocationListening() { 
       Log.v(tag, "We are about to turn on location listening.") 
       val ctx = Journaler.ctx 
       if (ctx != null) { 
            Log.v(tag, "We are about to check location permissions.") 

            val permissionsOk = 
            ActivityCompat.checkSelfPermission(ctx,
            Manifest.permission.ACCESS_FINE_LOCATION) ==  
            PackageManager.PERMISSION_GRANTED  
            &&  
            ActivityCompat.checkSelfPermission(ctx, 
            Manifest.permission.ACCESS_COARSE_LOCATION) ==
            PackageManager.PERMISSION_GRANTED 

            if (!permissionsOk) { 
                throw IllegalStateException( 
                "Permissions required [ ACCESS_FINE_LOCATION ]
                 [ ACCESS_COARSE_LOCATION ]" 
                ) 
            } 
            Log.v(tag, "Location permissions are ok. 
            We are about to request location changes.") 
            val locationManager =
            ctx.getSystemService(Context.LOCATION_SERVICE)
            as LocationManager 

            val criteria = Criteria() 
            criteria.accuracy = Criteria.ACCURACY_FINE 
            criteria.powerRequirement = Criteria.POWER_HIGH 
            criteria.isAltitudeRequired = false 
            criteria.isBearingRequired = false 
            criteria.isSpeedRequired = false 
            criteria.isCostAllowed = true 

            locationManager.requestLocationUpdates( 
                    1000, 1F, criteria, locationListener, 
                    Looper.getMainLooper() 
            ) 
            } else { 
             Log.e(tag, "No application context available.") 
          } 
        } 

要打开位置监听,我们必须检查权限是否得到正确履行。如果是这样,那么我们正在获取安卓的LocationManager并定义Criteria进行位置更新。我们将我们的标准定义得非常精确。最后,我们通过传递以下参数来请求位置更新:

  • long minTime
  • float minDistance
  • Criteria criteria
  • LocationListener listener
  • Looper looper

如您所见,我们通过了LocationListener具体化,将通知所有订阅的第三方location事件:

     turnOffLocationListening():private fun turnOffLocationListening() 
     { 
       Log.v(tag, "We are about to turn off location listening.") 
       val ctx = Journaler.ctx 
       if (ctx != null) { 
         val locationManager =  
         ctx.getSystemService(Context.LOCATION_SERVICE)
         as LocationManager 

         locationManager.removeUpdates(locationListener) 
        } else { 
            Log.e(tag, "No application context available.") 
        } 
     } 
  • 我们通过简单地移除我们的监听器instance.doSubscribe()来停止监听位置:
      private fun doSubscribe(listener: LocationListener): Boolean { 
        val iterator = listeners.iterator() 
        while (iterator.hasNext()) { 
          val reference = iterator.next() 
          val refListener = reference.get() 
          if (refListener != null && refListener === listener) { 
                Log.v(tag, "Already subscribed: " + listener) 
                return false 
            } 
         } 
         listeners.add(WeakReference(listener)) 
         Log.v(tag, "Subscribed, subscribers count: " + listeners.size) 
         return true 
      }  
  • doUnsubscribe()方法代码如下:
      private fun doUnsubscribe(listener: LocationListener): Boolean { 
        var result = true 
        val iterator = listeners.iterator() 
        while (iterator.hasNext()) { 
            val reference = iterator.next() 
            val refListener = reference.get() 
            if (refListener != null && refListener === listener) { 
                val success = listeners.remove(reference) 
                if (!success) { 
                    Log.w(tag, "Couldn't un subscribe, subscribers
                    count: " + listeners.size) 
                } else { 
                    Log.v(tag, "Un subscribed, subscribers count: " +
                    listeners.size) 
                } 
                if (result) { 
                    result = success 
                } 
               } 
             } 
            return result 
        } 

这两种方法负责向感兴趣的第三方订阅和取消订阅位置更新。

我们需要的都有了。打开NoteActivity类,扩展如下:

     class NoteActivity : ItemActivity() { 
       private var note: Note? = null 
       override val tag = "Note activity" 
       private var location: Location? = null 
       override fun getLayout() = R.layout.activity_note 

      private val textWatcher = object : TextWatcher { 
        override fun afterTextChanged(p0: Editable?) { 
            updateNote() 
        } 

        override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2:
        Int, p3: Int) {} 
        override fun onTextChanged(p0: CharSequence?, p1: Int, p2:
        Int, p3: Int) {} 
      } 

      private val locationListener = object : LocationListener { 
        override fun onLocationChanged(p0: Location?) { 
            p0?.let { 
                LocationProvider.unsubscribe(this) 
                location = p0 
                val title = getNoteTitle() 
                val content = getNoteContent() 
                note = Note(title, content, p0) 
                val task = object : AsyncTask<Note, Void, Boolean>() { 
                    override fun doInBackground(vararg params: Note?):
                    Boolean { 
                        if (!params.isEmpty()) { 
                            val param = params[0] 
                            param?.let { 
                                return Db.NOTE.insert(param) > 0 
                            } 
                        } 
                        return false 
                    } 

                    override fun onPostExecute(result: Boolean?) { 
                        result?.let { 
                            if (result) { 
                                Log.i(tag, "Note inserted.") 
                            } else { 
                                Log.e(tag, "Note not inserted.") 
                            } 
                        } 
                     } 
                  } 
                task.execute(note) 
              } 
          } 

         override fun onStatusChanged(p0: String?, p1: Int, p2:
         Bundle?) {} 
         override fun onProviderEnabled(p0: String?) {} 
         override fun onProviderDisabled(p0: String?) {} 
        } 

        override fun onCreate(savedInstanceState: Bundle?) { 
          super.onCreate(savedInstanceState) 
          note_title.addTextChangedListener(textWatcher) 
          note_content.addTextChangedListener(textWatcher) 
        } 

       private fun updateNote() { 
         if (note == null) { 
          if (!TextUtils.isEmpty(getNoteTitle()) &&
          !TextUtils.isEmpty(getNoteContent())) { 
             LocationProvider.subscribe(locationListener) 
          } 
         } else { 
            note?.title = getNoteTitle() 
            note?.message = getNoteContent() 
            val task = object : AsyncTask<Note, Void, Boolean>() { 
                override fun doInBackground(vararg params: Note?):
            Boolean { 
              if (!params.isEmpty()) { 
                 val param = params[0] 
                 param?.let { 
                   return Db.NOTE.update(param) > 0 
                  } 
                } 
                  return false 
              } 

              override fun onPostExecute(result: Boolean?) { 
                result?.let { 
                   if (result) { 
                       Log.i(tag, "Note updated.") 
                   } else { 
                       Log.e(tag, "Note not updated.") 
                   } 
                 } 
               } 
            } 
            task.execute(note) 
          } 
       } 

       private fun getNoteContent(): String { 
         return note_content.text.toString() 
       } 

       private fun getNoteTitle(): String { 
         return note_title.text.toString() 
       } 

     } 

我们在这里做了什么?让我们从上到下解释一切!我们添加了两个字段——一个包含我们正在编辑的当前Note实例,另一个包含当前用户位置的信息。然后,我们定义了一个TextWatcher实例。TextWatcher是一个监听器,我们将把它分配给我们的EditText视图,并且,在每次更改时,将触发适当的更新方法。该方法将创建一个新的note类,如果它不存在,则将它保存到数据库中,如果它存在,则执行数据更新。

由于在没有可用的位置数据之前,我们不会插入注释,因此我们定义了我们的locationListener来将接收到的位置放入位置字段,并自行取消订阅。然后,我们将获取note标题及其主要内容的当前值,并创建一个新的note实例。由于数据库操作可能需要一些时间,我们将异步执行它们。为此,我们将使用AsyncTask类。AsyncTask类是安卓的类,旨在用于大多数异步操作。类定义输入类型、进度类型和结果类型。在我们的例子中,输入类型是Note。我们没有进度类型,但是有结果类型Boolean,即操作成功与否。

主要工作在doInBackground()具体化,结果在onPostExecute()处理。如您所见,我们正在使用最近为数据库管理定义的类在后台执行插入。

如果您继续查看,我们接下来要做的是在onCreate()方法中将textWatcher分配给EditText视图。然后,我们定义我们最重要的方法- updateNote()。它将更新现有的注释,如果不存在,则插入新的注释。再次,我们使用AsyncTask在后台执行操作。

构建并运行您的应用。尝试插入note。观察你的日志。您会注意到数据库相关日志是您的类型:

    I/Note activity: Note inserted. 
    I/Note activity: Note updated. 
    I/Note activity: Note updated. 
    I/Note activity: Note updated. 

如果你能看到这些日志,你已经成功地在安卓系统中实现了你的第一个数据库。我们鼓励您为剩余的 CRUD 操作扩展代码。确保NoteActivity支持selectdelete操作。

摘要

在这一章中,我们演示了如何在安卓系统中保存复杂的数据。数据库是每个应用的核心,所以 Journaler 也不例外。我们介绍了在 SQLite 数据库上执行的所有 CRUD 操作,并给出了它们的正确实现。在我们的下一章中,我们将演示另一种针对不太复杂的数据的持久性机制。我们将处理安卓共享偏好,我们将使用它们来为我们的应用保留简单和小的数据。