1. 四大组件

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

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

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

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

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

#mermaid-svg-VNVYx4eccXFIcQtN {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-VNVYx4eccXFIcQtN .error-icon{fill:#552222;}#mermaid-svg-VNVYx4eccXFIcQtN .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-VNVYx4eccXFIcQtN .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-VNVYx4eccXFIcQtN .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-VNVYx4eccXFIcQtN .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-VNVYx4eccXFIcQtN .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-VNVYx4eccXFIcQtN .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-VNVYx4eccXFIcQtN .marker{fill:#333333;stroke:#333333;}#mermaid-svg-VNVYx4eccXFIcQtN .marker.cross{stroke:#333333;}#mermaid-svg-VNVYx4eccXFIcQtN svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-VNVYx4eccXFIcQtN .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-VNVYx4eccXFIcQtN .cluster-label text{fill:#333;}#mermaid-svg-VNVYx4eccXFIcQtN .cluster-label span{color:#333;}#mermaid-svg-VNVYx4eccXFIcQtN .label text,#mermaid-svg-VNVYx4eccXFIcQtN span{fill:#333;color:#333;}#mermaid-svg-VNVYx4eccXFIcQtN .node rect,#mermaid-svg-VNVYx4eccXFIcQtN .node circle,#mermaid-svg-VNVYx4eccXFIcQtN .node ellipse,#mermaid-svg-VNVYx4eccXFIcQtN .node polygon,#mermaid-svg-VNVYx4eccXFIcQtN .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-VNVYx4eccXFIcQtN .node .label{text-align:center;}#mermaid-svg-VNVYx4eccXFIcQtN .node.clickable{cursor:pointer;}#mermaid-svg-VNVYx4eccXFIcQtN .arrowheadPath{fill:#333333;}#mermaid-svg-VNVYx4eccXFIcQtN .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-VNVYx4eccXFIcQtN .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-VNVYx4eccXFIcQtN .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-VNVYx4eccXFIcQtN .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-VNVYx4eccXFIcQtN .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-VNVYx4eccXFIcQtN .cluster text{fill:#333;}#mermaid-svg-VNVYx4eccXFIcQtN .cluster span{color:#333;}#mermaid-svg-VNVYx4eccXFIcQtN div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-VNVYx4eccXFIcQtN :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}

Android组件
Activity
Fragment
Service服务
BroadcastReceiver
ContentProvider

2. Activity

1. 简介

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

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

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

  2. 一个App允许有多个Activity

2. 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文件中添加一个标签,标签的一般格式如下:

 <activityandroid: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.myapplicationimport 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: TextViewoverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)Log.e("$this","onCreate")textview = TextView(MainActivity@this)textview.text ="MainActivity"textview.gravity = Gravity.CENTERsetContentView(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.myapplicationimport 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.AppCompatActivityclass SecondActivity: AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)val textView = TextView(this)textView.text = "SecondActivity"textView.gravity = Gravity.CENTERsetContentView(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)// 返回给MainActivitysetResult(Activity.RESULT_OK,intent)// 必须调用finishfinish()}}}

2. 隐式启动

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

<activityandroid: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.Urival 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。

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"><FrameLayoutandroid:id="@+id/container"android:layout_width="match_parent"android:layout_height="match_parent"/>
</LinearLayout>
  1. 创建Fragment
package com.example.myapplication.componentsimport 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.Fragmentclass 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.CENTERLog.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 = bundleval 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 TextViewtextView.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"><FrameLayoutandroid:id="@+id/container"android:layout_width="match_parent"android:layout_height="0dp"android:layout_weight="1" /><com.google.android.material.button.MaterialButtonToggleGroupandroid: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.MaterialButtonandroid: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.MaterialButtonandroid: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.MaterialButtonandroid: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 = 0toggle_group.addOnButtonCheckedListener { group, checkedId, isChecked ->val childCount = group.childCountfor(index in 0 until childCount){var button = group.getChildAt(index) as MaterialButton// 选中的原色设置为红色,没选中为黑色if(button.id == checkedId){selectIndex = indexbutton.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 = bundlevar 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服务也会停止运行。

