Kotlin第七章: Android四大组件

1. 四大组件

四大组件是每一个Android人必须要会,要掌握的知识点,因为他们是我们在日常开发工作中打交道最频繁的组件,而且他们四个在不同的领域扮演着极其重要的角色。

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

**Service服务:**不需要和用户交互,负责后台任务,比如播放音乐,socket长连接

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

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

Android组件
Activity
Fragment
Service服务
BroadcastReceiver
ContentProvider

2. Activity

1. 简介

Activity是Android的四大组件之一,Activity是一种能够显示用户界面的组件,用户通过和Activity交互完成相关操作。

一个应用中可以包含0个或多个 Activity,但不包含任何 Activity 的应用程序是无法被用户看见的。

  1. Activity用于显示用户界面,用户通过Activity交互完成相关操作

  2. 一个App允许有多个Activity
    01_activity_info.png

2. Activity生命周期

02_activity生命周期

Activity 类中定义了7个回调方法,覆盖了 Activity 生命周期的每一个环节,下面就来介绍一下这7个方法。

  1. onCreate()

该方法会在 Activity 第一次创建时进行调用,在这个方法中通常会做 Activity 初始化相关的操作,例如:加载布局、绑定事件等。

  1. onStart()

这个方法会在 Activity 由不可见变为可见的时候调用,但是还不能和用户进行交互。

  1. onResume()

表示Activity已经启动完成,进入到了前台,可以同用户进行交互了。

  1. onPause()

这个方法在系统准备去启动另一个 Activity 的时候调用。可以在这里释放系统资源,动画的停止,不宜在此做耗时操作。

  1. onStop()

当Activity不可见的时候回调此方法。需要在这里释放全部用户使用不到的资源。可以做较重量级的工作,如对注册广播的解注册,对一些状态数据的存储。此时Activity还不会被销毁掉,而是保持在内存中,但随时都会被回收。通常发生在启动另一个Activity或切换到后台时

  1. onDestroy()

Activity即将被销毁。此时必须主动释放掉所有占用的资源。

  1. onRestart()

这个方法在 Activity 由停止状态变为运行状态之前调用,也就是 Activity 被重新启动了(APP切到后台会进入onStop(), 再切换到前台时会触发onRestart()方法)

3. Activity组件注册

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

一般情况下: 在新建一个activity后,为了使intent可以调用此活动,我们要在androidManifest.xml文件中添加一个标签,标签的一般格式如下:

 <activity
            android:name=".MainActivity"
            android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
  </activity>
  • android:name是对应Activity的类名称
  • android:label是Activity标题栏显示的内容. 现已不推荐使用
  • 是意图过滤器. 常用语隐式跳转
  • 是动作名称,是指intent要执行的动作
  • 是过滤器的类别 一般情况下,每个 中都要显示指定一个默认的类别名称,即<category android:name="android.intent.category.DEFAULT" />

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

4. Activity启动与参数传递

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

1. 显式启动

  1. 显式启动

改方式通过包名来启动

// 常规跳转
val intent = Intent(MainActivity@this,SecondActivity::class.java)
startActivity(intent)

// 携带参数跳转
intent.putExtra("testInt",100)
intent.putExtra("testObj","123")
startActivity(intent)

//子页面取值,两种
println("from MainActivity ${intent.extras?.get("testInt")}")
println("from MainActivity ${intent.getStringExtra("testObj")}")
  1. 带返回值的启动

修改MainActivity

package com.example.myapplication

import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.view.Gravity
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.navigation.findNavController
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.setupWithNavController
import com.example.myapplication.databinding.ActivityMainBinding
import com.example.myapplication.http.ApiServiceKotlin
import com.example.myapplication.http.Result
import com.example.myapplication.http.RetrofitUtil
import com.example.myapplication.http.UserInfo
import com.google.android.material.bottomnavigation.BottomNavigationView
import com.google.android.material.button.MaterialButtonToggleGroup
import retrofit2.Callback
import java.util.*

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding
    // 让textView延迟加载
    private lateinit var textview: TextView
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Log.e("$this","onCreate")
        textview = TextView(MainActivity@this)
        textview.text ="MainActivity"
        textview.gravity = Gravity.CENTER
        setContentView(textview)
        textview.setOnClickListener {

            val intent = Intent(MainActivity@this,SecondActivity::class.java)
            intent.putExtra("testInt",100)
            intent.putExtra("testObj","123")
            startActivityForResult(intent,1000)
        }
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if(requestCode == 1000 && resultCode == Activity.RESULT_OK && data != null){
            textview.text = "${data.getStringExtra("extra_string")} --> ${data.getIntExtra("extra_Int",0)}"
        }
    }
}

