Activity 相关

  • Activity: 负责用户界面的展示和用户交互,学习 Activity 就要学习 Fragment,虽然它不是四大组件之一,但是它在我们的开发工作中也是频频被使用到,且必须和 Activity 一块使用,常用于分模块开发,比如慕课首页的几个 tab,每个 tab 都是对应着一个 Fragment

Activity

1.Activity 生命周期

Activity

方法说明
onCreate()该方法会在 Activity 第一次创建时进行调用,在这个方法中通常会做 Activity 初始化相关的操作,例如:加载布局、绑定事件等
onStart()这个方法会在 Activity 由不可见变为可见的时候调用,但是还不能和用户进行交互。
onResume()表示 Activity 已经启动完成,进入到了前台,可以同用户进行交互了。
onPause()这个方法。可以在这里释放系统资源,动画的停止,不宜在此做耗时操作。 发生场景 1.在系统准备去启动另一个 Activity 的时候调用 2.按下返回键 3.从前台切换到后台
onStop()当 Activity 不可见的时候回调此方法。需要在这里释放全部用户使用不到的资源。可以做较重量级的工作,如对注册广播的解注册,对一些状态数据的存储。此时 Activity 还不会被销毁掉,而是保持在内存中,但随时都会被回收。通常发生在启动另一个 Activity 或切换到后台时 发生场景和 onPause()相同
onDestroy()Activity 即将被销毁。此时必须主动释放掉所有占用的资源。
onReStart()这个方法在 Activity 由停止状态变为运行状态之前调用,也就是 Activity 被重新启动了(APP 切到后台会进入 onStop(), 再切换到前台时会触发 onRestart()方法)
  • 按 home 键 : 运行中状态 转变 开始 执行 onPause() onStop()
  • 返回键: onPause() —> onResume()
  • 回到这个 Activity : 从 onStop() 转变 开始执行 onRestart() —> onStart() —>onResume()

2.Activity 组件创建

class SecondActivity : AppCompatActivity(){

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
    }

}
  • 选择复写只有一个参数的 onCreate 方法

3.Activity 组件注册

  • 四大组件需要在 AndroidManifest 文件中配置否则无法使用,类似 Activity 无法启动,

  • 在 AndroidManifest.xml 中注册

<activity android:name=".SecondActivity"
    android:exported="true">
    <intent-filter>
        <!--自定义 action时一般包名.action.全大写类名-->
        <action android:name="com.example.componentLearn.action.SECONDACTIVITY"/>
        <category android:name="android.intent.category.DEFAULT"/>
    </intent-filter>
</activity>

<!--

  android:name是对应Activity的类名称
  android:exported 是否支持其它应用调用当前组件。默认值:如果包含有intent-filter 默认值为true; 没有intent-filter默认值为false
  android:label是Activity标题栏显示的内容. 现已不推荐使用
  intent-filter 是意图过滤器. 常用语隐式跳转
    action android:name 是动作名称,是指intent要执行的动作
    category android:name 是过滤器的类别, 一般情况下,每个 中都要显示指定一个默认的类别名称,即<category android:name="android.intent.category.DEFAULT" />
 -->

  • 但是上面的代码中没有指定默认类别名称,这是一个例外情况,因为其 中的是"android.intent.action.MAIN",意思是这个 Activity 是应用程序的入口点,这种情况下可以不加默认类别名称。

4.Activity 启动与参数传递

tips 在 Android 中我们可以通过下面两种方式来启动一个新的 Activity,注意这里是怎么启动,分为显示启动和隐式启动!

4.1 显式启动:通过包名来启动

无参数跳转
textView.setOnClickListener {
    //MainActivity@this : Context
    val intent = Intent(MainActivity@this , SecondActivity::class.java)
    startActivity(intent)
}
  • 日志 ActivityA 跳转到 ActivityB
2021-11-03 22:08:59.781 4562-4562/com.example.componentlearn E/MainActivity:: onCreate
2021-11-03 22:08:59.790 4562-4562/com.example.componentlearn E/MainActivity:: onStart
2021-11-03 22:08:59.797 4562-4562/com.example.componentlearn E/MainActivity:: onResume
2021-11-03 22:09:07.295 4562-4562/com.example.componentlearn E/MainActivity:: onPause
2021-11-03 22:09:07.332 4562-4562/com.example.componentlearn E/SecondActivity:: onCreate
2021-11-03 22:09:07.337 4562-4562/com.example.componentlearn E/SecondActivity:: onStart
2021-11-03 22:09:07.338 4562-4562/com.example.componentlearn E/SecondActivity:: onResume
2021-11-03 22:09:08.009 4562-4562/com.example.componentlearn E/MainActivity:: onStop
有参数跳转
textView.setOnClickListener {
    //有参跳转
    val intent = Intent(MainActivity@this , SecondActivity::class.java)
    intent.putExtra("extra_data","extra_data")
    intent.putExtra("extra_int_data",100)
    startActivity(intent)
}
  • ActivityB 接受参数
val stringExtra = intent.getStringExtra("extra_data")
val intExtra = intent.getIntExtra("extra_int_data" , 0 )
期待从目标页获取数据
  • registerForActivityResult---->比如启动相册获取图片

  • lateinit 延迟初始化

①假设从A--->B页面,以registerForActivityResult方式启动
registerForActivityResult(
   ActivityResultContracts.StartActivityForResult()
){result ->
      val data = result.data
      val resultCode = result.resultCode
      val stringExtraResult = data?.getStringExtra("result_extra_string")
      val intExtraResult = data?.getIntExtra("result_extra_int",0)
      textView.text = "MainActivity result [${resultCode}] ${stringExtraResult} ---- ${intExtraResult}"
}.launch(Intent(this,SecondActivity::class.java))

②如果B页面返回时,调用了
textView.setOnClickListener {
      val resultIntent = Intent()
      resultIntent.putExtra("result_extra_string" , "result_extra_string")
      resultIntent.putExtra("result_extra_int" , 1000)
      setResult(Activity.RESULT_OK , resultIntent)
      finish() //关闭SecondActivity 等同于点击返回键
}

