第八章 ContentProvider

235 阅读5分钟

第八章 ContentProvider

  1. 运行时权限

    1. Android将常用的权限大致分为了两类:普通权限和危险权限

      • 普通权限是指那些不会直接威胁到用户安全和隐私的权限,系统会自动帮我们授权,比如监听开机广播的权限
      • 危险权限是指可能会触及用户隐私或者对设备安全性造成影响的权限
    2. 危险权限(11组30个)

      权限组名权限名
      CALENDARandroid.permission.READ_CALENDAR
      android.permission.WRITE_CALENDAR
      CALL_LOGandroid.permission.READ_CALL_LOG
      android.permission.WRITE_CALL_LOG
      CAMERAandroid.permission.CAMERA
      CONTACTSandroid.permission.READ_CONTACTS
      android.permission.WRITE_CONTACTS
      android.permission.GET_ACCOUNTS
      LOCATIONandroid.permission.ACCESS_FINE_LOCATION
      android.permission.ACCESS_COARSE_LOCATION
      android.permission.ACCESS_BACKGROUND_LOCATION
      MICROPHONEandroid.permission.RECORD_AUDIO
      PHONEandroid.permission.READ_PHONE_STATE
      android.permission.READ_PHONE_NUMBERS
      android.permission.CALL_PHONE
      android.permission.ANSWER_PHONE_CALLS
      android.permission.ADD_VOICEMAIL
      android.permission.USE_SIP
      android.permission.ACCEPT_HANDOVER
      SENSORSandroid.permission.BODY_SENSORS
      ACTIVITY_RECOGNITIONandroid.permission.ACTIVITY_RECOGNITION
      SMSandroid.permission.SEND_SMS
      android.permission.RECEIVE_SMS
      android.permission.READ_SMS
      android.permission.RECEIVE_MMS
      STORAGEandroid.permission.WRITE_EXTERNAL_STORAGE
      android.permission.READ_EXTERNAL_STORAGE
      android.permission.ACCESS_MEDIA_LOCATION
    3. 运行时申请权限

      1. XML文件中添加权限声明

        <uses-permission android:name="android.permission.CALL_PHONE" />
        
      2. 检查并获取权限

        if (ContextCompat.checkSelfPermission(
                this,
                Manifest.permission.CALL_PHONE
            ) != PackageManager.PERMISSION_GRANTED
        ) {
          	// 第二个参数为权限的数组,可请求多个权限
            ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CALL_PHONE), 1)
        } 
        
      3. 申请权限的回调

        // 第二个参数为权限的数组,第三个为对应权限数组的结果数组
        override fun onRequestPermissionsResult(
            requestCode: Int,
            permissions: Array<out String>,
            grantResults: IntArray
        ) {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults)
            when (requestCode) {
                1 -> {
                    if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                        startCallIntent()
                    } else {
                        Toast.makeText(this, "you've denied", Toast.LENGTH_SHORT).show()
                    }
                }
            }
        }
        
  2. ContentResolver

    1. Uri介绍

      authoritypath组成

      • authority主要是对不同的应用程序做区分
      • path对程序中不同的表做区分
    2. 示例读取联系人

      contentResolver.query(
          Uri.withAppendedPath(
              ContactsContract.CommonDataKinds.Phone.CONTENT_URI, "1"),
              null, null, null, null
          )?.apply {
          while (moveToNext()) {
              contactList.add(
                  "${getString(getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME))} ${
                      getString(
                          getColumnIndex(
                              ContactsContract.CommonDataKinds.Phone.NUMBER
                          )
                      )
                  }"
              )
          }
          adapter.notifyDataSetChanged()
          close()
      }
      
  3. ContentProvider

    1. Uri

      1. 以路径结尾表示期望访问表中所有的数据,以id结尾表示期望访问该表中拥有相应id的数据。

        content://com.youngly.firstlineofcode/table 表中所有数据

        content://com.youngly.firstlineofcode/table/1 表中id为1的数据

      2. 我们可以使用通配符分别匹配着两种格式的内容URI,规则如下

        *表示匹配任意长度的任意字符

        #表示匹配任意长度的数字

    2. getType()方法

      主要获取Uri对象所对应的MIME类型。一个内容URI对应的MIME字符串由3部分组成,规定如下

      • 必须以vnd开头
      • 如果内容以路径结尾,则后接android.cursor.dir/;如果内容URI以id结尾,则后接android.cursor.item/
      • 最后接上vnd..

      content://com.youngly.firstlineofcode/table 的MIME

      vnd.android.cursor.dir/vnd.com.youngly.firstlineofcode.table

      content://com.youngly.firstlineofcode/table/1的MIME

      vnd.android.cursor.item/vnd.com.youngly.firstlineofcode.table

      MIME类型主要是Activity的Intent-filter的data域

      MIME类型在Activity中是用来指定,当前的Activity所支持打开的文件类型

      MIME类型其实就是一个字符串,中间有一个“/”来隔开,“/”前面的部分是系统识别的部分,就相当于我们定义一个变量时的变量数据类型,通过这个“数据类型”,系统能够知道我们所要表示的是个什么东西。至于“/”后面的部分就是我们自已来随便定义的“变量名”了。

      重写getType()的作用:

      <activity android:name=".chapter8.ContentResolverActivity" >
          <intent-filter>
              <action android:name="com.youngly.providertypetest"/>
              <category android:name="android.intent.category.DEFAULT"/>
            	// 该activity的data中mimeType为getType中的
              <data android:mimeType="vnd.android.cursor.item/vnd.com.youngly.firstlineofcode.book"/>
          </intent-filter>
      </activity>
      
      // 可以通过mimeType进行跳转
      val intent = Intent(packageName)
      intent.action = "com.youngly.providertypetest"
      // 通过getType就能匹配上data中的mimeType
      intent.data = Uri.parse("content://com.youngly.firstlineofcode/book/1")
      startActivity(intent)
      

      getType在什么时候用?

      1. 隐式调用activity传入data数据. 且这个data数据, 是某个ContentProvider的uri参数
      2. 为了防止data是activity无法处理, 所以activity才需要设置mime进行data校验
      3. 为了acvity在校验自定义ContentProvider时能有结果, 所以才要实现ContentProvider的getType
      package com.youngly.firstlineofcode.chapter8
      
      import android.content.ContentProvider
      import android.content.ContentValues
      import android.content.UriMatcher
      import android.database.Cursor
      import android.net.Uri
      import com.youngly.firstlineofcode.chapter7.database.MyDatabaseHelper
      
      class BookStoreContentProvider : ContentProvider() {
      
          private val bookDir = 0
          private val bookItem = 1
          private val categoryDir = 2
          private val categoryItem = 3
          private val authority = "com.youngly.firstlineofcode"
          private lateinit var dbHelper: MyDatabaseHelper
      
          private val uriMatcher by lazy {
              val matcher = UriMatcher(UriMatcher.NO_MATCH)
              matcher.addURI(authority, "book", bookDir)
              // 通配符,任意一行数据
              matcher.addURI(authority, "book/#", bookItem)
              matcher.addURI(authority, "category", categoryDir)
              matcher.addURI(authority, "category/#", categoryItem)
              matcher
          }
      
          override fun onCreate() = context?.let {
              // 初始化操作,完成对数据库创建和升级
              dbHelper = MyDatabaseHelper(it, "BookStore.db", 2)
              true
          } ?: false
      
          override fun query(
              uri: Uri, projection: Array<String>?, selection: String?, selectionArgs: Array<String>?,
              sortOrder: String?
          ): Cursor? =
              dbHelper.let {
                  val db = it.readableDatabase
                  var cursor = when (uriMatcher.match(uri)) {
                      bookDir -> db.query(
                          "Book", projection, selection, selectionArgs, null, null, sortOrder
                      )
                      bookItem -> {
                          val bookId = uri.pathSegments[1]
                          db.query("Book", projection, "id = ?", arrayOf(bookId), null, null, sortOrder)
                      }
                      categoryDir -> db.query(
                          "Category", projection, selection, selectionArgs, null, null, sortOrder
                      )
                      categoryItem -> {
                          val categoryId = uri.pathSegments[1]
                          db.query(
                              "Category",
                              projection,
                              "id = ?",
                              arrayOf(categoryId),
                              null,
                              null,
                              sortOrder
                          )
                      }
                      else -> null
                  }
                  cursor
              }
      
          override fun insert(uri: Uri, values: ContentValues?) = dbHelper.let {
              val db = it.writableDatabase
              val uriReturn = when (uriMatcher.match(uri)) {
                  bookDir, bookItem -> {
                      val insertId = db.insert("Book", null, values)
                      Uri.parse("content://$authority/book/$insertId")
                  }
                  categoryDir, categoryItem -> {
                      val insertId = db.insert("Category", null, values)
                      Uri.parse("content://$authority/category/$insertId")
                  }
                  else -> null
              }
              uriReturn
          }
      
          override fun update(
              uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<String>?
          ) = dbHelper.let {
              val db = it.writableDatabase
              val updateId = when (uriMatcher.match(uri)) {
                  bookDir -> db.update("Book", values, selection, selectionArgs)
                  bookItem -> {
                      val bookId = uri.pathSegments[1]
                      db.update("Book", values, "id = ?", arrayOf(bookId))
                  }
                  categoryDir -> db.update("Category", values, selection, selectionArgs)
                  categoryItem -> {
                      val categoryId = uri.pathSegments[1]
                      db.update("Category", values, "id = ?", arrayOf(categoryId))
                  }
                  else -> 0
              }
              updateId
          }
      
          override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?) =
              dbHelper.let {
                  val db = it.writableDatabase
                  val deleteId = when (uriMatcher.match(uri)) {
                      bookDir -> db.delete("Book", selection, selectionArgs)
                      bookItem -> {
                          val bookId = uri.pathSegments[1]
                          db.delete("Book", "id = ?", arrayOf(bookId))
                      }
                      categoryDir -> db.delete("Category", selection, selectionArgs)
                      categoryItem -> {
                          val categoryId = uri.pathSegments[1]
                          db.delete("Category", "id = ?", arrayOf(categoryId))
                      }
                      else -> 0
                  }
                  deleteId
              }
      
          override fun getType(uri: Uri): String? = when (uriMatcher.match(uri)) {
              bookDir -> "vnd.android.cursor.dir/vnd.$authority.book"
              bookItem -> "vnd.android.cursor.item/vnd.$authority.book"
              categoryDir -> "vnd.android.cursor.dir/vnd.$authority.category"
              categoryItem -> "vnd.android.cursor.item/vnd.$authority.category"
              else -> null
          }
      }
      
  4. Kotlin课堂

    1. 泛型

      1. 泛型类

        class MyClass<T> {
            fun method(params: T): T {
                return params
            }
        }
        

        调用

        val myClass = MyClass<Int>()
        val method = myClass.method(2)
        
      2. 泛型方法

        class MyClass {
            fun <T> method(params: T): T {
                return params
            }
        }
        

        调用

        val myClass = MyClass()
        val method = myClass.method<Int>(2)
        
      3. 类型限制

        泛型上界设置为Number类型

        fun <T : Number> method(params: T): T {
            return params
        }
        
    2. 类委托和委托属性

      1. 类委托

        委托是一种设计模式,它的基本理念是:操作对象自己不去处理某段逻辑,而是把工作委托给另外一个辅助对象处理。

        class MySet<T>(val helper: HashSet<T>) : Set<T> {
            override val size: Int
                get() = helper.size
        
            override fun contains(element: T) = helper.contains(element)
        
            override fun containsAll(elements: Collection<T>) = helper.containsAll(elements)
        
            override fun isEmpty() = helper.isEmpty()
        
            override fun iterator() = helper.iterator()
        }
        

        helper相当于辅助对象。这其实就是一种委托模式。

        这种写法弊端是借口待实现的方法过多,写起来比较繁琐。

        class MySet<T>(val helper: HashSet<T>) : Set<T> by helper {
            
        }
        

        Kotlin中委托使用的关键字是by,我们只需要在接口声明的后面使用by关键字,再接上受委托的辅助对象,就可以免去之前所写的一大堆模版代码。

        如果我们需要对某个方法进行重新实现,只需要单独重写那一个方法就可以了。

        class MySet<T>(val helper: HashSet<T>) : Set<T> by helper {
        
            override fun isEmpty(): Boolean {
                TODO("Not yet implemented")
            }
        }
        
      2. 属性委托

        属性委托的核心是将一个属性的具体实现委托给另一个类取完成

        Delegate类定义:

        class Delegate {
            var propValue: Any? = null
        
          	// 第一个参数表明该Delegate类的委托功能在什么类中使用,第二个参数是Kotlin中的一个属性操作类,可用户获取各种属性相关的值,<*>这种泛型写法表示你不知道或者不关心泛型的具体类型,类似Java中的<?>
            operator fun getValue(thisRef: Any?, property: KProperty<*>): Any? {
                return propValue
            }
        		// 最后一个参数表示要赋值给委托属性的值
            operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Any?) {
                propValue = value
            }
        }
        

        实现自己的lazy

        先定义一个kt文件

        class Lazy<T>(val block: () -> T) {
            var value: Any? = null
            operator fun getValue(any: Any?, prop: KProperty<*>): T {
                if (value == null) {
                    value = block()
                }
                return value as T
            }
        }
        

        由于懒加载技术不会对属性进行赋值,所以不需要提供setValue()方法

        fun <T> later(block: () -> T) = Lazy(block)