修改SecondActivity

package com.example.myapplication

import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.view.Gravity
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity

class SecondActivity: AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val textView = TextView(this)
        textView.text = "SecondActivity"
        textView.gravity = Gravity.CENTER
        setContentView(textView)
        println("from MainActivity ${intent.extras?.get("testInt")}")
        println("from MainActivity ${intent.getStringExtra("testObj")}")
        Log.e("$this","onCreate")

        textView.setOnClickListener {
            val intent = Intent()
            intent.putExtra("extra_string","extra_string")
            intent.putExtra("extra_Int",100)
            // 返回给MainActivity
            setResult(Activity.RESULT_OK,intent)
            // 必须调用finish
            finish()
        }
    }

}

2. 隐式启动

并不明确指定要启动哪个 Activity,而是通过指定 actioncategory 的信息,让系统去分析这个 Intent,并找出合适的 Activity 去启动。

<activity
          android:name=".SecondActivity"
          android:label="@string/app_name"
          android:exported="true">
    <intent-filter>
        <action android:name="com.example.myapplication.action.SECONDACTIVITY" />
        <category android:name="com.example.myapplication.category.SECONDACTIVITY"/>
        <category android:name="android.intent.category.DEFAULT"/>
    </intent-filter>
</activity>
textview.setOnClickListener {

    val intent = Intent()
    intent.action = "com.example.myapplication.action.SECONDACTIVITY"
    intent.addCategory("com.example.myapplication.category.SECONDACTIVITY")
    intent.putExtra("testInt",100)
    intent.putExtra("testObj","123")
    startActivity(intent)
    //startActivityForResult(intent,1000)
}

5. 系统中常见的Activity

1. 拨打电话

给10086打电话

import android.net.Uri


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

2. 发短信

val uri = Uri.parse("smsto:10086")
val intent = Intent(Intent.ACTION_SENDTO,uri)
intent.putExtra("sms_body","Hello")
startActivity(intent)

3. 打开浏览器

打开baidu主页

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

4. 多媒体播放

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

5. 打开摄像头拍照

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

// 在Activity的onActivityResult方法回调中取出照片数据
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)
    val extras = intent.getExtras();
    var bitmap = extras?.get("data");
    println("$bitmap")
}

6. 从图库选图并剪切

var intent = Intent(Intent.ACTION_GET_CONTENT)
intent.type = "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(File("/mnt/sdcard/temp"))); // 保存路径
intent.putExtra("outputFormat", "JPEG");// 返回格式
startActivityForResult(intent, 0);

7. 剪切指定图片文件

val intent = Intent("com.android.camera.action.CROP"); 
intent.setClassName("com.android.camera","com.android.camera.CropImage"); 
intent.setData(Uri.fromFile(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);

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

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

3. Fragment

1. 简介

**初衷:**Fragment是Android3.0后引入的一个新的API,他出现的初衷是为了适应大屏幕的平板电脑, 当然现在他仍然是平板APP UI设计的宠儿。

**现状:**现在我们普通APP开发也经常会用到Fragment,如果一个界面很复杂,我们把所有代码都写在一个Activity里面,页面布局都写在同一个xml文件中。过不了多久我们就会发现写不动了,一个Activity上万行代码,非常难以维护,后续如果有变动,更是无从下手。而使用Fragment 我们可以把页面结构划分成几块,每块使用一个Fragment来管理。这样我们可以更加方便的在运行过程中动态地更新Activity中的用户界面,日后迭代更新、维护也是更加方便。

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

2. 生命周期

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

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

  3. 当新页面返回后,当前Activity和Fragment又可见了,会再次执行onStartonResume

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

3. 动态添加与数据传递

1. 动态添加Fragment

  1. 创建activity_second.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/container_second">
    <FrameLayout
        android:id="@+id/container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</LinearLayout>
  1. 创建Fragment
package com.example.myapplication.components

import android.content.Context
import android.os.Bundle
import android.util.Log
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.fragment.app.Fragment

class SecondFragment: Fragment() {
    override fun onAttach(context: Context) {
        super.onAttach(context)
        Log.e("SecondFragment","onAttach: 当Fragment呗添加到Activity中会回调,只会被调用一次")
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Log.e("SecondFragment","onCreate: 创建Fragment时回调,只会被调用一次")
    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        var textView = TextView(context)
        textView.text = "SecondFragment"
        textView.gravity = Gravity.CENTER
        Log.e("SecondFragment","onCreateView: 每次创建,绘制Fragment的View组件的时候回回调,会将显示的View返回")
        return textView
    }

    override fun onStart() {
        super.onStart()
        Log.e("SecondFragment","onStart: 启动Fragment的时候回调,此时页面还不能操作")
    }

    override fun onResume() {
        super.onResume()
        Log.e("SecondFragment","onResume: 回复Fragment的时候回调,onStart之后一定会调用onResume,onStart让页面可见,onResume可交互")
    }

    override fun onPause() {
        super.onPause()
        Log.e("SecondFragment","onPause: 暂停Fragment的时候回调")
    }

    override fun onStop() {
        super.onStop()
        Log.e("SecondFragment","onStop: 停止Fragment的时候回调")
    }

    override fun onDestroyView() {
        super.onDestroyView()
        Log.e("SecondFragment","onDestroyView: 销毁Fragment所包含的View组件是使用")
    }

    override fun onDestroy() {
        super.onDestroy()
        Log.e("SecondFragment","onDestroy: 销毁Fragment的时候回调")
    }

    override fun onDetach() {
        super.onDetach()
        Log.e("SecondFragment","onDestroy: 将Fragment从Activity删除、替换没完成后回调此方法")
    }
}
  1. 在SecondActivity中绑定
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_second)
    val fragment = SecondFragment()
    val ft = supportFragmentManager.beginTransaction()
    ft.add(R.id.container,fragment)
    // 必须调用此方法,否则的话fragment加载不出来
    ft.commitAllowingStateLoss()
}