tips startActivityForResult 过时用 registerForActivityResult 代替 https://www.zbug.cc/index.php/archives/registerForActivityResult.html

  • 这个事件中两个 Activity 的生命周期
2021-11-04 11:19:12.221 4381-4381/com.example.componentlearn E/MainActivity:: onCreate
2021-11-04 11:19:12.233 4381-4381/com.example.componentlearn E/MainActivity:: onStart
2021-11-04 11:19:12.235 4381-4381/com.example.componentlearn E/MainActivity:: onResume
2021-11-04 11:19:12.267 4381-4381/com.example.componentlearn E/MainActivity:: onPause
2021-11-04 11:19:12.705 4381-4381/com.example.componentlearn E/SecondActivity:: onCreate
2021-11-04 11:19:12.709 4381-4381/com.example.componentlearn E/SecondActivity:: onStart
2021-11-04 11:19:12.709 4381-4381/com.example.componentlearn E/SecondActivity:: onResume
2021-11-04 11:19:12.883 4381-4381/com.example.componentlearn E/MainActivity:: onStop
2021-11-04 11:19:20.553 4381-4381/com.example.componentlearn E/SecondActivity:: onPause
2021-11-04 11:19:20.565 4381-4381/com.example.componentlearn E/MainActivity:: onRestart
2021-11-04 11:19:20.568 4381-4381/com.example.componentlearn E/MainActivity:: onStart
2021-11-04 11:19:20.569 4381-4381/com.example.componentlearn E/MainActivity:: onResume
2021-11-04 11:19:21.141 4381-4381/com.example.componentlearn E/SecondActivity:: onStop
2021-11-04 11:19:21.142 4381-4381/com.example.componentlearn E/SecondActivity:: onDestroy

4.2. 隐式启动

  • 通过指定 action 和 category 的信息,让系统去分析这个 Intent,并找出合适的 Activity 去启动。
<activity android:name=".SecondActivity"
    android:exported="true">
    <intent-filter>
        <!--自定义 action时一般包名.action.全大写类名-->
        <action android:name="com.example.componentLearn.action.SECONDACTIVITY"/>
        <category android:name="com.example.componentLearn.category.SecondActivity"/>
        <category android:name="android.intent.category.DEFAULT"/>
    </intent-filter>
</activity>

textView.setOnClickListener {
    val intent = Intent()
    intent.action = "com.example.componentLearn.action.SECONDACTIVITY"
    intent.addCategory("com.example.componentLearn.category.SECONDACTIVITY")

    intent.putExtra("extra_data" , "extra_data")
    intent.putExtra("extra_data_int" , 111)
    //隐式启动
    startActivity(intent)
}

5.原生常见的 Activtiy

拨打电话

val uri: Uri = Uri.parse("tel: 11111")
val intent = Intent(Intent.ACTION_DIAL,uri)
startActivity(intent)

发送短信

val uri:Uri = Uri.parse("smsto:1111")
val intent = Intent(Intent.ACTION_SENDTO , uri)
intent.putExtra("sms_body" , "发送短信")
startActivity(intent)

打开网页

val uri:Uri = Uri.parse("https://www.baidu.com")
val intent = Intent(Intent.ACTION_VIEW , uri)
startActivity(intent)

打开相册

val intent = Intent(Intent.ACTION_PICK)
intent.type = "image/*"
startActivity(intent)

多媒体播放

val uri:Uri = Uri.parse("file:///sdcard/1.mp3")
val intent = Intent(Intent.ACTION_VIEW , uri)
intent.type = "audio/mp3"
startActivity(intent)

打开摄像头拍照

val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
startActivityForResult(intent, 0);

>>> 在Activity的onActivityResult方法回调中取出照片数据
Bundle extras = intent.getExtras();
Bitmap bitmap = (Bitmap) extras.get("data");

从图库选图并剪切

// 获取并剪切图片
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("image/*");
intent.putExtra("crop", "true"); // 开启剪切
intent.putExtra("aspectX", 1); // 剪切的宽高比为1:2
intent.putExtra("aspectY", 2);
intent.putExtra("outputX", 20); // 保存图片的宽和高
intent.putExtra("outputY", 40);
intent.putExtra("output", Uri.fromFile(new File("/mnt/sdcard/temp"))); // 保存路径
intent.putExtra("outputFormat", "JPEG");// 返回格式
startActivityForResult(intent, 0);

>>>>  在Activity的onActivityResult方法中去读取保存的文件

剪切指定图片文件

Intent intent = new Intent("com.android.camera.action.CROP");
intent.setClassName("com.android.camera", "com.android.camera.CropImage");
intent.setData(Uri.fromFile(new File("/mnt/sdcard/temp")));
intent.putExtra("outputX", 1); // 剪切的宽高比为 1:2
intent.putExtra("outputY", 2);
intent.putExtra("aspectX", 20); // 保存图片的宽和高
intent.putExtra("aspectY", 40);
intent.putExtra("scale", true);
intent.putExtra("noFaceDetection", true);
intent.putExtra("output", Uri.parse("file:///mnt/sdcard/temp"));
startActivityForResult(intent, 0);

>>>> 在 Activity 的 onActivityResult 方法中去读取保存的文件

进入手机的无线网络设置页面

// 进入无线网络设置界面(其它可以举一反三)
Intent intent = new Intent(android.provider.Settings.ACTION_WIRELESS_SETTINGS);
startActivityForResult(intent, 0);

6. Activity 四种启动模式

  • standard

    • 标准模式
    • 默认值,多实例模式。每启动一次,都会创建一个新的 Activity 实例。
    • 启动的生命周期为:onCreate()->onStart()->onResume()

    Activity

  • singleTop

    • 栈顶复用模式

    • 如果任务栈顶已经存在需要启动的目标 Activity,则直接启动,并会回调 onNewIntent()方法,生命周期顺序为: onPause() ->onNewIntent()->onResume()

    • 如果任务栈上顶没有需要启动的目标 Activity,则创建新的实例,此时生命周期顺序为: onCreate()->onStart()->onResume()

    • 两种情况如下图,从图中可以看出,此模式下还是会出现多实例,只要启动的目标 Activity 不在栈顶的话

      Activity

  • singleTask

    • 栈内复用模式

    • 一个任务栈只能有一个实例

    • 有几种情况:

      • 当启动的 Activity 目标任务栈不存在时,则以此启动 Activity 为根 Activity 创建目标任务栈,并切换到前面

      • D 为 singleTask 模式

        Activity

      • 当启动的 Activity 存在时,则会直接切换到 Activity 所在的任务栈,并且任务栈中在 Activity 上面的所有其他 Activity 都出栈(调用 destroy()),此时启动的 Activity 位于任务栈顶,并且会回调 onNewIntent()方法 Activity

  • singleInstance

    • singleInstance 名称是单例模式,即 App 运行时,该 Activity 只有一个实例。既然只有一个,那么也就说明很重要、很特殊,我们需要将其“保护起来”。单例模式的“保护措施”是将其单独放到一个任务栈中

