快速入门Kotlin语言基础(入门级别)

一、 Kotlin语言基础知识

a.基础知识—日志工具Log的使用

1、Log.v()打印最琐碎、意义最小的日志信息。对应级别verbose

2、Log.d()打印调试信息,适用于调试程序和分析问题。对应级别debug

3、Log.i()打印重要数据,分析用户行为的数据。对应级别info

4、Log.w()打印警告信息,提示程序可能存在的风险。对应级别warn

5、Log.e()打印错误信息,如程序进入catch语句。对应级别error

级别:verbose<debug<info<warn<error

b.基础知识—判空辅助工具

可为空的类型系统就是在类名后面加上一个“”。比如可为空的整形为:Int?

1、”?.” :当对象不为空时正常调用相应的方法,对象为空时什么都不做
2、“?:”:操作符左右两边都接收一个表达式,若左表达式的结果不为空就返回左边表达式的结果,否则返回右边表达式的结果。
       

3、有时候编写的代码会存在空指针风险,及时是已经做了非空检查,但是扔无法编译通过,若想强行通过编译,可以使用非空断言工具,写法是在对象后面加上”!!” 。就是告诉Kotlin,我非常确信这里的对象不会为空,不用帮我做空指针检查,如果出现问题,直接抛出空指针异常。

4、”let”:既不是操作符也不是关键字,而是一个函数,提供了函数式API的编程接口,并将原始调用对象作为参数传递到Lambda表达式中。

调用obj对象的let函数,然后Lambda表达式中的代码会立即执行,并且这个obj对象本身会作为参数传递到Lambda表达式中。为防止变量重名,参数名改成了obj2,实际上他们是同一个对象。

Lambda表达式参数列表中只有一个参数时,可以不声明参数名,直接用it关键字代替即可
     

let函数可以处理全局变量的判空问题,而if判断语句无法做到。

c.基础知识—标准函数with、run、apply

Kotlin中的标准函数指的是Standard.kt文件中定义的函数,可以自由的调用

1、with函数
接收两个参数,参数1可以是一个任意类型的对象,参数2是一个Lambda表达式。with函数会在Lambda表达式中提供第一个参数对象的上下文,并使用Lambda表达式中的最后一行代码作为返回值返回。
2、run函数
run函数一般不会直接调用,而是要在某个对象的基础上调用。run函数只接收一个Lambda参数,并且会在Lambda表达式中提供调用对象的上下文,其他和with函数相同。
3、apply函数
要在某个对象上调用,只接收一个Lambda函数,也会在Lambda表达式中提供调用对象的上下文,但是apply函数无法指定返回值,而是会自动返回调用对象本身。

下面举个例子哈:
1、在这里插入图片描述      2、在这里插入图片描述
3、在这里插入图片描述      4、在这里插入图片描述
上面四种运行方式的结果最终都是相同的,结果如下:
在这里插入图片描述

二、Activity

A:初识Activity

定义:在Android开发中,Activity 是一个非常重要的组件,它是应用程序与用户交互的界面。每个Activity 代表一个单独的屏幕,用户可以在这个屏幕上进行操作和查看信息。

功能:
1、用户界面(UI)。 Activity 提供了一个窗口,用于展示用户界面。
2、生命周期管理。创建、运行、暂停、恢复、停止和销毁等状态。
3、事件处理。 Activity 可以接收并响应用户的输入事件,如点击、触摸、键盘输入等。
4、数据传递。Activity 可以通过Intent进行Activity之间的通信和数据传递。

B:Toast

简介:Toast用来进行页面文本提示,显示一段时间后自行消失,可以给用户进行通知。

用法:
在这里插入图片描述
参数1:Context。是Toast的上下文,Activity本身就是一个Context对象。

参数2:text,就是要弹出的文本内容。

参数3:Toast显示的时间长短,有Toast.LENGTH_LONG和Toast.LENGTH_SHORT可选。

很多初学者很容易忘记.show()的使用,将文本显示出来。

在这里插入图片描述

C:Menu菜单栏

简介:我们在手机的右上角经常看到的3个小点点,就是我们这里所要介绍的Menu,点击三个点后,就可以看到里面所显示的内容。

用法:在res目录下新建menu文件夹,并创建名为‘main’的menu文件
在这里插入图片描述
<item>标签用于创建具体的菜单项

在Activity中重写方法,实现Activity菜单的创建

在这里插入图片描述
         在这里插入图片描述        在这里插入图片描述
inflate()第一个参数指定通过哪个资源文件创建菜单,第二个参数指定将菜单项添加到哪一个Menu

定义出来的菜单还需要实现菜单的响应事件
那么可以在MainActivity中重写onOptionsItemSelected()方法实现点击监听