2. 常用方法

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() 

3. 给Fragment传递数据

与Activity不同,这个需要使用budle传递数据

  1. 改造SecondActivity
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_second)
    val fragment = SecondFragment()
    var bundle = Bundle()
    bundle.putInt("intExtra",100)
    bundle.putString("stringExtra","stringExtra")
    fragment.arguments = bundle
    val ft = supportFragmentManager.beginTransaction()
    ft.add(R.id.container,fragment)
    ft.commitAllowingStateLoss()
}
  1. 修改SecondFragment
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    var intExtra = arguments?.get("intExtra")
    println(intExtra)
    var stringExtra = arguments?.get("stringExtra")
    println(stringExtra)
    // 使用as关键字进行强转类型
    val textView = view as TextView
    textView.text = "$intExtra --> $stringExtra"
}

4. 实现底部导航栏布局

  1. 修改activity_second.xml
<?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"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <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/logo"
            app:iconGravity="textTop"
            app:iconTint="@null"
            app:iconSize="30dp"
            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/logo"
            app:iconGravity="textTop"
            app:iconTint="@null"
            app:iconSize="30dp"
            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/logo"
            app:iconGravity="textTop"
            app:iconTint="@null"
            app:iconSize="30dp"
            tools:ignore="HardcodedText" />

    </com.google.android.material.button.MaterialButtonToggleGroup>
</LinearLayout>
  1. 修改SecondActivity
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    // 绑定xml文件
    setContentView(R.layout.activity_second)
    // 确定用户选中的标签的下标
    var selectIndex = 0
    toggle_group.addOnButtonCheckedListener { group, checkedId, isChecked ->
        val childCount = group.childCount
        for(index in 0 until childCount){
            var button = group.getChildAt(index) as MaterialButton
            // 选中的原色设置为红色,没选中为黑色
            if(button.id == checkedId){
                selectIndex = index
                button.setTextColor(Color.RED)
                button.iconTint = ColorStateList.valueOf(Color.RED)
            }else{
                button.setTextColor(Color.BLUE)
                button.iconTint = ColorStateList.valueOf(Color.BLUE)
            }
        }
		switchFragment(selectIndex)
	}
     toggle_group.check(R.id.tab1)
 }
// 用来确认fragment是否展示
private var showFt: SecondFragment ?= null
private fun switchFragment(selectIndex: Int){
    var tabFragment = SecondFragment()
    var bundle = Bundle()
    bundle.putString("tab","tab${selectIndex + 1}")
    tabFragment!!.arguments = bundle
    var ft = supportFragmentManager.beginTransaction()
    // fragment只能添加一次
    if(!tabFragment.isAdded) {
        ft.add(R.id.container,tabFragment)
    }
    ft.show(tabFragment)
    // 如果showFt不为空的话就把他隐藏掉,否则文字会重叠
    if(showFt != null){
        ft.hide(showFt!!)
    }
    showFt = tabFragment
    // 不加这个不展示
    ft.commitAllowingStateLoss()
}

4. Service

1. 简介

Service服务是Android四大组件之一,是Android提供的一种的 不需要和用户交互,且需要长期运行任务的解决方案。

Service启动后默认是运行在主线程中,在执行具体耗时任务过程中要手动开启子线程,应用程序进程被杀死,所有依赖该进程的Service服务也会停止运行。
05_service_info.png