Fragment

  • 注意事项: Fragment 并不能单独使用,他需要嵌套在 Activity 中使用,尽管他拥有自己的生命周期,但是还是会受到宿主 Activity 的生命周期的影响,比如 Activity 被 destory 销毁了,他也会跟着销毁!一个 Activity 可以嵌套多个 Fragment

Activity

1.Fragment 的生命周期

Activity

  • 四个场景用于加深对 Fragment 生命周期的理解

    • ①Activity 加载 Fragment 的时候,依次调用下面的方法: onAttach -> onCreate -> onCreateView -> onActivityCreated -> onStart ->onResume

    • ② 当我们启动一个新的页面, 此时 Fragment 所在的 Activity 不可见,会执行 onPause

    • ③ 当新页面返回后,当前 Activity 和 Fragment 又可见了,会再次执行 onStart 和 onResume

    • ④ 退出了 Activity 的话,那么 Fragment 将会被完全结束, Fragment 会进入销毁状态 onPause -> onStop -> onDestoryView -> onDestory -> onDetach

    onActivityCreated 已过时 常用 onCreateView 当前 fragment 所对应的视图对象 onResume 恢复动画/视频播放

2. Fragment 的动态添加与数据传递

2.1 动态添加 Fragment

  • ① 创建 Fragment
class SecondActivity : AppCompatActivity(){

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_second)


        val fragment  = SecondFragment()
        //事务操作对象
        val ft:FragmentTransaction = supportFragmentManager.beginTransaction()
        ft.add(R.id.container , fragment)
        ft.commitAllowingStateLoss()
    }
}

2.2 Fragment 常见的操作

val fragment = StudyFragment()
val ft = supportFragmentManager.beginTransaction()

if(!fragment.isAdded()){
  ft.add(R.id.container,fragment) //把fragment添加到事务中,当且仅当该fragment未被添加过
}
ft.show(fragment) //显示出fragment的视图
ft.hide(fragment) //隐藏fragment,使得它的视图不可见
ft.remove(fragment)//移除fragment
//替换fragment,之前添加过的fragment都会被暂时移除,把当前这个fragment添加到事务中
ft.replace(R.id.container,fragment)

//提交事务,执行对fragment的add、replace、show、hide操作
ft.commitAllowingStateLoss()

2.3 给 Fragment 传递数据

  • Activity 传入数值
class SecondActivity : AppCompatActivity(){

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_second)

        val fragment  = SecondFragment()

        //给Fragment传递数据
        val bundle = Bundle()
        bundle.putInt("int_extra" , 100)
        bundle.putString("string_extra" , "string_extra")
        fragment.arguments = bundle

        //事务操作对象
        val ft:FragmentTransaction = supportFragmentManager.beginTransaction()
        ft.add(R.id.container , fragment)
        ft.commitAllowingStateLoss()
    }
}
  • Fragment 接受数据
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    val intValue = arguments?.getInt("int_extra")
    val strValue = arguments?.getString("string_extra")

    //view是onCreateView return的view
    val textview = view as TextView
    textview.text = "${intValue} ---${strValue}"
}

思考:运行时 Fragment 如何获取 Activity 中的数据、又如何将数据传递到 Activity 中呢? https://developer.android.com/guide/fragments/communicate?hl=zh-cn

设计并实现底部导航栏页面结构

Activity 页面布局

  • 此布局和如果 创建项目选择带导航栏 的样式相同
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <FrameLayout
        android:id="@+id/container"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />

    <com.google.android.material.button.MaterialButtonToggleGroup
        android:id="@+id/toggle_group"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:selectionRequired="false"
        android:background="#08000000"
        app:singleSelection="true">

        <com.google.android.material.button.MaterialButton
            android:id="@+id/tab1"
            style="@style/Widget.MaterialComponents.Button.UnelevatedButton"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:backgroundTint="@android:color/transparent"
            android:text="Tab1"
            android:textColor="#000000"
            android:textSize="12sp"
            app:icon="@drawable/ic_home_black_24dp"
            app:iconGravity="textTop"
            app:iconTint="@color/black"
            tools:ignore="HardcodedText" />

        <com.google.android.material.button.MaterialButton
            android:id="@+id/tab2"
            style="@style/Widget.MaterialComponents.Button.UnelevatedButton"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:backgroundTint="@android:color/transparent"
            android:text="Tab2"
            android:textColor="#000000"
            android:textSize="12sp"
            app:icon="@drawable/ic_notifications_black_24dp"
            app:iconGravity="textTop"
            app:iconTint="@color/black"
            tools:ignore="HardcodedText" />

        <com.google.android.material.button.MaterialButton
            android:id="@+id/tab3"
            style="@style/Widget.MaterialComponents.Button.UnelevatedButton"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:backgroundTint="@android:color/transparent"
            android:text="Tab3"
            android:textColor="#000000"
            android:textSize="12sp"
            app:icon="@drawable/ic_dashboard_black_24dp"
            app:iconGravity="textTop"
            app:iconTint="@color/black"
            tools:ignore="HardcodedText" />

    </com.google.android.material.button.MaterialButtonToggleGroup>
</LinearLayout>
  • tips
  1. 占用父布局(除了导航栏)剩余的空间
 android:layout_height="0dp"
 android:layout_weight="1"

2.水平平分

android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"

监听选中事件

  • 在 onCreate 方法中