在这里插入图片描述
item.itemId时间上就是调用的item的getItemId()方法--------语法糖

D:Activity跳转—Intent

方法一:显示Intent

所谓显示Intent就是在Activity中通过Intent构造函数进行参数传递实现。
在这里插入图片描述
Tips:在获取button2这个按钮的时候并没有通过findViewById(R.id.xxx),而是通过视图绑定的方法进行完成的。这的前提是需要在build.gradle(:app)中提前进行配置。并在Activity中进行Layout文件的绑定。
在这里插入图片描述

方法二:隐式Intent

隐,就是很含蓄,不会明确指出跳转到哪个Activity,而是在AndroidManifest进行配置。
在这里插入图片描述
注:只有和中的内容同时匹配Intent中的action和category,Activity才会响应Intent
注:每个Intent只能有一个action但是可以有多个category
在这里插入图片描述

Activity跳转—隐式Intent扩展
1、网页跳转

在这里插入图片描述
Intent的action是Android系统内置的动作Intent.ACTION_VIEW。
Uri.parse将字符串解析成一个Uri对象。
最后通过Intent的setData()方法将Uri传入。

Tips:可以在标签中配置一个标签,准确的指定当前Activity能够响应的数据。
①android:scheme 指定数据的协议部分,如https
②android:host 指定数据的主机名部分,如www.baidu.com
③android:port 指定数据的端口部分
④android:path 指定主机名和端口之后的部分
⑤android:mimeType 指定可以处理的数据类型
举例:<data android:scheme:https />

2、拨打电话

在这里插入图片描述

3、也可以通过geo显示地理位置

在这里插入图片描述
也可以进行发送邮件,总而言之,隐式Intent的功能是很强大的。

向下一个Activity传递数据

思路:Intent中又一系列的putExtra()方法的重载,可以先将数据暂存在Intent中,在启动另一个Activity的时候将数据取出即可。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

返回数据给上一个Activity

问:返回上一个Activity只需按下Back键,但并没有用于启动Activity的Intent来传递数据,这该怎么给上一个Activity返回数据呢?
答:其实Activity类还有一个用于启动Activity的startActivityForResult()方法,它期望Activity销毁的时候返回一个结果给上一个Activity。
在这里插入图片描述
参数1:Intent
参数2:请求码,用于之后回调中判断数据的来源
在这里插入图片描述
setResult方法专门用于向上一个Activity返回数据
在这里插入图片描述
在这里插入图片描述
问:如果不点击button按钮返回到上一个Activity,直接按下Back键,这样数据还能返回吗????
答:自然是不可以的。但是可以在Activity中重写onBackPressed()方法解决这个问题。
在这里插入图片描述
方法还是那个方法,只是重写了onBackPressed()方法进行响应按下Back键这个动作。

E:Activity最佳启动方法

假设在启动SecondActivity时需要两个非常重要的参数,习惯性在MainActivity的写法如下:
在这里插入图片描述
这么写确实没毛病,但是在项目对接中容易不清楚传递哪些数据,是很麻烦的。那么换一种方法,在SecondActivity中添加如下代码:
在这里插入图片描述
Kotlin规定,所有定义在companion object中的方法都可以使用类似于Java静态方法形式的调用。

在MainActivity中只需要一行代码就可以启动SecondActivity并传递参数
在这里插入图片描述

F:Activity生命周期—状态和生存期

Android中的Activity是可以层叠的,每次启动一个Activity就会覆盖在原来的Activity之上,按下Back键就会销毁最上面的活动,这是通过栈来实现的。

在这里插入图片描述

每个Activity最多有4种状态:运行状态、暂停状态(仍可见)、停止状态、销毁状态
Activity中有7个回调方法:
1、onCreate()  会在Activity第一次被创建的时候调用,常用于初始化、加载布局、绑定事件
2、onStart()     Activity由不可见变为可见时调用
3、onResume()  Activity准备好和用户交互时调用,此时Activity一定处于栈顶  
4、onPause()  在系统准备去启动或者恢复另一个Activity的时候进行调用
5、onStop()   Activity完全不可见的时候进行调用(停止状态)
6、onDeatory()  Activity在被销毁之前进行调用
7、onRestart()  Activity在重新启动的时候(停止-->运行状态)之前调用

完整生存期:onCreate() 与onDeatory() 之间
可见生存期:onStart()与onStop() 之间
前台生存期:onResume()与onPause() 之间

为更好理解Activity的生命周期,下面提供了示意图
在这里插入图片描述
下面进行体验一下Activit生命周期在操作器件到底是怎么变化的!!!
条件:创建两个Activity,分别是NormalActivity(普通)和DialogActivity(对话框)