2. 生命周期

Service启动方式分为两种,普通启动startService绑定启动bindService
06_service生命周期

1. 普通启动startService()

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

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

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

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

  • 创建service服务
package com.example.myapplication.service

import android.app.Service
import android.content.Intent
import android.os.IBinder
import android.util.Log


class TestService1:  Service(){
    override fun onBind(p0: Intent?): IBinder? {
        return null
    }

    override fun onCreate() {
        Log.e("TestService1","onCreate")
        super.onCreate()
    }
	 /**
     * 对于使用startService的方式而言
     * onStartCommand就是我们用于后台任务的地方,
     * 如果我们多次调用startService,会直接调用onStartCommand,而不调用onCrate
     *
     * 这种方式启动的服务,他的生命周期跟应用程序的周期一样长,
     * 除非调用stopService,否则只要应用程序不被杀死,服务就会一直运行着
     */
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        Log.e("TestService1","onStartCommand")
        return super.onStartCommand(intent, flags, startId)
    }

    override fun onUnbind(intent: Intent?): Boolean {
        Log.e("TestService1","onUnbind")
        return super.onUnbind(intent)
    }

    override fun onDestroy() {
        super.onDestroy()
        Log.e("TestService1","onDestroy")
    }
}
  • 创建Activity
package com.example.myapplication.service

import android.content.Intent
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import com.example.myapplication.R
import kotlinx.android.synthetic.main.activity_testservice.*

class TestServiceActivity: AppCompatActivity() {
    private val TAG = "TestServiceActivity"
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_testservice)
        Log.e(TAG,"$TAG onCreate")
		val intent = Intent(TestServiceActivity@this,TestService1::class.java)
        start_service.setOnClickListener {           
            startService(intent)
        }

        stop_service.setOnClickListener{
            stopService(intent)
        }
    }
}
  • 创建activity_testservice.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:gravity="center"
    android:layout_height="match_parent">

    <Button
        android:id="@+id/start_service"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="StartService"
        android:textAllCaps="false"
        android:backgroundTint="@color/black"
        />
    <Button
        android:id="@+id/stop_service"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="StopService"
        android:textAllCaps="false"
        android:backgroundTint="@color/black"
        />
</LinearLayout>
  • AndroidMainfest.xml定义
<application>
	<activity android:name=".service.TestServiceActivity"/>
    <service android:name=".service.TestService1" />
</application>
  • 启动日志
TestService1: onCreate方法被调用!
TestService1: onStartCommand方法被调用!
TestService1: onStartCommand方法被调用!
TestService1: onStartCommand方法被调用!
TestService1: onStartCommand方法被调用!
TestService1: onDestory方法被调用!

从上面的运行结果我们可以验证我们生命周期图中解释的内容: 我们发现onBind()方法并没有被调用,另外多次点击启动Service,只会重复地调用onStartCommand 方法!无论我们启动多少次Service,一个stopService就会停止Service!

2. 绑定启动 bindService()

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

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

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

  • 创建TestService2
package com.example.myapplication.service

import android.app.Service
import android.content.Intent
import android.nfc.Tag
import android.os.Binder
import android.os.IBinder
import android.util.Log

class TestService2: Service(){
    private var count = 0
    private var flag = false
    private var tag = "TestService2"
    override fun onCreate() {
        Log.e(tag ,"onCreate")
        Thread (Runnable{
            while (!flag) {
                Log.e(tag,"count $count " )
                Thread.sleep(100)
                count++
            }
        }).start()
        super.onCreate()
    }
    private val binder = MyBinder()
    inner class MyBinder: Binder(){
        fun getCount(): Int{
            return count
        }
    }

    override fun onBind(intent: Intent?): IBinder?{
        Log.e(tag,"onBInd")
        return binder
    }

    /**
     * 对于使用startService的方式而言
     * onStartCommand就是我们用于后台任务的地方,
     * 如果我们多次调用startService,会直接调用onStartCommand,而不调用onCrate
     *
     * 这种方式启动的服务,他的生命周期跟应用程序的周期一样长,
     * 除非调用stopService,否则只要应用程序不被杀死,服务就会一直运行着
     */
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        Log.e(tag,"onStartCommand")
        return super.onStartCommand(intent, flags, startId)
    }

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

    override fun onRebind(intent: Intent?) {
        super.onRebind(intent)

    }
    override fun onDestroy() {
        super.onDestroy()
        Log.e(tag,"onDestroy")
    }
}
  1. 注册Service
<application>
    <service android:name=".service.TestService2" />
</application>    
  1. 修改TestActivity
package com.example.myapplication.service