//改变选中按钮的颜色
binding.toggleGroup.addOnButtonCheckedListener { group:MaterialButtonToggleGroup, checkedId:Int , isChecked:Boolean ->
    val childCount = group.childCount
    var selectIndex = 0
    for (index in 0 until childCount){
        val button = group.getChildAt(index) as MaterialButton
        if(button.id == checkedId){
            //选中的按钮变红
            selectIndex = index
            button.setTextColor(Color.CYAN)
            button.iconTint = ColorStateList.valueOf(Color.CYAN)
        } else {
            button.setTextColor(Color.BLACK)
            button.iconTint = ColorStateList.valueOf(Color.BLACK)
        }
    }
    switchFragment(selectIndex)
}
binding.toggleGroup.check(binding.tab1.id)

导航栏切换动态切换显示的 fragment

private var tab1Fragment:SecondFragment?=null
private var tab2Fragment:SecondFragment?=null
private var tab3Fragment:SecondFragment?=null
private var shownFragment:Fragment ?= null //当前正在显示的Fragment

//切换Fragment
private fun switchFragment(selectIndex: Int) {
    //根据选中tab的id返回fragment
     val fragment = when(selectIndex){
        0->{
            if(tab1Fragment == null){
                tab1Fragment = SecondFragment()
                val bundle = Bundle()
                bundle.putString("tab" , "tab1")
                tab1Fragment!!.arguments = bundle
            }
            tab1Fragment
        }1->{
            if(tab2Fragment == null){
                tab2Fragment = SecondFragment()
                val bundle = Bundle()
                bundle.putString("tab" , "tab2")
                tab2Fragment!!.arguments = bundle
            }
            tab2Fragment
        }2->{
            if(tab3Fragment == null){
                tab3Fragment = SecondFragment()
                val bundle = Bundle()
                bundle.putString("tab" , "tab3")
                tab3Fragment!!.arguments = bundle
            }
            tab3Fragment
        }else -> {
            throw IllegalStateException("下标不符合预期")
        }
     }?:return


    val ft = supportFragmentManager.beginTransaction()
    if(!fragment.isAdded){
        ft.add(R.id.container , fragment)
    }
    ft.show(fragment)
    //避免出现多个fragment重叠
    if(shownFragment !=null){
        ft.hide(shownFragment!!)
    }
    shownFragment = fragment
    ft.commitAllowingStateLoss()
}

本例中多个 fragment 的生命周期

  • 第一次点击各个 tab 每个 fragment onAttach----> onResume
2021-11-11 16:34:22.661 5666-5666/com.example.componentlearn E/SecondFragment: onAttach
2021-11-11 16:34:22.662 5666-5666/com.example.componentlearn E/SecondFragment: onCreate
2021-11-11 16:34:22.665 5666-5666/com.example.componentlearn E/SecondFragment: onCreateView
2021-11-11 16:34:22.675 5666-5666/com.example.componentlearn E/SecondFragment: onStart
2021-11-11 16:34:22.679 5666-5666/com.example.componentlearn E/SecondFragment: onResume
2021-11-11 16:34:24.570 5666-5666/com.example.componentlearn E/SecondFragment: onHiddenChanged : tab1-- true
2021-11-11 16:34:24.573 5666-5666/com.example.componentlearn E/SecondFragment: onAttach
2021-11-11 16:34:24.574 5666-5666/com.example.componentlearn E/SecondFragment: onCreate
2021-11-11 16:34:24.575 5666-5666/com.example.componentlearn E/SecondFragment: onCreateView
2021-11-11 16:34:24.576 5666-5666/com.example.componentlearn E/SecondFragment: onStart
2021-11-11 16:34:24.577 5666-5666/com.example.componentlearn E/SecondFragment: onResume
2021-11-11 16:34:25.737 5666-5666/com.example.componentlearn E/SecondFragment: onHiddenChanged : tab2-- true
2021-11-11 16:34:25.739 5666-5666/com.example.componentlearn E/SecondFragment: onAttach
2021-11-11 16:34:25.740 5666-5666/com.example.componentlearn E/SecondFragment: onCreate
2021-11-11 16:34:25.741 5666-5666/com.example.componentlearn E/SecondFragment: onCreateView
2021-11-11 16:34:25.741 5666-5666/com.example.componentlearn E/SecondFragment: onStart
2021-11-11 16:34:25.745 5666-5666/com.example.componentlearn E/SecondFragment: onResume
  • tab3 切换 tab2
2021-11-11 16:35:53.583 5666-5666/com.example.componentlearn E/SecondFragment: onHiddenChanged : tab3-- true
2021-11-11 16:35:53.584 5666-5666/com.example.componentlearn E/SecondFragment: onHiddenChanged : tab2-- false
  • tab2 切换 tab1
2021-11-11 16:35:55.533 5666-5666/com.example.componentlearn E/SecondFragment: onHiddenChanged : tab2-- true
2021-11-11 16:35:55.533 5666-5666/com.example.componentlearn E/SecondFragment: onHiddenChanged : tab1-- false

  • 当且仅当 activity 存在多个 fragment , 并且我们调用了 show - hide
  • fragment 的生命周期会调用
  • override fun onHiddenChanged(hidden: Boolean)
  • 不可见为 true , 可见为 false

service

  • Service 服务不需要和用户交互,且需要长期运行任务的解决方案。负责后台任务,比如播放音乐,socket 长连接 Service 启动后默认是运行在主线程中,在执行具体耗时任务过程中要手动开启子线程,应用程序进程被杀死,所有依赖该进程的 Service 服务也会停止运行。

Activity

1.Service 启动方式与生命周期

  • Service 启动方式分为两种,普通启动 startService 、绑定启动 bindService

Activity

1.1 普通启动 startService()

  • 一般用于创建一个长时间持续运行的后台任务的时候才会使用,比如说 socket,文件上传下载服务

普通启动 startService(),它的生命周期和应用程序的生命周期一样长,只要应用程序不被杀死,服务就会一直运行 ,除非我们使用 stopService()

  • ① 首次启动会创建一个 Service 实例,依次调用 onCreate()和 onStartCommand()方法,此时 Service 进入运行状态

  • ② 如果再次调用 StartService 启动 Service,将不会再创建新的 Service 对象, 系统会直接复用前面创建的 Service 对象,调用它的 onStartCommand()方法!

  • ③ 这样的 Service 与它的调用者无必然的联系,就是说当调用者结束了自己的生命周期, 但是只要不调用 stopService,那么 Service 还是会继续运行的!

  • ④ 无论启动了多少次 Service,只需调用一次 StopService 即可停掉 Service