在AndroidManifest.xml文件下进行配置,将Activity设置为对话框模式。
在这里插入图片描述
在MainActivity中设置页面跳转逻辑,并重写生命活动的方法
在这里插入图片描述    在这里插入图片描述
结果分析:

1、第一次创建MainActivity
在这里插入图片描述
2、启动NormalActivity
在这里插入图片描述
3、back
在这里插入图片描述
4、启动DialogActivity
在这里插入图片描述
5、back
在这里插入图片描述
6、back
在这里插入图片描述

G:Activity被回收了怎么办?????

我们知道啊,当Activity进入了停止状态,如果内存空间不足的话,是有可能被系统回收的,那这种现象是有些可怕的!那怎么办呢?

Activity中提供了一个onSaveInstanceState()回调方法,会携带一个Bundle类型的参数,Bundle提供了一系列的方法用于保存数据,可以保证在Activity在回收之前一定会被调用。

在MainActivity中添加下边的代码,就可以将数据保存下来:
在这里插入图片描述
怎么取值恢复呢???
其实onCreate()方法也有一个Bundle类型的参数,一般是null,但是Activity被系统回收之前,通过onSaveInstanceState()方法保存数据,这个参数就会带有之前保存的全部数据。
在MainActivity中添加下面代码,取出值后再做相应的恢复就行了。
在这里插入图片描述

H:Activity的启动模式

1、standard

这就是Activity默认的启动模式,每启动一个Activity就会放入栈顶,不管栈中是否存在该Activity,都会创建一个新的该Activity的实例。

比如,我们在MainActivity中编写如下代码,进行启动MainActivity本身(无实际意义)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2、singleTop

在启动Activity时如果发现返回栈的栈顶已经是该Activity,则可以直接使用,是不需要再重新创建新的Activity实例的。

首先需要对AndroidManifest.xml文件进行修改

在这里插入图片描述
重新运行程序,并多次点击按钮,发现不会创建新的实例对象啦。
在这里插入图片描述

那如果MainActivity并没有在栈顶的位置,再次启动MainActivity会创建新的实例的。
例:修改MainActivity在这里插入图片描述
修改SecondActivity

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3、singleTask

对于singleTop而已,如果Activity没有处于栈顶,还是可能会创建多个Activity实例的,而singleTask模式就可以做到让某个Activity在整个应用程序的上下文只有一个实例。

其实原理很简单,就是每次启动该Activity时,系统先去栈中检查是否存在该Activity的实例,如果存在,就将该Activity之上的所有Activity全部出栈,不存在就创建。

首先还是修改AndroidManifest.xml文件:
在这里插入图片描述
在MainActivity中重写onRestart()方法
在SecondActivity中重写onDestroy()方法
     在这里插入图片描述       在这里插入图片描述

4、singleInstance

指定为singleInstance模式的Activity会启动一个新的、单独的返回栈来管理这个Activity

可以将SecondActivity启动模式设置为singleInstance模式(修改.xml文件,不多说了)

修改一下MainActivity中的代码,进行打印当前返回栈的id:
在这里插入图片描述
(上述taskId实际上是调用的父类的getTaskID()方法)
同样的方法,在SecondActivity、ThirdActivity中打印taskId(返回栈的id),并实现MainActivity—>SecondActivity—>ThirdActivity的页面跳转逻辑:
在这里插入图片描述
在这里插入图片描述

三、常用控件以及基本布局

1、常用控件

(1)、TextView

很简单,就是在页面上显示文字信息。
在这里插入图片描述
id:唯一标识
layout_width:宽
layout_height:高
gravity:指定文字的对齐方式,可选值有top、bottom、start、end、center等,可以用“|”同时指定多个值。center表示效果等同于center_vertical|center_horizon
textColor:文字颜色
textSize:文字大小,单位用sp表示
text:文本内容

在这里插入图片描述

(2)、Button

在这里插入图片描述
textAllCaps:Button按钮文本中的英文默认大写显示,false表示保留原始文字内容。
在这里插入图片描述

Button按钮的监听方式:
①在MainActivity中为Button按钮注册监听器
在这里插入图片描述
②使用实现接口的方式进行注册
在这里插入图片描述

(3)、EditText

允许用户输入信息的文本框。
在这里插入图片描述
hint:输入框中的提示文字,只要输入内容就会消失。
maxLines:指定EditText的最大行数,输入的内容超过行数时,文本就会向上滚动,EditText就不会再继续拉伸。

在这里插入图片描述

还可以与Button按钮联动,获取输入框中输入的内容。
在这里插入图片描述调用toString()方法将获取的内容转换成字符串、

(4)、ImageView

用于在页面上展示图片。
               在这里插入图片描述            在这里插入图片描述