import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.os.Bundle
import android.os.IBinder
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import com.example.myapplication.R
import kotlinx.android.synthetic.main.activity_testservice.*

class TestServiceActivity: AppCompatActivity() {
    private val TAG = "TestServiceActivity"
    private lateinit var mybinder : TestService2.MyBinder
    private var connection: ServiceConnection? = null
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_testservice)
        Log.e(TAG,"$TAG onCreate")
		// 创建连接
        connection = object :ServiceConnection{
            override fun onServiceConnected(componentName: ComponentName?, binder: IBinder?) {
                Log.e("$this","service connected")
                // 使用as关键字强转为自己创建的内部类
                mybinder = binder as TestService2.MyBinder
            }

            override fun onServiceDisconnected(p0: ComponentName?) {
                Log.e("$this","service onServiceDisconnected")
            }

        }
        // 绑定intent,声明周期和Activity一样长,
        // 适合运行一些和Activity声明周期相同的任务,比如说跨进程的通信
        val intent = Intent(TestServiceActivity@this,TestService2::class.java)
        // 绑定Service
        bindService(intent,connection!!, Context.BIND_AUTO_CREATE)
        start_service.setOnClickListener {

            Log.e("$this TestService2","getCount: ${mybinder?.getCount()} ")
            //startService(intent)
        }

        stop_service.setOnClickListener{
            //val intent = Intent(TestServiceActivity@this,TestService1::class.java)
            //stopService(intent)
            // 使用unbindService停止服务
            unbindService(connection!!)
        }
    }
	// 本activity销毁的时候停止service
    override fun onDestroy() {
        super.onDestroy()
        unbindService(connection!!)
    }
}

使用BindService绑定Service,依次调用onCreate(),onBind()方法, 我们可以在onBind()方法中返回自定义的IBinder对象;再接着调用的是 ServiceConnection的onServiceConnected()方法该方法中可以获得 IBinder对象,从而进行相关操作;当Service解除绑定后会自动调用 onUnbind和onDestroyed方法,当然绑定多客户端情况需要解除所有 的绑定才会调用onDestoryed方法进行销毁哦

3. Android8.0以后

在一加手机上,用户升级了新版8.0的系统,用户将app切到后台,过一会儿就弹出“xxx app 已停止运行”的弹窗。

通过定位分析,发现下面俩前置条件

  1. 8.0系统杀服务杀的很频繁
  2. 为了保活,我们使用了俩Service互保的方式

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

  • 如果针对 Android 8.0 的应用尝试在不允许其创建后台服务的情况下使用 startService() 函数,则该函数将引发一个 IllegalStateException
  • 新的 Context.startForegroundService() 函数将启动一个前台服务。现在,即使应用在后台运行,系统也允许其调用 Context.startForegroundService()。不过,应用必须在创建服务后的五秒内调用该服务的 startForeground() 函数。
Process: com.example.firstapp, PID: 10510
    java.lang.IllegalStateException: Not allowed to start service Intent { cmp=com.example.firstapp/.components.TestService }: app is in background uid UidRecord{adece9d u0a77 LAST bg:+1m35s61ms idle procs:1 seq(0,0,0)}
        at android.app.ContextImpl.startServiceCommon(ContextImpl.java:1505)
        at android.app.ContextImpl.startService(ContextImpl.java:1461)
        at android.content.ContextWrapper.startService(ContextWrapper.java:644)
        at android.content.ContextWrapper.startService(ContextWrapper.java:644)

1. 解决办法

  1. AndroidManifest.xml添加权限
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
  1. 修改TestActivity
 override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_testservice)
        Log.e(TAG,"$TAG onCreate")
        var build = Notification.Builder(applicationContext, "channel_id").build()
        val intent = Intent(TestServiceActivity@this,TestService2::class.java)
        //bindService(intent,connection!!, Context.BIND_AUTO_CREATE)
        Handler().postDelayed(Runnable {
            //startService(Intent(this@TestServiceActivity,TestService2::class.java))
            if (Build.VERSION.SDK_INT >= 26) {
                startForegroundService(intent);
            } else {
                // Pre-O behavior.
                startService(intent);
            }

        },70000) // 超过60s才算在后台
    }
  1. 修改TestService2
override fun onCreate() {
    Log.e(tag ,"onCreate")
    Log.e(tag ,"beforeThread")
    Thread (Runnable{
        while (!flag) {
            Log.e(tag,"count $count " )
            Thread.sleep(100)
            count++
        }
    }).start()
    Log.e(tag ,"afterThread")
    //startForeground(0)
    super.onCreate()

    if(Build.VERSION.SDK_INT >= 26){
        val notification = Notification.Builder(applicationContext,"channel_id").build()
        startForeground(1,notification)
    }
}