定义 Service 服务

class TestService1 : Service(){

    private var TAG = "TestService1"

    override fun onCreate() {
        super.onCreate()
        Log.e(TAG,"onCreate")
    }

    override fun onBind(p0: Intent?): IBinder? {
        Log.e(TAG,"onBind")
        return null
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        Log.e(TAG,"onStartCommand")
        return super.onStartCommand(intent, flags, startId)
    }

    override fun onDestroy() {
        Log.e(TAG,"onDestroy")
        super.onDestroy()
    }
}
  • AndroidManifest.xml 完成 Service 注册
<application>
	<service android:name=".TestService1"/>
</application>

在 Avtivity 中 StartService 启动服务 (要在 AndroidManifest 注册)

private lateinit var binding : ActivityTestServiceBinding

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    binding = ActivityTestServiceBinding.inflate(layoutInflater)
    setContentView(binding.root)

    //启动服务
    binding.startService.setOnClickListener {
        val intent = Intent(this , TestService1::class.java)
        startService(intent)
    }
    //停止服务
    binding.stopService.setOnClickListener {
        val intent = Intent(this , TestService1::class.java)
        stopService(intent)
    }
}

日志输出与结果分析

  • 对于使用 startService 的方式而言,onStartCommand 就是我们用于做后台任务的地方,如果我们多次点击 startService 按钮,会直接调 onStartCommand,而不再回调 onCreate
//第一次点击startService
2021-11-11 22:37:16.274 6417-6417/com.example.componentlearn E/TestService1: onCreate
2021-11-11 22:37:16.275 6417-6417/com.example.componentlearn E/TestService1: onStartCommand

//第二次点击startService
2021-11-11 22:37:20.167 6417-6417/com.example.componentlearn E/TestService1: onStartCommand

//第三次点击startService
2021-11-11 22:37:21.579 6417-6417/com.example.componentlearn E/TestService1: onStartCommand

//点击stopSevice
2021-11-11 22:37:22.612 6417-6417/com.example.componentlearn E/TestService1: onDestroy

//第四次点击startService
2021-11-11 22:37:24.957 6417-6417/com.example.componentlearn E/TestService1: onCreate
2021-11-11 22:37:24.959 6417-6417/com.example.componentlearn E/TestService1: onStartCommand

1.2 绑定启动 bindService()

运行一些和 Activity 生命周期相等的后台任务,如跨进程的通信

  • ① 当首次使用 bindService()启动一个 Service 时,系统会实例化一个 Service 实例,并调用其**onCreate()和 onBind()**方法,然后调用者就可以通过返回的 IBinder 对象和 Service 进行交互了,此后如果我们再次使用 bindService 绑定 Service,系统不会创建新的 Sevice 实例,也不会再调用 onBind()方法,只会直接把 IBinder 对象返回给调用方

  • ② 如果我们解除与服务的绑定,只需调用 unbindService(),此时 onUnbind 和 onDestory 方法将会被调用

  • ③bindService 启动的 Service 服务是与调用者(Activity)相互关联的,可以理解为 “一条绳子上的蚂蚱”,要死一起死,在 bindService 后,一旦调用者(Activity)销毁,那么 Service 也立即终止

定义 Service 服务

class TestService2 : Service(){

    private var TAG = "TestService2"
    private var count = 0
    private var quit = false //标记位 线程退出

    override fun onCreate() {
        super.onCreate()
        Log.e(TAG,"onCreate")

        //线程开启
        Thread(Runnable {
            while(true){
                if(quit)
                    break
                Thread.sleep(1000)
                count++
            }
        }).start()
    }

    private val binder = MyBinder()

    //内部类 继承Binder类 ,Binder类实现IBinder
    inner class MyBinder : Binder(){
        fun getCount() : Int{
            return count
        }

    }

    override fun onBind(p0: Intent?): IBinder? {
        Log.e(TAG,"onBind")
        return binder
    }

    override fun onUnbind(intent: Intent?): Boolean {
        Log.e(TAG,"onUnbind")
        quit = true
        return super.onUnbind(intent)
    }

    override fun onDestroy() {
        Log.e(TAG,"onDestroy")
        super.onDestroy()
    }
}

AndroidManifest.xml 中注册服务

<application>
	<service android:name=".TestService2"/>
</application>

在 Activity 中 bindService 启动服务

class TestServiceActivity : Activity(){

    private lateinit var binding : ActivityTestServiceBinding
    private var connection: ServiceConnection?=null
    private var myBinder: TestService2.MyBinder? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        binding = ActivityTestServiceBinding.inflate(layoutInflater)
        setContentView(binding.root)

        connection = object:ServiceConnection{
            //Activity与Service连接成功时回调该方法
            override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
                Log.e("TestService2","--------------Service  Connected--------------")
                myBinder = service as TestService2.MyBinder
            }

            override fun onServiceDisconnected(p0: ComponentName?) {
                //Activity与Service断开连接时回调该方法
                Log.e("TestService2","--------------Service  Disconnected--------------")
            }

        }

        //绑定service
        val intent = Intent(this , TestService2::class.java)
        bindService(intent , connection!! , Context.BIND_AUTO_CREATE)

        //启动服务
        binding.startService.setOnClickListener {
/*            val intent = Intent(this , TestService1::class.java)
            startService(intent)*/

            Log.e("TestService2","*********** getCount:=${myBinder?.getCount()} ***********")
        }
        //停止服务
        binding.stopService.setOnClickListener {
            //普通启动starService()
/*            val intent = Intent(this , TestService1::class.java)
            stopService(intent)*/

            //绑定启动bindService()
            unbindService(connection!!)

        }
    }

    //否则Activity会发生内存泄露
    override fun onDestroy() {
        super.onDestroy()
        unbindService(connection!!)
    }
}