此外,与Button按钮进行联动可以动态的修改图片资源
在这里插入图片描述

(5)、ProgressBar

用于在页面上显示一个进度条,表示程序正在加载数据。
在这里插入图片描述怎么让旋转的进度条消失呢?用到一个所有Android控件都有的一个属性android:visibility,可选值有visible、invisible、gone三种,默认是visible,invisible表示不可见但仍占据页面空间,gone表示不可见而且不占据屏幕的任何空间。

Button按钮与进度条显隐联动:
在这里插入图片描述

将圆形进度条更改为水平进度条,通过max属性还可以动态的更改进度条的进度:
在这里插入图片描述

(6)、AlertDialog

在当前页面弹出对话框,置于所有页面元素之上,能屏蔽其他组件的交互:
在这里插入图片描述       在这里插入图片描述

2、基本布局

(1)、LinearLayout

看名字就知道是线性布局
在这里插入图片描述这个属性很简单,值为vertical则控件垂直排列,为horizontal则控件水平排列。
但是有点需要注意LinearLayout排列方向是vertical时,内部的控件就绝对不能将高度设置为match_parent,horizontal时不能将宽度设为match_parent。

android:layout_gravity属性指定控件在布局中的对齐方式。
注意:当LinearLayout是horizontal时只有垂直方向上的对齐方式才会生效,当LinearLayout是vertical时只有水平方向上的对齐方式才会生效

LinearLayout还有一个重要的属性-----------android:layout_weight
允许我们使用比例的方式指定控件的大小(在手机屏幕适配性方面)
在这里插入图片描述

(2)、RelativeActivity

看名字就知道是相对布局,比较随意排列
在这里插入图片描述
每个控件都是相对于父布局进行的,下面演示相对于控件进行定位:
在这里插入图片描述
android:layout_alignLeft表示让一个控件与另一个控件的左边缘对齐

同理还有:
android:layout_alignRight
android:layout_alignTop
android:layout_alignBottom

(3)、FrameLayout

帧布局,所有控件默认摆放在布局的左上角,全都堆放在一个角落

这是默认效果,可以使用layout_gravity指定控件在布局中的对齐方式。

FrameLayout由于定位欠缺,用的很少,就不多讲。

3、Android控件不够用?创建自定义布局!!!

1、引入布局

在layout文件夹下有一个title.xml布局文件。        在这里插入图片描述

怎么引入布局呢???        只需一条语句:
在这里插入图片描述
可以将系统默认的标题栏隐藏:
在这里插入图片描述

2、创建自定义控件

引入布局确实方便,但是当控件需要响应事件的时候就需要自定义控件啦
在这里插入图片描述
在这里插入图片描述

4、Android最常用和最难用的控件—ListView

在这里插入图片描述
集合的数据无法直接传递给ListView,需要用到适配器完成。
我们这里所引用的子项布局,是Android内置的布局文件,里面只有一个TextView用于显示一段文本。

适配器中传递的三个参数分别是:Activity实例、ListView子项布局id、数据源。

只显示文本是不是有点太单调了呢??我们来点稍微复杂的。

用实体类作为ListView适配器的适配类型:
在这里插入图片描述
为ListView的子项指定一个自定义布局
在这里插入图片描述
创建一个自定义适配器FruitAdapter,适配器继承自ArrayAdapter,并将泛型指定为Fruit类
在这里插入图片描述
接下来在MainActivity创建并添加适配器:
在这里插入图片描述



提升ListView的运行效率:

1、在FruitAdapter的getView()方法中,每次都将布局重新加载一遍,效率很低。这可以通过convertView参数优化,该参数用于将之前加载好的布局进行缓存,以便之后重用。
在这里插入图片描述
问题:如果使用该参数进行优化的话,似乎binding在这个地方是不可以使用的,因为binding接收到的是FruitItemBinding而convertView接收到的是View

2、每次在getView方法中仍会调用View的findViewById()方法来获取一次控件的实例。
可以借助ViewHolder对这部分进行性能优化。
在这里插入图片描述
这样所有控件的实例都缓存在了ViewHolder中,不需要每次通过findViewById()方法获取控件实例了

5、更强大的滚动部件—RecyclerView

适配器创建好之后,就可以开始使用RecyclerView了,修改MainActivity:
在这里插入图片描述

RecyclerView—实现横向滚动

ListView做不到的事情,那就交给俺RecyclerView来做吧。

首先需要对子项布局fruit_item.xml文件进行修改,让其垂直排列。
在这里插入图片描述
然后只需要在MainActivity中添加一行代码即可让控件横向排列
在这里插入图片描述
在这里插入图片描述