5. BroadcastReceiver广播接收者

1. 简介

BroadcastReceiver广播接收者Android四大组件之一,是Android系统提供的一种通讯方式。

我们举个形象的例子来帮我理解下BroadcastReceiver,记得以前读书 的时候,每个班级都会有一个挂在墙上的大喇叭,用来广播一些通知,比如,开学要去搬书,教导主任对着大喇叭喊广播: “每个班级找几个同学教务处拿书”,发出这个广播后,所有同学都会在同一时刻收到这条广播通知, 收到,但不是每个同学都会去搬书,一般去搬书的都是班里的"大力士",这群"大力士"接到这条 广播后就会动身去把书搬回教室!

上面这个就是一个广播传递的一个很形象的例子: 教导主任喊大喇叭–> 发送广播 --> 所有学生都能收到广播 --> 大力士处理广播 。这个流程涉及到两个角色,一个是广播发送者,一个是广播接收者。

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

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

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

应用场景:

Android不同组件间的通信(含 :应用内 / 不同应用之间)

多线程通信

Android 系统在特定情况下的通信

2. 广播类型

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

3. 实例Demo

创建一个广播接收者,监听网络连接变化

1. 创建广播接收者

package com.example.myapplication.receiver

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.net.ConnectivityManager
import android.util.Log
import android.widget.Toast

class TestBroadcastReceiver: BroadcastReceiver() {
    private val tag = "TestBroadcastReceiver"

    override fun onReceive(context: Context?, intent: Intent?) {
        var action = intent?.action?: return
        if(action == ConnectivityManager.CONNECTIVITY_ACTION){
            val connectivityManager =
                context?.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
            var activeNetworkInfo = connectivityManager?.activeNetworkInfo
            if(activeNetworkInfo != null && activeNetworkInfo.isAvailable){
                Log.e(tag,"有网络连接")
                // 弹窗提示消息
                Toast.makeText(context,"当前网络连接类型: ${activeNetworkInfo.typeName}",Toast.LENGTH_LONG).show()
            }else{
                Log.e(tag,"无网络连接")
                Toast.makeText(context,"无网络连接",Toast.LENGTH_LONG).show()
            }
        }
    }
}

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

  1. 添加Activity用来注册广播接收事件
package com.example.myapplication.receiver

import android.content.IntentFilter
import android.net.ConnectivityManager
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity

class TestBroadcastReceiverActivity: AppCompatActivity() {

    private lateinit var receiver: TestBroadcastReceiver
    private var tag = "TestBroadcastReceiverActivity"

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Log.e(tag,"onCreate")
        receiver = TestBroadcastReceiver()
        // 绑定接收者出发的事件为网络连接
        var intentFilter = IntentFilter()
        intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION)
        // 动态注册广播接收者
        registerReceiver(receiver,intentFilter)
    }

    override fun onDestroy() {
        super.onDestroy()
        // 当activity销毁的时候卸载接收者
        unregisterReceiver(receiver)
    }
}
  1. 修改SecondFragment使之点击文字调转到本Activity
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    val textView = view as TextView
    textView.text = "${arguments?.get("tab")}"
    textView.setOnClickListener {
        Log.e("testTag","testTag")
        startActivity(Intent(context,TestBroadcastReceiverActivity::class.java))
    }
    println(activity?.intent)
}
  1. 注册本Activity
<application>
	<activity android:name=".receiver.TestBroadcastReceiverActivity"/>
</application>

3. 静态注册广播

这种方式需要在AndroidManifest.xml中注册

Google官方声明:Beginning with Android 8.0 (API level 26), the system imposes additional restrictions on manifest-declared receivers. If your app targets API level 26 or higher, you cannot use the manifest to declare a receiver for most implicit broadcasts (broadcasts that do not target your app specifically).

大概意思就是说:从android 8.0(API26)开始,对清单文件中静态注册广播接收者增加了限制,建议大家不要在清单文件中静态注册广播接收者。

**其实说白点:**就是因为在清单文件中静态注册广播接收者,容易让一些"不法分子"获取用户的隐私(如:电话监听、短信监听等等),所以google限制了静态注册(Android在保护用户隐私上坚持不懈的努力着…也许google还要其他的考虑吧。咱也不知道…咱也不敢问😁)

  1. 添加静态注册信息
<receiver android:name=".receiver.TestBroadcastReceiver"
          android:exported="true">
    <intent-filter>
        <action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>
    </intent-filter>
</receiver>
  1. 修改activity
package com.example.myapplication.receiver


import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity

class TestBroadcastReceiverActivity: AppCompatActivity() {

