第 6 章 广播
6.1 广播机制
类型:
- 标准广播:广播发出所有的接收器同时接收
- 有序广播:广播发出后,按照优先级先后接收,先接收的可以截断广播
6.2 接收系统广播
如:开机广播、电量变化广播、系统时间变化广播
6.2.1 动态注册监听时间变化
class MainActivity : AppCompatActivity() {
lateinit var timeChangeReceiver: BroadcastReceiver
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val intentFilter = IntentFilter()
intentFilter.addAction("android.intent.action.TIME_TICK")//设置广播接收的类型
timeChangeReceiver = TimeChangeReceiver()
registerReceiver(timeChangeReceiver,intentFilter)
}
override fun onDestroy() {
super.onDestroy()
unregisterReceiver(timeChangeReceiver)
}
inner class TimeChangeReceiver : BroadcastReceiver() {
override fun onReceive(p0: Context?, p1: Intent?) {
Toast.makeText(p0, "Time has changed", Toast.LENGTH_SHORT).show()
}
}
}
完整的系统广播列表 SDK路径/platforms/<任意版本的 api>/data/broadcast_action.txt
动态注册缺点:应用启动后才能接收
6.2.2 静态注册的广播实现开机
安卓8.0后所有隐式广播不允许使用静态注册的方式来接收,特殊类型的广播除外
- new - other - BroadcastReceiver
class BootCompleteReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Toast.makeText(context, "Boot Complete", Toast.LENGTH_SHORT).show()
}
}
- 配置清单文件
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"></uses-permission>
<uses-permission android:name="android.permission.RESTART_PACKAGES" />
<receiver
android:name=".BootCompleteReceiver"
android:enabled="true"//表示此广播接收本程序以外的广播
android:exported="true">//表示启用这个广播
<intent-filter
>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
//因为在Android studio3.4.1的程序是安装在sd卡上的,在android.intent.action.BOOT_COMPLETED的广播发送之后,sd卡才被挂载,所以广播接收器接收不到该广播
<intent-filter > //监听sd卡挂载
<action android:name="android.intent.action.MEDIA_MOUNTED"/>
<action android:name="android.intent.action.MEDIA_UNMOUNTED"/>
<data android:scheme="file">
</data>
</intent-filter>
</receiver>
- 重启手机,需要等一会儿
6.3 发送自定义广播
6.3.1 发送标准广播
- new 一个广播
class MyBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Toast.makeText(context, "received in MyBroadcastReceiver", Toast.LENGTH_SHORT).show()
}
}
- 配置清单文件
<receiver
android:name=".MyBroadcastReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="com.example.broadcasttest.MY_BROADCAST"></action>
</intent-filter>
</receiver>
- 发送广播
注:安卓8.0后静态注册的广播无法接收隐式广播,需要传入包名指定特定的程序接收把他变成一条显式广播
补充:可以在intent里传递一些数据
button.setOnClickListener{
val intent = Intent("com.example.broadcasttest.MY_BROADCAST")//指定发送的广播类型
intent.setPackage(packageName)
sendBroadcast(intent)
}
6.3.2 发送有序广播
- new 另一个广播
class AnotherBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Toast.makeText(context, "received in AnotherBroadcastReceiver", Toast.LENGTH_SHORT).show()
}
}
- 配置清单文件,指定接收的广播类型,同上面那个
- 发送有序广播
button.setOnClickListener{
val intent = Intent("com.example.broadcasttest.MY_BROADCAST")
intent.setPackage(packageName)
sendOrderedBroadcast(intent,null)
}
- 设置优先级
<receiver
android:name=".MyBroadcastReceiver"
android:enabled="true"
android:exported="true">
<intent-filter
android:priority="100"
>
<action android:name="com.example.broadcasttest.MY_BROADCAST" />
</intent-filter>
</receiver>
- 截断广播
class MyBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Toast.makeText(context, "received in MyBroadcastReceiver", Toast.LENGTH_SHORT).show()
abortBroadcast()
}
}
注:广播的onReceive方法不能添加过多的逻辑或进行任何耗时操作,因为广播不允许开启线程,当进行太长时间而没有结束就会出错
6.4 广播的最佳实践:强制下线功能
- 关闭所有活动功能创建 ActivityConllector类和BaseActivity类
- 创建登录布局
<?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">
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="60dp">
<TextView
android:layout_width="90dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:textSize="18sp"
android:text="Account:" />
<EditText
android:id="@+id/accountEdit"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_gravity="center_vertical" />
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="60dp">
<TextView
android:layout_width="90dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:textSize="18sp"
android:text="Password:" />
<EditText
android:id="@+id/passwordEdit"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_gravity="center_vertical"
android:inputType="textPassword" />
</LinearLayout>
<Button
android:id="@+id/login"
android:layout_width="200dp"
android:layout_height="60dp"
android:layout_gravity="center_horizontal"
android:text="Login" />
</LinearLayout>
- 登录代码
class LoginActivity : BaseActivity(){
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)
login.setOnClickListener {
val account = accountEdit.text.toString()
val password = passwordEdit.text.toString()
if(account == "admin" && password == "123456"){
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
finish()
}else{
Toast.makeText(this,"account or password is invalid",Toast.LENGTH_SHORT).show()
}
}
}
}
- 修改主活动代码,触发强制下线功能
class MainActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
forceOffline.setOnClickListener {
val intent = Intent("com.example.broadcastbestpractice.FORCE_OFFLINE")
sendBroadcast(intent)
}
}
}
- 修改BaseActivity
open class BaseActivity : AppCompatActivity() {
lateinit var receiver: ForceOfflineReceiver
override fun onResume() {
super.onResume()
val intentFilter = IntentFilter()
intentFilter.addAction("com.example.broadcastbestpractice.FORCE_OFFLINE")
receiver = ForceOfflineReceiver()
registerReceiver(receiver,intentFilter)
}
override fun onPause() {
super.onPause()
unregisterReceiver(receiver)
}
override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
super.onCreate(savedInstanceState, persistentState)
ActivityCollector.addActivity(this)
}
override fun onDestroy() {
super.onDestroy()
ActivityCollector.removeActivity(this)
}
inner class ForceOfflineReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, p1: Intent) {
AlertDialog.Builder(context).apply {
setTitle("Warning")
setMessage("You are forced to be offline. Please try to login again.")
setCancelable(false)
setPositiveButton("OK"){_,_->
ActivityCollector.finishAll()
val i = Intent(context, LoginActivity::class.java)
context.startActivity(i)
}
show()
}
}
}
}
注:如果没有取消注册会报错
- 更换LoginActivity为主活动
6.5 Kotlin 课堂
6.5.1 定义高阶函数:函数的参数是个函数或返回值是个函数,类似于java中的接口
- 函数类型:(String , Int) -> Unit 左边是参数右边是返回值,Unit说明返回值为空
fun num1AndNum2(num1: Int,num2: Int,operation:(Int,Int)->Int):Int{
val result = operation(num1,num2)
return result
}
fun plus(num1: Int, num2: Int):Int {
return num1 + num2
}
fun minus(num1: Int, num2: Int):Int {
return num1 - num2
}
fun main() {
val num1 = 100
val num2 = 80
val result1 = num1AndNum2(num1,num2,::plus)//引用函数的写法 表示获得函数类型的对象
val result2 = num1AndNum2(num1,num2,::minus)
println("result1 is $result1")
println("result2 is $result2")
}
- 利用Lambda优化
- 语义结构:{参数名 1:参数类型,参数名2:参数类型 -> 函数体}
- -> 表示参数列表结束和函数体的开始,函数体最后一行作为返回值
- 当 Lambda 是函数的最后一个参数,可以移到括号外面
- Lambda 有优秀的类型推到机制,所以可以不用写参数类型
- 语义结构:{参数名 1:参数类型,参数名2:参数类型 -> 函数体}
fun num1AndNum2(num1: Int,num2: Int,operation:(Int,Int)->Int):Int{
val result = operation(num1,num2)
return result
}
fun main() {
val num1 = 100
val num2 = 80
val result1 = num1AndNum2(num1,num2){ n1,n2 -> n1+n2}
val result2 = num1AndNum2(num1,num2){ n1,n2 -> n1-n2}
println("result1 is $result1")
println("result2 is $result2")
}
- 利用高阶函数实现一个StringBuilder用apply的功能
fun StringBuilder.build(block: StringBuilder.() -> Unit):StringBuilder{
block()
return this
}
//StringBuilder.表示在这个类里声明的函数,然后调用函数时就会自动拥有StringBuilder的上下文
fun main() {
val list = listOf("Apple","Banana","Orange","Pear","Grape")
val result = StringBuilder().build{
append("Start eating fruits.\n")
for (fruit in list) {
append(fruit).append("\n")
}
append("Ate all fruits.")
}
println(result.toString())
}
缺点:这种写法在编译的时候每个Lambda表达式都会转换为java的接口形式,调用的时候会创建接口的匿名实现,会占用很多空间,下面是解决办法
6.5.2 内联函数的作用
在代码编译的时候自动替换到调用它的地方
在高阶函数前面加上 inline 关键字就行
相当于c++里面的宏定义#define
6.5.3 noinline 与 crossinline 暂时不会
6.6 Git
6.6.1 安装 Git
mac电脑 在安卓Homebrew的基础上,在命令行里敲:brew install git
6.6.2 创建代码仓库
//配置身份
git config --global user.name "自己写"
git config --global user.email "自己写"
//查看是否配置成功
git config --global user.name
git config --global user.email
//创建代码仓库
git init
//然后在这个目录下会自动创建.git文件:用来保存本地所有Git操作
//查看
ls -al
//如果想删除仓库,删除这个目录就行
6.6.3 提交本地代码
//把想提交的代码添加进来
git add 文件名/目录名/.所有文件
//真正执行提交操作
git commit -m "描述信息"