RecyclerView内置布局—StaggeredGirdLayoutManager瀑布流布局

还是先修改fruit_item.xml文件,瀑布流应根据布局的列数自动式自动适配,而不应是固定值
在这里插入图片描述
再在MainActivity中将布局形式改成瀑布流
在这里插入图片描述
                        在这里插入图片描述                             在这里插入图片描述

RecyclerView的点击事件

RecyclerView需要我们给子项具体的View去注册点击事件,更加方便为子项任意控件和布局添加点击事件。这样的话仅需要对适配器进行更改,在适配器中添加相应的点击事件即可。
在这里插入图片描述
上述代码分别为最外层布局和ImageView都注册了点击事件,先获取用户点击的position,再通过position拿到相应的Fruit实例。

这样就实现了点击图像和文本时有不同的响应动作。

四、广播

1、广播简介

Android中的广播机制是一种允许应用程序组件之间进行通信的方式。广播可以被发送和接收,并且可以携带信息。这些消息可以是系统事件(如屏幕关闭或开机)或应用程序定义的事件。广播可以被其他组件接收并响应,从而实现组件之间的通信和数据共享。

广播主要可以分为两种:标准广播、有序广播

在这里插入图片描述
标准:几乎同时收到这条广播信息
有序:同一时刻只有一个BroadcastReceiver能收到广播信息,并且可以截断广播。

2、接收系统广播----------动态注册监听时间变化

BroadcastReceiver的创建方法:只需创建一个类,让其继承BroadcastReceiver并重写父类的onReceive()就行了,有广播到来时,onReceive()方法就会得到执行。
在这里插入图片描述
<Android SDK>/platforms/api版本/data/broadcast_actions.txt该路径下可以查看各个广播的字符串action

3、接收系统广播—静态注册实现开机启动

动态注册有一个缺点,必须在程序启动之后才能接收广播,所以注册的逻辑写在onCreate()中。在Android8.0后隐式广播都不允许使用静态注册的方法进行接收。

原本是通过创建内部类创建的BroadcastReceiver,还可以通过studio快捷创建new->Other

在这里插入图片描述
静态的BroadcastReceiver必须在AndroidManifest.xml文件中(标签内)进行注册才能够使用。

在这里插入图片描述
enabled:是否启用这个BroadcastReceiver
exported:是否允许BroadcastReceive接收本程序以外的广播
在标签中声明相应的action

为保护用户设备的安全和隐私,对于用户说是一些比较敏感的操作。必须在AndroidManifest.xml文件中(<application>标签外)进行权限的声明。
在这里插入图片描述

3、发送自定义广播—标准广播

发送广播之前,先定义一个BroadcastReceiver来接收广播,不然发出去也是白发。
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
静态注册的BroadcastReceiver是无法接收隐式广播的。而我们发送的自定义广播往往都是隐式广播。因此一定要使用setPackage()方法来指定这条广播室发送给哪个应用程序的,从而让其变成一条显示广播。

4、发送自定义广播—有序广播

在这里插入图片描述

在这里插入图片描述

点击按钮后,会弹出两次信息,因为在MyBroadcastReceiver和OtherBroadcastReceiver中都接收到了发送的广播,但现在发送的仍然是标准广播。

修改MainActivity,让其发送的广播变成有序广播:
在这里插入图片描述
参数1:仍是Intent
参数2:权限相关,直接传null

现在已经是有序广播了,但是看起来似乎和标准广播没有区别,这是因为有序广播还没有进行广播截断。

如何设定BroadcastReceiver的先后顺序呢???必然是在注册的时候设置优先级!!!
在这里插入图片描述
优先级越高就先越先收到广播

在这里插入图片描述

onReceive()方法中调用的abortBroadcast()方法表示将广播截断。

再次点击发送按钮,只有MyBroadcastReceiver中能够Toast出信息。

5、广播的最佳实践—实现强制下线功能

账号在别的设备上登录时,经常会出现强制下线。思路很简单,就是在页面上弹出一个对话框,用户无法取消对话框,只能点击对话框中的“确认”按钮,强制返回到登录页面即可。

强制下线功能需要先关闭所有的Activity,然后回到登录页面。

先创建一个类用来管理所有的Activity:
在这里插入图片描述

然后创建一个类作为所有Activity的父类:
在这里插入图片描述
先做一个简单的模拟登录的功能,创建一个LoginActivity让其继承BaseActivity:
在这里插入图片描述
MainActivity就是登录成功后的主页面,主页面不需要什么功能,只需要加入一个强制下线功能即可。

在MainActivity中,通过监听点击按钮事件用于触发强制下线功能。
在这里插入图片描述