    private var tag = "TestBroadcastReceiverActivity"

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Log.e(tag,"onCreate")
    }

    override fun onDestroy() {
        super.onDestroy()
    }
}
  1. 运行起来之后发现并没有提示。

  2. 解决静态注册广播接收者收不到事件的问题,修改Activity

package com.example.myapplication.receiver


import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity

class TestBroadcastReceiverActivity: AppCompatActivity() {

    private var tag = "TestBroadcastReceiverActivity"

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Log.e(tag,"onCreate")
        var intent = Intent()
        intent.action = "com.example.myapplication.TEST_BROADCAST_RECEIVER"
        intent.component = ComponentName(packageName,"com.example.myapplication.receiver.TestBroadcastReceiver")
        // 设置广播接收者
        sendBroadcast(intent)
    }

    override fun onDestroy() {
        super.onDestroy()
    }
}
  1. 修改清单文件的aciton为自己的自定义名称
<application>
    <receiver android:name=".receiver.TestBroadcastReceiver"
              android:exported="true">
        <intent-filter>
            <action android:name="com.example.myapplication.TEST_BROADCAST_RECEIVER"/>
        </intent-filter>
    </receiver>
</application>
  1. 修改广播接收者
override fun onReceive(context: Context?, intent: Intent?) {
    if(action == "com.example.myapplication.TEST_BROADCAST_RECEIVER"){
        Toast.makeText(context,"静态注册的自定义事件",Toast.LENGTH_LONG).show()
    }
}

4. 全局发送广播

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

sendBroadcast(new Intent("com.example.myapplication.TEST_BROADCAST_RECEIVER"));

5. 应用内发送广播

  • App应用内广播可理解为一种局部广播,广播的发送者和接收者都同属于一个App。

  • 相比于全局广播(普通广播),App应用内广播优势体现在:安全性高 & 效率高

package com.example.myapplication.receiver

import android.content.ComponentName
import android.content.Intent
import android.content.IntentFilter
import android.net.ConnectivityManager
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.localbroadcastmanager.content.LocalBroadcastManager

class TestBroadcastReceiverActivity: AppCompatActivity() {

    private lateinit var receiver: TestBroadcastReceiver
    private var tag = "TestBroadcastReceiverActivity"
    private lateinit var localBroadcastManager : LocalBroadcastManager
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Log.e(tag,"onCreate")
        receiver = TestBroadcastReceiver()
        var intentFilter = IntentFilter()
        intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION)
        // 动态注册
        //registerReceiver(receiver,intentFilter)
        // 应用内广播
        localBroadcastManager = LocalBroadcastManager.getInstance(this)
        localBroadcastManager.registerReceiver(receiver,intentFilter)
        localBroadcastManager.sendBroadcast(Intent(ConnectivityManager.CONNECTIVITY_ACTION))
    }

    override fun onDestroy() {
        super.onDestroy()
        // 卸载广播
        localBroadcastManager.unregisterReceiver(receiver)
    }
}

6. 系统广播

  • Android中内置了多个系统广播:只要涉及到手机的基本操作(如开机、网络状态变化、拍照等等),都会发出相应的广播
  • 每个广播都有特定的Intent - Filter(包括具体的action),Android系统广播action如下:
