持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第1天,点击查看活动详情
1. 四大组件
四大组件是每一个Android人必须要会,要掌握的知识点,因为他们是我们在日常开发工作中打交道最频繁的组件,而且他们四个在不同的领域扮演着极其重要的角色。
Activity: 负责用户界面的展示和用户交互,学习Activity就要学习Fragment,虽然它不是四大组件之一,但是它在我们的开发工作中也是频频被使用到,且必须和Activity一块使用,常用于分模块开发,比如慕课首页的几个tab,每个tab都是对应着一个Fragment.
**Service服务:**不需要和用户交互,负责后台任务,比如播放音乐,socket长连接
BroadcastReceiver广播接收者: 负责页面间通信,系统和APP通信,APP和APP通信,比如监听网络连接状态变化,就是通过BroadcastReceiver广播接收者来实现的
ContentProvider内容提供者: 负责数据存取,常用于APP进数据共享,跨进程数据存取等....比如读取相册,读取联系人,都是ContentProvider来实现的
graph LR
ac[Android组件] --> Activity
Activity --> Fragment
ac --> Service服务
ac --> BroadcastReceiver
ac --> ContentProvider
2. Activity
1. 简介
Activity是Android的四大组件之一,Activity是一种能够显示用户界面的组件,用户通过和Activity交互完成相关操作。
一个应用中可以包含0个或多个 Activity,但不包含任何 Activity 的应用程序是无法被用户看见的。
-
Activity用于显示用户界面,用户通过Activity交互完成相关操作
-
一个App允许有多个Activity
2. Activity生命周期
Activity 类中定义了7个回调方法,覆盖了 Activity 生命周期的每一个环节,下面就来介绍一下这7个方法。
- onCreate()
该方法会在 Activity 第一次创建时进行调用,在这个方法中通常会做 Activity 初始化相关的操作,例如:加载布局、绑定事件等。
- onStart()
这个方法会在 Activity 由不可见变为可见的时候调用,但是还不能和用户进行交互。
- onResume()
表示Activity已经启动完成,进入到了前台,可以同用户进行交互了。
- onPause()
这个方法在系统准备去启动另一个 Activity 的时候调用。可以在这里释放系统资源,动画的停止,不宜在此做耗时操作。
- onStop()
当Activity不可见的时候回调此方法。需要在这里释放全部用户使用不到的资源。可以做较重量级的工作,如对注册广播的解注册,对一些状态数据的存储。此时Activity还不会被销毁掉,而是保持在内存中,但随时都会被回收。通常发生在启动另一个Activity或切换到后台时
- onDestroy()
Activity即将被销毁。此时必须主动释放掉所有占用的资源。
- 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. 显式启动
- 显式启动
改方式通过包名来启动
// 常规跳转
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")}")
- 带返回值的启动
修改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,而是通过指定
action
和category
的信息,让系统去分析这个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。
2. 生命周期
-
Activity加载Fragment的时候,依次调用下面的方法: onAttach -> onCreate -> onCreateView -> onActivityCreated -> onStart ->onResume
-
当我们启动一个新的页面, 此时Fragment所在的Activity不可见,会执行 onPause
-
当新页面返回后,当前Activity和Fragment又可见了,会再次执行onStart和 onResume
-
退出了Activity的话,那么Fragment将会被完全结束, Fragment会进入销毁状态 onPause -> onStop -> onDestoryView -> onDestory -> onDetach
3. 动态添加与数据传递
1. 动态添加Fragment
- 创建
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>
- 创建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删除、替换没完成后回调此方法")
}
}
- 在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传递数据
- 改造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()
}
- 修改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. 实现底部导航栏布局
- 修改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>
- 修改
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服务也会停止运行。
2. 生命周期
Service启动方式分为两种,普通启动startService 、绑定启动bindService
1. 普通启动startService()
首次启动会创建一个Service实例,依次调用onCreate()和onStartCommand()方法,此时Service 进入运行状态
如果再次调用StartService启动Service,将不会再创建新的Service对象, 系统会直接复用前面创建的Service对象,调用它的onStartCommand()方法!
这样的Service与它的调用者无必然的联系,就是说当调用者结束了自己的生命周期, 但是只要不调用stopService,那么Service还是会继续运行的!
无论启动了多少次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()
当首次使用bindService()启动一个Service时,系统会实例化一个Service实例,并调用其**onCreate()和onBind()**方法,然后调用者就可以通过返回的IBinder对象和Service进行交互了,此后如果我们再次使用bindService绑定Service,系统不会创建新的Sevice实例,也不会再调用onBind()方法,只会直接把IBinder对象返回给调用方
如果我们解除与服务的绑定,只需调用unbindService(),此时onUnbind和onDestory方法将会被调用
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")
}
}
- 注册Service
<application>
<service android:name=".service.TestService2" />
</application>
- 修改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 已停止运行”的弹窗。
通过定位分析,发现下面俩前置条件
- 8.0系统杀服务杀的很频繁
- 为了保活,我们使用了俩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. 解决办法
AndroidManifest.xml
添加权限
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
- 修改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才算在后台
}
- 修改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. 广播类型
- 标准广播:发出广播后,该广播事件的接收者,几乎会在同一时刻收到通知,都可以响应或不响应该事件
- 有序广播:发出广播后,同一时刻,只有一个广播接收者能收到、一个接收者处理完后之后,可以选择继续向下传递给其它接收者,也可以拦截掉广播。[不常用、不推荐使用了]
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. 运行时动态注册广播接收事件
- 添加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)
}
}
- 修改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)
}
- 注册本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还要其他的考虑吧。咱也不知道...咱也不敢问😁)
- 添加静态注册信息
<receiver android:name=".receiver.TestBroadcastReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>
</intent-filter>
</receiver>
- 修改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()
}
}
-
运行起来之后发现并没有提示。
-
解决静态注册广播接收者收不到事件的问题,修改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()
}
}
- 修改清单文件的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>
- 修改广播接收者
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 |
成功安装APK | Intent.ACTION_PACKAGE_ADDED |
成功删除APK | Intent.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,另外我们可以选择要暴露的数据,就避免了我们隐私数据的的泄露!
2. 动态权限申请
从android6.0开始,凡是涉及用户隐私的权限(读写短信,读写联系人,拍摄,录音等等),都需要运行时申请,弹窗提醒用户是否授权。用户不授权则无法继续操作,而且今年工信部对于违规收集,申请用户权限的APP查的非常严格,不定期抽查,抽查有问题的必须按期整改,否则强制下架。
1. 清单文件声明权限
<uses-permission android:name="android.permission.READ_CONTACTS"/>
2. 动态申请授权
-
ActivityCompat.checkSelfPermission():检查权限是否已授权,如果没授权则需要向用户申请
-
ActivityCompat.requestPermissions():发起权限申请,会弹出对话框
-
ActivityCompat.shouldShowRequestPermissionRationale(): 检查用户是否已经永久拒绝,如果用户已永久拒绝某个权限的申请,即便再调用ActivityCompat.requestPermissions,系统也不会弹框向用户申请了。此时需要自己弹对话框,引导用户去开启授权
-
onRequestPermissionsResult:处理权限授权的结果
-
完整的权限申请案例
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,"读取通讯录权限被拒绝")
}
}
}