可以发现强制用户下线的逻辑并没有写在MainActivity中,而是应该写在接收这条广播的BroadcastReceiver中。这样强制下线的功能就不会依附于任何页面,不管是在程序的任何地方,只要发出这样的一条广播,都可以完成强制下线的功能。

BroadcastReceiver中需要弹出一个对话框来阻止用户操作,但如果创建一个静态注册的BroadcastReceiver是没有办法在onReceive()方法中弹出对话框这样的UI控件,而显然也不可能在每个Activity中注册一个动态的BroadcastReceiver,所以该怎么办呢???????

只需要在BaseActivity(所有Activity的父类)中动态注册一个BroadcastReceiver,代码如下:

在这里插入图片描述
在这里插入图片描述
发现了吗,注册和取消注册原本是写在onCreate和onDestory中的,但是现在写在了onResume和onPause中,这是因为我们始终需要保证只有处于栈顶的Activity才能接收到这条强制下线广播,非栈顶的Activity不应该也没必要接收这条广播。

自此,强制下线的逻辑已经完成,下面需要在AndroidManifest.xml中修改主启动Activity
在这里插入图片描述


在这里插入图片描述

五、通知

1、使用通知

发出通知后,手机状态栏会有一个通知的图标,下拉状态栏可以看到详细的内容。

Android8.0后引入了通知渠道的概念,就是每条通知都要属于一个对应的渠道。但是通知渠道的控制权在用户的手中,而且通知渠道一旦创建,开发者就无法修改。

在这里插入图片描述

需要一个NotificationManager对通知进行管理,可以调用Context的getSystemService()方法获取,方法接收 一个字符串用于确定获取系统的哪个服务。

然后使用NotificationChannel类构建一个通知渠道,通过调用NotificationManager的createNotificationChannel()方法完成创建。

该类和方法都是Android8.0新增的API,故要进行版本判断

通知渠道的创建至少需要渠道ID、渠道名称、重要等级这3个参数。渠道ID可随意定义但要保证唯一。渠道名称是展示给用户的。重要等级主要有IMPORTANCE_HIGH、IMPORTANCE_DEFAULT、IMPORTANCE_LOW、IMPORTANCE_MIN,重要程度由高到低。

创建通知首先需要 一个Builder构造器来创建Notification对象,未解决Android系统API不稳定的问题,使用AndroidX库中提供的NotificationCompat类创建Notification对象。Builder()构造函数中接收两个参数,参数1是Context上下文,参数2是渠道ID,需要和在创建通知渠道时指定的渠道ID相匹配才可以。在最终的build()方法之前还可以连缀任意多的设置方法丰富Notification对象。

在所有工作完成之后,只需调用NotificationManager的notify()方法就能通知显示出来。该方法接收两个参数,参数1是id,要保证每个通知的id是不同的,参数2是一个Notification对象。
在这里插入图片描述
此时的通知是不能点击的,想要实现点击效果,需要借助与Intent相似的PendingIntent

PendingIntent和Intent都可以用于启动Activity、启动Service、发送广播。不同的是Intent倾向于立即执行执行某个动作,而PendingIntent倾向于在某个合适的时机执行某个动作。

PendingIntent提供了几个静态方法用于获取该实例,根据情况选择使用getActivity()、getBroadcase()、getService()方法,这些方法都是接收4个参数,参数1为Context上下文,参数2是一般用不到,直接传入0,参数3是一个Intent对象,可以通过这个对象构造出PendingIntent的意图。参数4用于确定PendingIntent的行为,有FLAG_ONE_SHOT、FLAG_NO_CRETE、FLAG_CANCEL_CURRENT、FLAG_UPDATE_CURRENT四个值可选,通常直接传入0

NotificationCompat.Builder这个构造器还可以连缀一个setContentIntent()方法,接收的参数正好是一个PendingIntent对象,因此构造出一个延迟执行的意图,当用户点击通知的时候就可以启动NotificationActivity。

修改MainActivity代码:
在这里插入图片描述

点击按钮跳转后,状态栏上的通知图标还没有消失,这显然是不是很合理的额,解决方法有两种,一种就是在NotificationCompat.Build中再连缀一个setAutoCancel()方法,另一种是显式的调用NotificationManager的cancel()方法进行取消。

在这里插入图片描述

2、通知的进阶技巧

NotificationCompat.Build中有很丰富的API,可以创造出多样的通知效果。

setStyle()方法允许我们构造出富文本的通知内容,该方法接收一个NotificationCompat.Style参数,该参数用来构建富文本信息,如长文字、图片等。

在通知中如果信息文本太长,则不能全部加载完全,这时就可以借助setStyle()方法了。

在这里插入图片描述
除了显示长文字以外,还可以显示一张大图片:
在这里插入图片描述
通过BitmapFactory的decodeResource()方法将图片解析成Bitmap对象,再传入bigPicture()方法中。