系统操作action
监听网络变化android.net.conn.CONNECTIVITY_CHANGE
关闭或打开飞行模式Intent.ACTION_AIRPLANE_MODE_CHANGED
充电时或电量发生变化Intent.ACTION_BATTERY_CHANGED
电池电量低Intent.ACTION_BATTERY_LOW
电池电量充足(即从电量低变化到饱满时会发出广播Intent.ACTION_BATTERY_OKAY
系统启动完成后(仅广播一次)Intent.ACTION_BOOT_COMPLETED
按下照相时的拍照按键(硬件按键)时Intent.ACTION_CAMERA_BUTTON
屏幕锁屏Intent.ACTION_CLOSE_SYSTEM_DIALOGS
设备当前设置被改变时(界面语言、设备方向等)Intent.ACTION_CONFIGURATION_CHANGED
插入耳机时Intent.ACTION_HEADSET_PLUG
未正确移除SD卡但已取出来时(正确移除方法:设置–SD卡和设备内存–卸载SD卡)Intent.ACTION_MEDIA_BAD_REMOVAL
插入外部储存装置(如SD卡)Intent.ACTION_MEDIA_CHECKING
成功安装APKIntent.ACTION_PACKAGE_ADDED
成功删除APKIntent.ACTION_PACKAGE_REMOVED
重启设备Intent.ACTION_REBOOT
屏幕被关闭Intent.ACTION_SCREEN_OFF
屏幕被打开Intent.ACTION_SCREEN_ON
关闭系统时Intent.ACTION_SHUTDOWN
重启设备Intent.ACTION_REBOOT

7. 自定义一个工具类

自定义一个工具类用来展示消息

package com.example.myapplication.utils

import android.content.Context
import android.widget.Toast

object ToastUtil {
    fun makeTextShowLong(context: Context?,message: String): Unit{
        Toast.makeText(context,message,Toast.LENGTH_LONG).show()
    }

    fun makeTextShowShort(context: Context?,message: String): Unit{
        Toast.makeText(context,message,Toast.LENGTH_SHORT).show()
    }
}

6. ContentProvider

1. 简介

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

2. 动态权限申请

从android6.0开始,凡是涉及用户隐私的权限(读写短信,读写联系人,拍摄,录音等等),都需要运行时申请,弹窗提醒用户是否授权。用户不授权则无法继续操作,而且今年工信部对于违规收集,申请用户权限的APP查的非常严格,不定期抽查,抽查有问题的必须按期整改,否则强制下架。

1. 清单文件声明权限

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

2. 动态申请授权

  1. ActivityCompat.checkSelfPermission():检查权限是否已授权,如果没授权则需要向用户申请

  2. ActivityCompat.requestPermissions():发起权限申请,会弹出对话框
    08_read_contacts.png

  3. ActivityCompat.shouldShowRequestPermissionRationale(): 检查用户是否已经永久拒绝,如果用户已永久拒绝某个权限的申请,即便再调用ActivityCompat.requestPermissions,系统也不会弹框向用户申请了。此时需要自己弹对话框,引导用户去开启授权

  4. onRequestPermissionsResult:处理权限授权的结果

  5. 完整的权限申请案例

package com.example.myapplication.prodiver

import android.content.pm.PackageManager
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import com.example.myapplication.utils.ToastUtil

class TestContentProviderActivity: AppCompatActivity() {
    private var tag = "TestContentProviderActivity"
    private var readContacts = android.Manifest.permission.READ_CONTACTS
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        if(ActivityCompat.checkSelfPermission(this, readContacts)
            != PackageManager.PERMISSION_GRANTED){
            ActivityCompat.requestPermissions(this,
                arrayOf(readContacts),
                100
            )

        }else{
            getContacts()
        }
    }

    private fun getContacts(){
        ToastUtil.makeTextShowLong(this,"获取短信")
    }

    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        // 这里与 ActivityCompat.requestPermissions最后一个参数一致
        if(requestCode == 100 &&
            permissions.contains(readContacts) &&
            grantResults.contains(PackageManager.PERMISSION_GRANTED)){
            getContacts()
        }else{
            ToastUtil.makeTextShowLong(this,"读取通讯录权限被拒绝")
        }
    }
}

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import com.example.myapplication.utils.ToastUtil

class TestContentProviderActivity: AppCompatActivity() {
private var tag = “TestContentProviderActivity”
private var readContacts = android.Manifest.permission.READ_CONTACTS
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

    if(ActivityCompat.checkSelfPermission(this, readContacts)
        != PackageManager.PERMISSION_GRANTED){
        ActivityCompat.requestPermissions(this,
            arrayOf(readContacts),
            100
        )

    }else{
        getContacts()
    }
}

private fun getContacts(){
    ToastUtil.makeTextShowLong(this,"获取短信")
}

override fun onRequestPermissionsResult(
    requestCode: Int,
    permissions: Array<out String>,
    grantResults: IntArray
) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults)
    // 这里与 ActivityCompat.requestPermissions最后一个参数一致
    if(requestCode == 100 &&
        permissions.contains(readContacts) &&
        grantResults.contains(PackageManager.PERMISSION_GRANTED)){
        getContacts()
    }else{
        ToastUtil.makeTextShowLong(this,"读取通讯录权限被拒绝")
    }
}

}








Kotlin中,如果你想要拨打电话,你可以使用Android的Intent来完成。你可以使用以下代码来实现拨打电话的功能: ```kotlin import android.net.Uri import android.content.Intent val uri = Uri.parse("tel:10086") val intent = Intent(Intent.ACTION_DIAL, uri) startActivity(intent) ``` 这段代码将会创建一个包含电话号码的Uri对象,并将其传递给一个ACTION_DIAL的Intent。然后,你可以调用startActivity()方法来启动拨号界面并拨打电话。请注意,这只是打开拨号界面,用户仍然需要手动确认是否要拨打电话。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [start_kotlin:启动kotlin](https://download.csdn.net/download/weixin_42105816/15224860)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [Kotlin第七章: Android四大组件](https://blog.csdn.net/qq_42059717/article/details/127398679)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值