日志输出与结果分析

  • 可以在 onCreate(),onBind() 开启耗时任务如线程

2021-11-13 11:25:33.264 5247-5247/com.example.componentlearn E/TestService2: onCreate
2021-11-13 11:25:33.266 5247-5247/com.example.componentlearn E/TestService2: onBind
2021-11-13 11:25:34.069 5247-5247/com.example.componentlearn E/TestService2: --------------Service  Connected--------------
2021-11-13 11:25:34.976 5247-5247/com.example.componentlearn E/TestService2: *********** getCount:=1 ***********
2021-11-13 11:25:36.415 5247-5247/com.example.componentlearn E/TestService2: *********** getCount:=3 ***********
2021-11-13 11:25:37.134 5247-5247/com.example.componentlearn E/TestService2: *********** getCount:=3 ***********
2021-11-13 11:25:38.562 5247-5247/com.example.componentlearn E/TestService2: *********** getCount:=5 ***********
2021-11-13 11:25:43.267 5247-5247/com.example.componentlearn E/TestService2: onUnbind
2021-11-13 11:25:43.267 5247-5247/com.example.componentlearn E/TestService2: onDestroy
  • 使用 BindService 绑定 Service,依次调用 onCreate(),onBind()方法, 我们可以在 onBind()方法中返回自定义的 IBinder 对象;再接着调用的是 ServiceConnection 的 onServiceConnected()方法该方法中可以获得 IBinder 对象,从而进行相关操作;当 Service 解除绑定后会自动调用 onUnbind 和 onDestroyed 方法,当然绑定多客户端情况需要解除所有 的绑定才会调用 onDestoryed 方法进行销毁哦

2.Android 8.0 及以上不允许后台启动 Service 服务

Android 8.0 还对特定函数做出了以下变更:

  • 1.如果针对 Android 8.0 的应用尝试在不允许其创建后台服务的情况下使用 startService() 函数,则该函数将引发一个 IllegalStateException

  • 2.新的 Context.startForegroundService() 函数将启动一个前台服务。现在,即使应用在后台运行,系统也允许其调用 Context.startForegroundService()。不过,应用必须在创建服务后的五秒内调用该服务的 startForeground() 函数

  • 软件不可见后台超过 60s 不可启动 service

2021-11-14 14:24:34.213 3846-3846/com.example.componentlearn E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.example.componentlearn, PID: 3846
    java.lang.IllegalStateException: Not allowed to start service Intent { cmp=com.example.componentlearn/.TestService2 }: app is in background uid UidRecord{89692e9 u0a121 LAST bg:+1m7s709ms idle change:cached procs:1 seq(0,0,0)}
        at android.app.ContextImpl.startServiceCommon(ContextImpl.java:1715)

AndroidManifest.xml 声明权限

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

服务启动兼容写法

  • TestServiceActivity 的 onCreate()
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
    //安卓版本高于8.0   26或者Build.VERSION_CODES.O
    startForegroundService(intent)
}else{
    startService(intent)
}
  • TestService2 中的 onCreate
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    val notification = Notification.Builder(applicationContext, "channel_id").build()
    startForeground(1,notification)
}

BroadcastReciver

  • BroadcastReceiver 广播接收者: 负责页面间通信(如两个 acticity 之间),系统和 APP 通信,APP 和 APP 通信,比如监听网络连接状态变化,就是通过 BroadcastReceiver 广播接收者来实现的

  • Android 中, 系统自己在很多时候都会发送广播,比如电量变化,wifi 连接变化,插入耳机,输入法改变等,系统都会发送广播,这个叫系统广播。此时系统就是广播发送者

  • APP 想要收到这些广播,这个时候我们只需要注册一个 BroadcastReceiver,当 wifi 连接发生变化,我们注册的广播就会收到通知~。此时我们的 APP 就是广播接收者

  • 我们也可以自己发广播,比如:登录成功后发出广播,监听这个广播的接收者就可以做些刷新页面的动作。此时我们的 APP 既是广播发送者,也是广播接收者

1.两种广播类型

  • 标准广播:发出广播后,该广播事件的接收者,几乎会在同一时刻收到通知,都可以响应或不响应该事件
  • 有序广播:发出广播后,同一时刻,只有一个广播接收者能收到、一个接收者处理完后之后,可以选择继续向下传递给其它接收者,也可以拦截掉广播。[不常用、不推荐使用了 5.0 以下]

2.监听系统网络连接变化

2.1 定义一个广播接收者

class TestBroadcastReceiver : BroadcastReceiver(){
    override fun onReceive(context: Context?, intent: Intent?) {
        //intent事件类型
        val action = intent?.action?:return //如果为空则不处理
        if(action == ConnectivityManager.CONNECTIVITY_ACTION){
            val connectivityManager = context?.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
            val info = connectivityManager.activeNetworkInfo //获取网络连接类型
            if(info!=null && info.isAvailable){
                //有网络连接的
                val typeName =info.typeName
                Toast.makeText(context!! , "当前网络连接类型${typeName}" ,  Toast.LENGTH_LONG).show()
            }else{
                Toast.makeText(context!! , "当前无网络连接" ,  Toast.LENGTH_LONG).show()
            }
        }
    }
}

2.2 运行时动态注册广播接收事件

class TestBroadcastReceiverActivity : AppCompatActivity(){

    private lateinit var receiver: TestBroadcastReceiver

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        receiver = TestBroadcastReceiver()

        val intentFilter = IntentFilter()
        intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION)

        registerReceiver(receiver , intentFilter)
    }

    override fun onDestroy() {
        super.onDestroy()
        // 必须要在onDestroy时反注册,否则会内存泄漏
        unregisterReceiver(receiver)
    }
}

不要忘记在 AndroidManifest.xml 中注册 TestBroadcastReceiverActivity 不要在收到广播后进行任何耗时操作,因为在广播中是不允许开辟线程的(默认主线程), 当 onReceiver( )方法运行较长时间(超过 10 秒)还没有结束的话,那么程序会报错(ANR), 广播更多的时候扮演的是一个打开其他组件的角色,比如启动 Service,Notification 提示, Activity 等!

2.3 静态注册广播

<receiver android:name=".components.TestBroadcastReceiver">
     <intent-filter >
          <action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>
     </intent-filter>