不同重要等级的通知渠道对通知的行为有不同的影响。等级越高越容易引起用户注意。

Tips:开发者只能在创建通知渠道的时候指定初识的重要等级,用户可以随时修改,但开发者无权调整修改(不可以通过代码修改)。

在这里插入图片描述
在这里插入图片描述

六、Service

Service是什么?????

Service是Android中实现程序后台运行的解决方案,非常适合执行那些不需要和用户交互而且还要求长期运行的任务。Service的运行不依赖任何用户页面,而且Service并不是运行在一个独立的进程当中的,而是依赖于创建Service时所在的应用程序进程,当某个应用进程被杀掉时,所以依赖于该进程的Service也会停止。

Service不会自动开启线程,所有的代码都是默认运行在主线程当中的,所以我们需要在Service内部手动创建子线程,并在这里面执行具体的任务,否则就有可能会出现主线程被阻塞的情况。

1、Android多线程编程—线程基本用法

a、继承的方式(耦合较高,不推荐)
在这里插入图片描述

b、Runnable接口定义一个线程
在这里插入图片描述

c、如果不想专门定义一个类去实现Runnable接口, 可以使用Lambda方式
在这里插入图片描述

d、otlin还提供了一种更加简单的开启线程的方式,这里的thread是一个Kotlin内置的顶层函数。
在这里插入图片描述

2、Android多线程编程—在子线程中更新UI

Android 的UI 是线程不安全的, 更新应用程序的UI元素, 必须在主线程中进行, 否则会出现异常。如果在子线程中直接更新UI,会出现崩溃。

在这里插入图片描述
在这里插入图片描述
问:Android不允许在子线程中进行UI操作,但有时我们必须在子线程中执行一些耗时的任务,然后跟任务的执行结果来更新相应的UI控件,这该怎么办????????

答:Android中提供了一套异步消息处理机制,完美解决了在子线程中进行UI操作的问题。
在这里插入图片描述
这并没有在子线程中直接进行UI操作,而是创建了一个Message对象进行操作的。先指定Message的what字段的值,然后调用Handler的sendMessage方法将Message发送出去。很快Handle就会收到这条Message并在handleMessage()中进行处理。

由于Handler的构造函数中传入了Looper.getMainLooper(),此时handleMessage()方法中的代码就是在主线程中运行的了,所以可以放心的进行UI操作。

3、Android多线程编程—解析异步消息处理机制

Android中的异步消息主要由4部分组成:Message、Handler、MessageQueue、Looper。

1、Message:在线程之间传递消息,可以携带少量的信息进行线程之间传递数据。what、arg1、arg2字段可以携带少量的整形数据,obj字段可以携带一个Object对象。

2、Handler:用于发送和处理消息,发送消息使用Handler的sendMessage()或post()方法等。
发出的消息会传递到Handler的handleMessage()方法中。

3、MessageQueue:消息队列,用于存放所有通过Handler发送的信息,在消息队列中等待被处理,每个线程只有一个MessageQueue对象。

4、Looper:每个线程中MessageQueue的管家,调用Looper的loop()方法后就会进入到一个无限循环中,每当发现MessageQueue中存在消息时就将其取出并传递到handleMessage()方法中,每个线程只有一个Looper对象。

4、Android多线程编程—使用AsyncTask

AsyncTask背后的实现原理也是基于异步消息处理机制的额,只是Android做了很好的封装。

AsyncTask是一个抽象类,要使用它就必须要创建一个类去继承它,在继承时可以为其指定3个泛型参数:
1、Params。在执行AsyncTask时需要传入的参数,可用于在后台显示。
2、Progress。在后台任务执行时,若需要在页面上显示当前的进度,则使用这里指定的泛型作为进度单位。
3、Result。任务执行完毕后,若要对结果进行返回,则使用这里指定的泛型作为返回值类型
class DownloadTask :AsyncTask<Unit, Int, Boolean> () {

}
当前是一个空任务,无任何实际操作,需要重写4个方法:
1 onPreExecute() 在任务执行前调用,用于初始化操作
2 doInBackground(Params…) 在子线程中执行, 执行具体耗时任务
3 onProgressUpdate(Progress…) 后台任务调用,进行UI操作
4 onPostExecute(Result) 后台任务执行完毕并通过return返回时, 收尾工作

要启动这个任务,DownloadTask().execute(),也可以为execute()方法传入任意数量的参数,这些参数会传递到doInBackground()中。

5、Service基本用法----定义一个Service

在这里插入图片描述
定义了MyService,继承于Service,onBind()方法是Service中唯一的抽象方法,必须在子类中实现。