2. 生命周期

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

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.serviceimport android.app.Service
import android.content.Intent
import android.os.IBinder
import android.util.Logclass 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.serviceimport 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"><Buttonandroid:id="@+id/start_service"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="StartService"android:textAllCaps="false"android:backgroundTint="@color/black"/><Buttonandroid: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.serviceimport android.app.Service
import android.content.Intent
import android.nfc.Tag
import android.os.Binder
import android.os.IBinder
import android.util.Logclass TestService2: Service(){private var count = 0private var flag = falseprivate 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 = truereturn 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.serviceimport 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.MyBinderprivate var connection: ServiceConnection? = nulloverride 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)// 绑定ServicebindService(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销毁的时候停止serviceoverride 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: 10510java.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.receiverimport android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.net.ConnectivityManager
import android.util.Log
import android.widget.Toastclass TestBroadcastReceiver: BroadcastReceiver() {private val tag = "TestBroadcastReceiver"override fun onReceive(context: Context?, intent: Intent?) {var action = intent?.action?: returnif(action == ConnectivityManager.CONNECTIVITY_ACTION){val connectivityManager =context?.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManagervar activeNetworkInfo = connectivityManager?.activeNetworkInfoif(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.receiverimport android.content.IntentFilter
import android.net.ConnectivityManager
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivityclass TestBroadcastReceiverActivity: AppCompatActivity() {private lateinit var receiver: TestBroadcastReceiverprivate 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 TextViewtextView.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还要其他的考虑吧。咱也不知道…咱也不敢问

Kotlin第七章: Android四大组件相关推荐

  1. Android——四大组件、六大布局、五大存储

    一.android四大组件 (一)android四大组件详解 Android四大组件分别为activity.service.content provider.broadcast receiver. 1 ...

  2. Android四大组件之——Activity的生命周期(图文详解)

        转载请在文章开头处注明本博客网址:http://www.cnblogs.com/JohnTsai       联系方式:JohnTsai.Work@gmail.com       [Andro ...

  3. Android 四大组件 —— 服务

    一.服务是什么 服务(Service)是Android 中实现程序后台运行的解决方案,它非常适合用于去执行那些不需要和用户交互而且还要求长期运行的任务.服务的运行不依赖于任何用户界面,即使当程序被切换 ...

  4. Android四大组件之bindService源码实现详解

        Android四大组件之bindService源码实现详解 Android四大组件源码实现详解系列博客目录: Android应用进程创建流程大揭秘 Android四大组件之bindServic ...

  5. Android四大组件系列7 Broadcast广播机制(上)

    一 概述 广播 (Broadcast) 机制用于进程或线程间通信,广播分为广播发送和广播接收两个过程,其中广播接收者 BroadcastReceiver 是 Android 四大组件之一.Broadc ...

  6. Android四大组件之BroadCastReceiver

    1. 基本概念 在Android 中,Broadcast 是一种广泛运用的在应用程序之间传输信息的机制.而BroadcastReceiver 是对发送出来的Broadcast 进行过滤接受并响应的一类 ...

  7. Android 四大组件之——Service(一)

    一.什么是服务 服务,是Android四大组件之一, 属于 计算型组件.   长期后台运行的没有界面的组件 ,特点是无用户界面.在后台运行.生命周期长 二,什么时候使用服务? 天气预报:后台的连接服务 ...

  8. Android 四大组件之——Acitivity(一)

    一,什么是Activity activity是Android组件中最基本也是最为常见用的四大组件之一.Android四大组件有Activity,Service服务,Content Provider内容 ...

  9. 重温Android四大组件(一)—Activity的生命周期

    前言 四大组件对于Android开发者是老生常谈的知识了,相信每个Android开发者对四大组件都已经很熟悉了.但是四大组件作为Android应用的基础,作为开发者不仅要熟悉而且要烂熟于心. 这里以& ...

最新文章

  1. 动态修改数据窗口的数据源
  2. JAVA基础----java中E,T,?的区别
  3. 02 算术、字符串与变量(1)
  4. 天津大学学硕和专硕的区别_21考研考生,学硕与专硕的区别你必须知道,选错或后悔读研...
  5. python培训班靠谱吗-学python去哪个培训机构好?靠谱的python培训机构推荐
  6. 二分搜索 POJ 1064 Cable master
  7. ActivityMq下载、安装、使用
  8. VMWare 各版本下载地址
  9. YYC松鼠聚合直播系统添加图片上传视频提示网络错误的问题解决方案
  10. 2020年全球搜索引擎市场份额和全球排名分析
  11. 中国政府数据开放许可协议(CLOD)研究
  12. 【Comsol学习】二维非稳态热传导问题
  13. Python基于周立功盒子的二次开发的封装和调用
  14. C语言外挂实战【转】
  15. Minecraft使用Alibaba_Dragonwell11运行1.16.5服务器[官服核心]
  16. 五分钟学Java:什么是 NullPointerException
  17. 工业超纯水机应用反渗透膜优势
  18. 安居客二手房信息爬取(六安)
  19. springMVC数据库加密解密
  20. Fiori学习01-前期准备

热门文章

  1. Copley驱动器控制永磁同步电机设置步骤1
  2. 走近源码:Redis如何清除过期key
  3. PYTHON 常用开发工具 IDE
  4. 学习Linux命令(15)
  5. ubuntu21.04中OBS的安装方式
  6. Revit SDK 及LookUp 各版本下载地址
  7. [译]星际争霸人工智能比赛——通告
  8. 工业液晶串口屏人机界面组态软件开发指南
  9. 计算机通信与网络大纲中英文,《计算机通信网络》教学大纲(电子信息)
  10. mvc ajax异常,使用SpringMVC的controller中能获取数据但直接跳到异常页面,使用Ajax。...