</receiver>

从 android 8.0(API26)开始,对清单文件中静态注册广播接收者增加了限制,建议大家不要在清单文件中静态注册广播接收者,可以静态注册的是 自定义的广播事件或者一些系统级别的广播如:是否开机、电池电量

解决静态注册广播接收者收不到事件的问题

  • 虽然从 Android8.0 开始,系统明确限制了静态广播注册去监听系统行为,但是应用自己定义的的广播事件还是可以使用静态注册的

AndroidManifest.xml 中注册

<!--静态注册广播-->
<receiver android:name=".TestBroadcastReceiver"
    android:exported="false">
    <intent-filter>
        <action android:name="com.example.componentLearn.TEST_BROADCAST_RECEVIER" />
    </intent-filter>
</receiver>

  • TestBroadcastReceiverActivity 的 onCreate
val intent = Intent()
intent.action = "com.example.componentLearn.TEST_BROADCAST_RECEIVER"
// 下面这一行在Android 7.0及以下版本不是必须的,但是Android 8.0或者更高版本,发送广播的条件更加严苛,必须添加这一行内容。
// 创建的ComponentName实例化对象有两个参数,第1个参数是指接收广播类的包名,第2个参数是指接收 广播类 的完整类名。
intent.component = ComponentName(packageName,"com.example.componentlearn.TestBroadcastReceiver")
sendBroadcast(intent)

3.发送自定义事件广播

3.1 全局发送广播

  • 全局发送广播,如果别人家 App 也注册了该事件监听,也能收到(信息泄露),比较不合理。

    sendBroadcast(new Intent("com.example.firstapp.component.TEST_BROADCAST_RECEVIER"));
    
    

3.2 应用内发送广播

  • 1.App 应用内广播可理解为一种局部广播,广播的发送者和接收者都同属于一个 App
  • 2.相比于全局广播(普通广播),App 应用内广播优势体现在:安全性高 & 效率高
// 使用LocalBroadcastManager来注册应用内广播
LocalBroadcastManager.getInstance(this).registerReceiver(myReceiver, itFilter)

// 使用LocalBroadcastManager来发送应用内广播
LocalBroadcastManager.getInstance(this).sendBroadcast(intent)

4.系统广播

  • Android 中内置了多个系统广播:只要涉及到手机的基本操作(如开机、网络状态变化、拍照等等),都会发出相应的广播
  • 每个广播都有特定的 Intent - Filter(包括具体的 action),Android 系统广播 action 如下
操作系统action
开机, 系统启动完成后(仅广播一次)android.intent.action.BOOT_COMPLETED
网络状态变化android.net.conn.CONNECTIVITY_CHANGE
关闭或打开飞行模式Intent.ACTION_AIRPLANE_MODE_CHANGED
充电时或电量发生变化Intent.ACTION_BATTERY_CHANGED
拍照Intent.ACTION_CAMERA_BUTTON
电池电量充足(即从电量低变化到饱满时会发出广播intent.action.BATTERY_OKAY
电池电量低(即从电量充足变化到低时会发出广播intent.action.BATTERY_LOW
屏幕锁屏Intent.ACTION_CLOSE_SYSTEM_DIALOGS
日期改变Intent.ACTION_DATE_CHANGED
时间改变Intent.ACTION_TIME_CHANGED
时区改变Intent.ACTION_TIMEZONE_CHANGED
电话状态改变TelephonyManager.ACTION_PHONE_STATE_CHANGED
插入耳机时Intent.ACTION_HEADSET_PLUG
成功安装 APKIntent.ACTION_PACKAGE_ADDED
卸载 APKIntent.ACTION_PACKAGE_REMOVED
语言改变Intent.ACTION_LOCALE_CHANGED

ContentProvider

  • ContentProvider 内容提供者: 负责数据存取,常用于 APP 进数据共享,跨进程数据存取等…比如读取相册,读取联系人,都是 ContentProvider 来实现的

**1.**我们想在自己的应用中访问别的应用,或者说一些 ContentProvider 暴露给我们的一些数据, 比如手机联系>人,短信、相册等!我们想对这些数据进行读取或者修改,这就需要用到 ContentProvider 了! **2.**我们自己的应用,想把自己的一些数据暴露出来,给其他的应用进行读取或操作,我们也可以用到 ContentProvider,另外我们可以选择要暴露的数据,就避免了我们隐私数据的的泄露!

ContentProvider

1.权限申请

从 android6.0 开始,凡是涉及用户隐私的权限(读写短信,读写联系人,拍摄,录音等等),都需要运行时申请,弹窗提醒用户是否授权。用户不授权则无法继续操作

  • 首先在 AndroidManifest.xml 中声明读取通信录的权限

    <uses-permission android:name="android.permission.READ_CONTACTS" />
    
  • 运行时动态申请权限,请求用户授权

class TestContentProviderActivity : AppCompatActivity(){

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        //判断读取通讯录的允许
        if(ActivityCompat.checkSelfPermission(this , android.Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED){
            //如果未授权
            ActivityCompat.requestPermissions(
                this ,
                arrayOf(android.Manifest.permission.READ_CONTACTS) ,
                100)
        }else{
            getContacts() //读取联系人
        }
    }

    //读取联系人
    private fun getContacts() {
        Toast.makeText(this, "getContacts", Toast.LENGTH_SHORT).show()
    }

    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)

        if(requestCode==100 && permissions[0]==android.Manifest.permission.READ_CONTACTS){
            if(grantResults[0] == PackageManager.PERMISSION_GRANTED){ //表示授予权限
                getContacts()
            }else{
                Toast.makeText(this,"读取通讯录的权限被拒绝,程序将无法继续工作",Toast.LENGTH_LONG).show()
            }
        }
    }
}

2.读取通讯录联系人

