• 《Android 编程权威指南》学习笔记 : 第15章 隐式 intent


    第15章 隐式 intent

    系列目录:https://www.cnblogs.com/easy5weikai/p/16322845.html

    本节要点

    • Room 的数据库迁移
    • 格式化字符串资源
    • 隐式 intent,其组成部分:
      • 要执行的操作、数据位置、数据类型、类别(可选)
    • 隐式 intent,启动操作系统内置的联系人应用程序

    数据库迁移

    Crime 添加新字段

    代码清单:Crime

    @Entity
    data class  Crime(
        ...
        var suspect: String = ""
    )
    

    定义数据库迁移对象

    代码清单:database/CrimeDatabase.kt

    @Database(entities = [ Crime::class], version = 2, exportSchema = true)
    @TypeConverters(CrimeTypeConverters::class)
    abstract class CrimeDatabase : RoomDatabase() {
        abstract fun crimeDao(): CrimeDao
    }
    
    val migration_1_2 = object : Migration(1,2) {
        override fun migrate(database: SupportSQLiteDatabase) {
            database.execSQL(
                "Alter Table Crime Add Column suspect Text Not Null Default ''"
            )
        }
    }
    
    • version = 2:由1变成2
    • 定义一个迁移对象 Migration(1,2) :第一个参数:旧版本号,第二个参数是:新版本号
      实现接口函数:migrate(),里面是迁移的动作
    • exportSchema = true:第一个版本的时候 exportSchema = false,并删除导出的Schema 文件
      现在第二个版本 exportSchema = true,没有生成导出的Schema 文件。

    执行迁移

    代码清单:CrimeRepository.kt

    class CrimeRepository private constructor(context: Context) {
    
        private val database: CrimeDatabase = Room.databaseBuilder(
            context.applicationContext,
            CrimeDatabase::class.java,
            DATABASE_NAME
        ).addMigrations(migration_1_2)
            .build()
    

    创建了Migration后,需要把它提交给数据库。在创建CrimeDatabase实例时,把Migration添加给Room
    调用build()函数之前,首先调用addMigrations(...)创建数据库迁移。addMigrations(...)函数接受多个Migration对象参数,你可以把声明好的多个addMigrations(...)全部传给它。

    当应用启动,Room创建数据库时,它会检查设备上现有数据库的版本。
    如果检查到的版本和定义在@Database注解里的不一样,Room会找到合适的Migration以更新数据库到最新版本。
    为数据库转换提供迁移很重要。如果不提供,Room则会先删除旧版本数据库,再创建新版本数据库。这意味着数据会全部丢失,用户肯定会抱怨的。

    准备

    代码清单:res/values/strings.xml

    添加字符串资源

    <resources>
        ...
    
        <string name="crime_suspect_text">Choose Suspect</string>
        <string name="crime_report_text">Send Crime Report</string>
        <string name="crime_report">%1$s!
          The crime was discovered on %2$s. %3$s, and %4$s
        </string>
        <string name="crime_report_solved">The case is solved</string>
        <string name="crime_report_unsolved">The case is not solved</string>
        <string name="crime_report_no_suspect">There is no suspect.</string>
        <string name="crime_report_suspect">the suspect is %s.</string>
        <string name="crime_report_subject">CriminalIntent Crime Report</string>
        <string name="send_report">Send crime report via</string>
    <resources>
    

    添加两个按钮

    代码清单:res/layout/fragment_crime.xml

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        ...
        <Button
            android:id="@+id/crime_suspect"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@string/crime_suspect_text"/>
    
        <Button
            android:id="@+id/crime_report"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@string/crime_report_text"/>
    
    </LinearLayout>
    

    发送消息

    创建隐式 intent,这里使用了intent4个组成部分的3个:

    • 要执行的操作:Intent(Intent.ACTION_SEND)
    • 数据位置:intent.putExtra(Intent.EXTRA_TEXT, getCrimeReport()):在这个:Intent.EXTRA_TEXT 位置保存数据
    • 数据类型:intent.type = "text/plan"
    • 类别(可选):这里不设置

    代码清单:CrimeFragment.kt

    class CrimeFragment : Fragment() {
        private lateinit var reportButton: Button
    
        override fun onCreateView(...) {
           reportButton = view.findViewById(R.id.crime_report) as Button
       }
    
        override fun onStart() {
            super.onStart()
               reportButton.setOnClickListener {
                Intent(Intent.ACTION_SEND).apply {
                    type = "text/plan"
                    putExtra(Intent.EXTRA_TEXT, getCrimeReport())
                    putExtra(Intent.EXTRA_SUBJECT, getString(R.string.crime_report_subject))
                }.also { intent ->
                    // startActivity(intent)
                    var chooserIntent =
                        Intent.createChooser(intent, getString(R.string.send_report))
                    startActivity(chooserIntent)
                }
            }
    
           ...
           // 获取报告内容
           private fun getCrimeReport(): String {
            val solvedString = if (crime.isSolved) {
                getString(R.string.crime_report_solved)
            } else {
                getString(R.string.crime_report_unsolved)
            }
    
            val dateString = DateFormat.format(DATE_FORMAT, crime.date).toString()
            val suspect = if (crime.suspect.isBlank()) {
                getString(R.string.crime_report_no_suspect)
            } else {
                getString(R.string.crime_report_suspect, crime.suspect)
            }
    
            return getString(
                R.string.crime_report,
                crime.title, dateString, solvedString, suspect
            )
        }
    
    
       ...
    }
    

    其中,

    • 使用 chooserIntent 再包装一层 intent,避免用户选择了默认执行程序后,不再显示其它应用列表。

    • 能处理 Intent.ACTION_SEND的应用程序,根据约定好的内置的数据位置(关键字),获取数据:

      • Intent.EXTRA_SUBJECT:主题
      • Intent.EXTRA_TEXT :文本
    • getString(R.string.crime_report_subject),根据字符串资源ID,获取字符串

    真机运行效果

    微信

    如果选【微信】,会弹出微信界面:

    选择一个联系人,点击【分享】

    然后消息就发送出去了:

    邮件

    如果选【邮件】,会弹发送邮件界面:

    对方就会收到邮件

    联系人应用程序

    启动联系人应用程序

    代码清单:CrimeFragment.kt

    private const val REQUEST_CONTACT = 1
    
    class CrimeFragment : Fragment() {
        private lateinit var suspectButton: Button
    
        override fun onCreateView(...) {
           suspectButton = view.findViewById(R.id.crime_suspect) as Button
       }
    
        override fun onStart() {
            super.onStart()
               ...   
          
           suspectButton.apply {
                var pickContactIntent =
                    Intent(Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI)
    
                setOnClickListener {
                    startActivityForResult(pickContactIntent, REQUEST_CONTACT)
                }
    
            }
        ...
        private fun updateUI() {
            ...
            if (crime.suspect.isNotEmpty()) {
                suspectButton.text = crime.suspect
            }
        }
    }
    

    创建隐式 intent,从联系人应用程序里选嫌疑人(suspect),

    • 要执行的操作:Intent(Intent.ACTION_PICK)
    • 数据位置:ContactsContract.Contacts.CONTENT_URI

    启动 Activit:

    startActivityForResult(pickContactIntent, REQUEST_CONTACT)
    

    该方法已经被弃用!

    点击【CHOOSE SUSPECT】按钮后,弹出【联系人应用程序】

    从联系人应用程序返回的数据

    很多应用程序都会共享联系人,Android内置提供了一个处理联系人信息:ContentProvider类。
    该类的实例封装了联系人数据库并提供给其它应用程序。我们可以通过 ContentResolver 访问 ContentProvider。

    代码清单:CrimeFragment.kt

    class CrimeFragment : Fragment() {
        ...
        override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
            when {
                requestCode != Activity.RESULT_OK -> return
    
                requestCode == REQUEST_CONTACT && data != null -> {
                    val contactUrl: Uri = data.data ?: return
                    //定义要查询哪个字段
                    val queryFields = arrayOf(ContactsContract.Contacts.DISPLAY_NAME)
                    // 执行查询
                    val cursor = requireActivity().contentResolver
                        .query(contactUrl, queryFields, null, null, null)
                    cursor?.use {
                        if (it.count == 0) {
                            return
                        }
    
                        it.moveToFirst()
                        val suspect = it.getString(0) //获取第一列(字段)
                        crime.suspect = suspect
                        crimeDetailViewModel.saveCrime(crime)
                        suspectButton.text = suspect
                    }
                }
            }
        }
    
      ...
    }
    
    

    不幸的是:Fragment.startActivityForResult(...)方法已经被弃用了,在高版本的Android系统中,运行的CriminalIntent应用程序
    点击【CHOOSE SUSPECT】按钮后,弹出【联系人应用程序】并选择联系人后,Fragment.onActivityResult()方法没有被回调。

    registerForActivityResult

    这里我们使用registerForActivityResult() 来替换,修改上面的代码
    代码清单:CrimeFragment.kt

    class CrimeFragment : Fragment() {
        ... 
        private lateinit var suspectButton: Button
        private val pickContactIntent = Intent(Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI)
        private lateinit var pickContactActivityResultLauncher: ActivityResultLauncher<Intent>
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            ...
            pickContactActivityResultLauncher =
                registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
                    if (result.resultCode == Activity.RESULT_OK
                        && result.data != null
                        && result.data?.data != null
                    ) {
                        val contactUrl: Uri = result.data?.data!!
                        //定义要查询哪个字段
                        val queryFields = arrayOf(ContactsContract.Contacts.DISPLAY_NAME)
                        // 执行查询
                        val cursor = requireActivity().contentResolver
                            .query(contactUrl, queryFields, null, null, null)
                        cursor?.use {
                            if (it.count > 0) {
                                it.moveToFirst()
                                val suspect = it.getString(0) //获取第一列(字段)
                                crime.suspect = suspect
                                crimeDetailViewModel.saveCrime(crime)
                                suspectButton.text = suspect
                            }
                        }
                    }
                }
        }
    
        override fun onCreateView(...) {
           ...
           suspectButton = view.findViewById(R.id.crime_suspect) as Button
        }
    
        override fun onStart() {
            super.onStart()
            ...
            suspectButton.apply {
                setOnClickListener {
                    //已被弃用:startActivityForResult(pickContactIntent, REQUEST_CONTACT)
                    pickContactActivityResultLauncher.launch(pickContactIntent)
                }
            }
        }
    
        private fun updateUI() {
            ...
            if (crime.suspect.isNotEmpty()) {
                suspectButton.text = crime.suspect
            }
        }
    }
    
    
    

    其中:

    • pickContactIntent = Intent(Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI):创建隐式 intent,从联系人应用程序里选嫌疑人(suspect),

      • 要执行的操作:Intent(Intent.ACTION_PICK)
      • 数据位置:ContactsContract.Contacts.CONTENT_URI
    • registerForActivityResult 注册返回结果时的回调函数,并返回一个 Activity的启动器 pickContactActivityResultLauncher

    • pickContactActivityResultLauncher.launch(pickContactIntent) :使用Activity的启动器 pickContactActivityResultLauncher,传入 intent 启动 activity。

    重新运行应用程序,点击【CHOOSE SUSPECT】按钮后,弹出【联系人应用程序】,

    选择一个联系人:

    选择完成后,自动返回到 CrimeFragment界面,同时
    【CHOOSE SUSPECT】的文本被更新为刚才选择的联系人,如下图所示:

    关于权限

    联系人应用返回包含intent中的Uris数据给父Activit时,会添加一个 Intent.FLAG_GRANT_READ_URI_PERMISSION 标志,该标志告诉Android,CriminalIntent应用中的父Activit可以使用联系人数据一次。
    这很有用,因为你不需要访问整个联系人数据库,只要访问其中的一条联系人信息就可以了。

    检查可响应任务的 activity

    因为有些设备上根本没有联系人应用。如果操作系统找不到匹配的activity,应用就会崩溃。
    解决办法是首先通过操作系统中的PackageManager类进行自检。在onStart()函数中实现检查
    代码清单:CrimeFragment.kt

        override fun onStart() {
            super.onStart()
            ...
            suspectButton.apply {
                setOnClickListener {
                    //已被弃用:startActivityForResult(pickContractIntent, REQUEST_CONTACT)
                    pickContactActivityResultLauncher.launch(pickContactIntent)
                }
    
                // 检查可响应任务的 activity,避免程序崩溃
                val packageManager: PackageManager = requireActivity().packageManager
                val resolvedActivity: ResolveInfo? =
                    packageManager.resolveActivity(pickContactIntent,
                        PackageManager.MATCH_DEFAULT_ONLY)
                if (resolvedActivity == null) {
                    isEnabled = false
                }
            }
        }
    

    Android设备上安装了哪些组件以及包括哪些activity,PackageManager全都知道。
    调用resolveActivity(Intent, Int)函数,可以找到匹配给定Intent任务的activity。
    flag标志MATCH_DEFAULT_ONLY限定只搜索带CATEGORY_DEFAULT标志的activity。这和startActivity(Intent)函数类似。

  • 相关阅读:
    VS2010的新特性:3.新要害词 Dynamic
    VS2010的新特性:1.可选参数
    VS2010的新特性:4.简化了对 Office API 对象的访问
    VS2010的新特性:2.命实参数
    Not beside my body,but inside my heart!
    Tears...
    首乘“子弹头”列车
    What doesn't kill me makes me stronger!
    HongKong Business Trip
    胃部不适,原来好辛苦!
  • 原文地址:https://www.cnblogs.com/easy5weikai/p/16337615.html
Copyright © 2020-2023  润新知