onCreate()方法会在Service创建的时候调用,
onStartCommand()方法会在每次Service启动的时候调用,
onDestroy()方法会在Service销毁的时候调用。

注:每个Service都需要在AndroidManifest.xml文件中进行注册(<application>标签内注册)才能生效
在这里插入图片描述

6、Service基本用法----启动和停止Service

Service的启动和停止主要是借助Intent实现的。

在这里插入图片描述
stratService()和stopService()都是定义在Context类中的,可以直接在Activity中直接调用。另外,Service也可以自我停止,只需在Service内部调用stopSelf()即可。

在这里插入图片描述

7、Service基本用法----Activity与Service进行通信

虽然Service是在Activity中启动的,但是点那个Service启动后,Activity与Service好像就没什么关系了,Service一直处于运行状态,具体运行的什么逻辑,Activity控制不了。

借助Service中的唯一抽象方法onBind()可以使其关系更加紧密。

比如说,希望在MyService里提供一个下载功能,然后在Activity中可以决定何时开始下载,以及随时查看下载进度。实现这个功能的思路是创建一个专门的Binder对象来对下载功能进行管理。修改MyService中的代码,如下所示:
在这里插入图片描述
1、创建了一个DownloadBinder类并继承于Binder类,内部提供了开始下载和查看下载进度两个模拟方法。
2、在MyService中创建了DownloadBinder实例
3、在onBind()方法中返回了实例

当一个Activity与Service绑定了之后,就可以调用该Service中的Binder提供的方法了。

在这里插入图片描述
1、创建了ServiceConnection匿名类实例并重写了方法。
onServiceConnected()方法会在Activity与Service成功绑定的时候调用,onServiceDisconnected()方法只有在Service的创建进程崩溃或者被杀掉的时候才会调用。
2、通过向下转型得到了DownloadBinder实例。关系变的非常亲密。

现在就可以在Activity中根据具体的情景调用DownloadBinder中任何public方法,即实现了指挥Service干什么Service就去干什么,这里只是做了测试。

bindService()方法将Activity与Service进行绑定,该方法接收3个参数
参数1为Intent对象。参数2为创建出的ServiceConnection实例。
参数3是一个标志位,传入BIND_AUTO_CREATE表示绑定后自动创建
Service,这会使得MyService的onCreate()执行而onStartCommand()不会执行

点击Activity与Service绑定按钮:
在这里插入图片描述
startDownload和getProgress方法都得到了执行,说明Activity成功调用了Service中的方法。

点击Activity与Service解绑按钮:
在这里插入图片描述
注:任何一个Service在整个应用程序范围内都是通用的,即MyService可以与任何一个Activity进行绑定,而且在绑定完成后,它们都可以获取相同的DownloadBInder实例。

8、Service的更多技巧----使用前台Service

只有应用保持在前台可见状态下,Service才能保证稳定运行,一旦应用进入后台之后,Service就随时有可能被系统回收。

前台Service和普通Service最大的区别就在于,它会一直有一个正在运行的图标在系统的状态栏显示,下拉状态栏后可以看到更加详细的信息,非常类似于通知的效果。

由于状态栏中一直有一个正在运行的图标,相当于我们的应用以另外一种形式保持在前台可见状态,所以系统不会倾向于回收前台Service。

修改MyService中的onCreate()方法:
在这里插入图片描述
这其实就是前面所讲的通知的设置,只不过这次在构建Notification对象后没有使用NotificationManager将通知显示出来,而是调用了startForeground()方法。参数1是通知的id,参数2是构建的Notification对象。调用startForeground()方法后就会让MyService变成一个前台Service,并在状态栏显示出来。

使用前台Service必须在AndroidManifest.xml文件中进行权限声明才行:

在这里插入图片描述

9、Service的更多技巧----使用IntentService

Service中的代码都是默认运行在主线程当中的,如果直接在Service中处理一些耗时的逻辑,就很容易出现ANR(Application Not Responding)。

所以就需要用到Android多线程编程技术,应该在Service的每个具体的方法里开启一个子线程,在这里处理耗时的逻辑。

故:一个标准的Service就可以写成如下形式:(仅展示MyService的一个方法)

在这里插入图片描述
Service一旦启动就会一直处于运行状态,所以必须调用stopService()或stopSelf()方法或者被系统回收,Service才会停止。

虽然写法比较简单,但是开启线程或者调用stopSelf()都是比较容易遗忘的。
Android提供了一个IntentService类,可以很好的解决这种问题。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
IntentService有个特性就是Service在运行结束后应该是会自动停止的。日志打印也验证了。

Tip:记得去AndroidManifest.xml文件中对Service进行注册。

在这里插入图片描述

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