表名说明
content://com.android.contacts/data/phones读取联系人的表的名字
字段说明
display_name联系人名
data1电话号码
//读取联系人
@SuppressLint("Range")
private fun getContacts() {
    //查询对象
    val resolver = contentResolver
    //格式化一个uri
    val uri = Uri.parse("content://com.android.contacts/data/phones")
    //得到一个游标
    val cursor = resolver.query(uri, null, null, null, null)?:return

    while (cursor.moveToNext()) {
        //获取联系人名字和号码
        val displayName = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME))
        val phoneNumber = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER))
        Log.e("ContentProvider", "姓名:$displayName")
        Log.e("ContentProvider", "号码:$phoneNumber")
        Log.e("ContentProvider", "======================")
    }
    cursor.close() //游标关闭!!
}

3.通信录插入联系人

表名说明
content://com.android.contacts/data/data插入联系人的表的名字
content://com.android.contacts/data/raw_contacts插入联系人的原始表的名字
//插入联系人
private fun insertContact() {
    /*
     * 首先向RawContacts.CONTENT_URI执行一个空值插入,目的是获得系统返回的rawContactId
     * 这时后面插入data表的数据,才能使插入的联系人在通讯录里面可见
     */
    val resolver = contentResolver
    val values = ContentValues()
    val rawContactUri = contentResolver!!.insert(ContactsContract.RawContacts.CONTENT_URI, values)!!
    val rawContactId = ContentUris.parseId(rawContactUri)

    //往data表里写入姓名数据
    values.clear() //先把可能存在的数据清空
    values.put(ContactsContract.Data.RAW_CONTACT_ID , rawContactId)
    values.put(ContactsContract.Data.MIMETYPE , CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE) //内容类型
    values.put(CommonDataKinds.StructuredName.GIVEN_NAME , "some_name")
    resolver.insert(ContactsContract.Data.CONTENT_URI , values)

    //往data表里写入电话数据
    values.clear() //先把可能存在的数据清空
    values.put(ContactsContract.Data.RAW_CONTACT_ID , rawContactId)
    values.put(ContactsContract.Data.MIMETYPE , CommonDataKinds.Phone.CONTENT_ITEM_TYPE) //内容类型
    values.put(CommonDataKinds.Phone.NUMBER , "1231231234")
    values.put(CommonDataKinds.Phone.TYPE , CommonDataKinds.Phone.TYPE_MOBILE) //手机号
    resolver.insert(ContactsContract.Data.CONTENT_URI , values)

    //往data表里写入Email的数据
    values.clear() //先把可能存在的数据清空
    values.put(ContactsContract.Data.RAW_CONTACT_ID , rawContactId)
    values.put(ContactsContract.Data.MIMETYPE , CommonDataKinds.Email.CONTENT_ITEM_TYPE) //内容类型
    values.put(CommonDataKinds.Email.DATA , "some_name@xx.com")
    values.put(CommonDataKinds.Email.TYPE , CommonDataKinds.Email.TYPE_HOME) //家庭邮箱
    resolver.insert(ContactsContract.Data.CONTENT_URI , values)
}

4.更新联系人信息

  • 根据手机号获取联系人在通讯录的 contact_id
@SuppressLint("Range")
fun getContactIdByPhone(phone : Long) : String?{
    val uri = Uri.parse("content://com.android.contacts/data/phones/filter/$phone")
    val resolver = contentResolver
    val cursor = resolver.query(uri, arrayOf(ContactsContract.Data.CONTACT_ID), null, null, null)?:return null

    if (cursor.moveToNext()){
        val contractId = cursor.getString(cursor.getColumnIndex(ContactsContract.Data.CONTACT_ID))
        return contractId
    }

    return null
}

  • 更新联系人的姓名
//更新联系人
private fun updateContact() {
    val contractId = getContactIdByPhone(234524)
    if(contractId == null){
        Toast.makeText(this , "联系人不存在,无法更新" , Toast.LENGTH_LONG).show()
        return
    }

    val values = ContentValues()
    values.put(ContactsContract.Data.MIMETYPE , CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
    values.put(CommonDataKinds.StructuredName.GIVEN_NAME, "new_name")
    contentResolver.update(
        ContactsContract.Data.CONTENT_URI,
        values,
        "${ContactsContract.Data.CONTACT_ID}=?",
        arrayOf(contractId)
    )

}

5.删除联系人

  • 根据姓名删除联系人
val ret = contentResolver.delete(
    ContactsContract.RawContacts.CONTENT_URI,
    CommonDataKinds.Phone.DISPLAY_NAME + "=?",
    arrayOf("some_name")
)
if(ret>0){
    Toast.makeText(this,"删除成功", Toast.LENGTH_SHORT).show()
}else{
    Toast.makeText(this, "删除失败", Toast.LENGTH_SHORT).show()
}
  • 根据手机号删除联系人
val contact_id = getContactIdByPhone(1111)

contentResolver.delete(RawContacts.CONTENT_URI,CommonDataKinds.Phone.CONTACT_ID+"=?", arrayOf(contact_id));

6.读取收件箱所有短信

字段说明
address收件人地址,即手机号,如+8613811810000
body短信内容
date日期,long 型,如 1346988516
type类型,1 是接收到的,2 是已发出
read是否阅读,0 未读,1 已读
protocol协议,0SMS_RPOTO 短信,1MMS_PROTO 彩信
person发件人,如果发件人在通讯录中则为具体姓名,陌生人为 null
  • 需要动态获取权限
<uses-permission android:name="android.permission.READ_SMS"/>
private fun getMessage() {
    val uri = Uri.parse("content://sms/")
    val resolver = contentResolver
    //查询短信的  发件人地址  日期     短信类型  短信具体内容
    val cursor = resolver.query(uri, arrayOf("address", "address", "type", "body"), null, null, null)?:return
    while (cursor.moveToNext()){
        val address = cursor.getString(0)
        val data = cursor.getString(1)
        val type = cursor.getString(2)
        val body = cursor.getString(3)

        Log.e("ContentProvider","收件人 $address")
        Log.e("ContentProvider","类型时间 [$type]:$data")
        Log.e("ContentProvider","短信内容 $body")
        Log.e("ContentProvider","-----------------------------")
    }

    cursor.close() //关闭游标!!
}

短信相关的其它操作 uri content://sms/ 所有短信 content://sms/inbox 收件箱 content://sms/sent 已发送 content://sms/draft 草稿 content://sms/outbox 发件箱 content://sms/failed 发送失败 content://sms/queued 待发送列表

参考资料