《第一行源代码》第三版第六章(广播)

6.1 广播机制简介

为了便于进行系统级别的消息通知,Android引入了一套广播消息机制。

每个应用程序都可以对自己感兴趣的广播进行注册,这样该程序就只会收到自己所关心的广播内容,这些广播可能是来自于系统的,也可能是来自于其他应用程序的。Android提供了一套完整的API,允许应用程序自由地发送和接收广播。

Android中的广播主要可以分为两种类型:
在这里插入图片描述
在这里插入图片描述

6.2 接收广播系统

1.动态注册监听时间变化

class MainActivity : AppCompatActivity() {
    lateinit var timeChangeReceiver: TimeChangeReceiver

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val intentFilter = IntentFilter()
        intentFilter.addAction("android.intent.action.TIME_TICK")
        //发出想要监听什么的广播    系统时间发生变化时,会发出android.intent.action.TIME_TICK的广播
        //这里的意思就是想要接受android.intent.action.TIME_TICK的广播
        timeChangeReceiver = TimeChangeReceiver()
        registerReceiver(timeChangeReceiver,intentFilter)
        //调用registerReceiver()方法注册 这样timeChangeReceiver
        // 就会收到所有值为android.intent.action.TIME_TICK的广播
    }

    override fun onDestroy() {
        super.onDestroy()
        unregisterReceiver(timeChangeReceiver)
        //动态的BroadcastReceiver一定要取消注册才行
    }
    inner class TimeChangeReceiver:BroadcastReceiver(){     //定义一个内部类继承BroadcastReceiver
        override fun onReceive(context: Context?, intent: Intent?) {    //重写
            Toast.makeText(context,"Time has changed",Toast.LENGTH_SHORT).show()
            //时间发生变化时,使用Toast提示一段文本信息
        }
    }
}

查看完整的系统广播列表:
到SDK路径查看完整的Android系统广播列表:

<Android SDK>/platforms/<任意android api 版本>/ data/ broadcast_actions.txt

动态注册在灵活性方面有很大的优势,但是必须在程序启动后才能收到广播(因为注册的逻辑写在onCreate()方法中) 想未启动之前收到广播就需要静态注册了

2. 静态注册实现开机启动

其实从理论上来说,动态注册能监听到的系统广播,静态注册也应该能监听到,在过去的Android系统中确实是这样的。但是由于大量恶意的应用程序利用这个机制在程序未启动的情况下监听系统广播,从而使任何应用都可以频繁地从后台被唤醒,严重影响了用户手机的电量和性能,因此Android系统几乎每个版本都在削减静态注册BroadcastReceiver的功能。

在Android 8.0系统之后,所有隐式广播都不允许使用静态注册的方式来接收了。隐式广播指的是那些没有具体指定发送给哪个应用程序的广播,大多数系统广播属于隐式广播,但是少数特殊的系统广播目前仍然允许使用静态注册的方式来接收。这些特殊的系统广播列表详见https://developer.android.google.cn/guide/components/broadcast-exceptions

下面我们来看一下实现开机启动的功能:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.broadcastreceiver">

    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
    //
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <receiver
        //如果使用的是快捷方式创建的,as会自动在该文件中注册
            android:name=".BootCompleteReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED"/>
            </intent-filter>
        </receiver>

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>
class BootCompleteReceiver : BroadcastReceiver() {

    override fun onReceive(context: Context, intent: Intent) {
        Toast.makeText(context,"Boot Complete",Toast.LENGTH_LONG).show()
    }
}

Android 系统为了保护用户设备的安全和隐私,做了严格的规定:如果程序需要进行-些对用户来说比较敏感的操作,必须在AndroidManifest.xml文件中进行权限声明,否则程序将会直接崩溃。比如这里接收系统的开机广播就是需要进行权限声明的,所以我们在上述代码中使用<uses. permission>标签声明了android. pe rmission.RECEIVE_ BOOT COMPLETED 权限。
需要注意的是,onReceive()方法中添加过多的逻辑或者进行任何的耗时操作,因为BroadeastReceiver中是j’n’n不允许开启线程的,当onReceive()方法运行了较长时间而没有结束时,程序就会出现错误。

6.3 发送自定义广播

1. 发送标准广播

//创建一个MyBroadcastReceiver
class MyBroadcastReceiver : BroadcastReceiver() {

    override fun onReceive(context: Context, intent: Intent) {
        Toast.makeText(context,"received in MyBroadcastReceiver",Toast.LENGTH_SHORT).show()
    }
}
//在AndroidManif.xml 中添加
<receiver
            android:name=".MyBroadcastReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="com.example.broadcastreceiver.MY_BROADCAST"/>
            </intent-filter>
        </receiver>
//修改activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <Button
        android:id="@+id/button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Send Broadcast"/>
</LinearLayout>
//在MainActivity中onCreate方法中添加
button.setOnClickListener{
            val intent = Intent("com.example.broadcastreceiver.MY_BROADCAST")
            //构建Intent对象,把要发送广播的值传入
            intent.setPackage(packageName)//packageName是getPackageName()的语法糖写法
            sendBroadcast(intent)//将广播发出
        }	

这里对第2步调用的setPackage()方法进行更详细的说明。前面已经说过,在Android8.0 系统之后,静态注册的BroadcastReceiver是无法接收隐式广播的,而默认情况下我们发出的自定义广播恰恰都是隐式广播。因此这里一定要调用setPackage()方法,指定这条广插是发送给哪个应用程序的,从而让它变成一条显式广 播,否则静态注册的BroadcastReceiver将无法接收到这条广播。

2. 发送有序广播

//再创建一个BroadcastReceiver
class AnotherBroadcastReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        Toast.makeText(context,"received in AnotherBroadcastReceiver",Toast.LENGTH_SHORT).show()
    }
}
//在AndroidManif.xml 中添加
<receiver
            android:name=".AnotherBrodcastReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="com.example.broadcastreceiver.MY_BROADCAST"/>
            </intent-filter>
        </receiver>
//在MainActivity中onCreate方法中修改成
button.setOnClickListener{
            val intent = Intent("com.example.broadcastreceiver.MY_BROADCAST")
            intent.setPackage(packageName)
            sendOrderedBroadcast(intent,null)
            //这样BroadcastReceiver就有先后顺序了
        }	

这时可以将广播截断,以阻止继续传播

//在AndroidManif.xml 中修改
<receiver
            android:name=".MyBroadcastReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter android:priority="100">
                <action android:name="com.example.broadcastreceiver.MY_BROADCAST" />
            </intent-filter>
        </receiver>
//最后再MyBroadcastReceiver中添加abortBroadcast()
class MyBroadcastReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        Toast.makeText(context,"received in MyBroadcastReceiver",Toast.LENGTH_SHORT).show()
        abortBroadcast()
    }
}

6.4 广播的最佳实践:实现强制下线功能

需要完整代码:可以去一下链接右下角有随书资源(下载中):
https://www.ituring.com.cn/book/2744
在这里插入图片描述

个人在编写的时候未将MainActivity继承BaseActivity类如下
在这里插入图片描述
最后点击Send force offline broadcast按钮的时候未出现对话框
(点击Send force offline broadcast按钮的时候未出现对话框的时候我是先测试按钮是否可以正常使用(按钮点击事件里面添加一些代码测试),然后再看看对话框代码是否出问题,查看发收广播是否正常,最后发现未将MainActivity继承BaseActivity类)

6.5 Kotlin课堂:高阶函数详解

如果一个函数接收另一个函数作为参数,或者返回值的类型是另一个函数,那么该函数就称为高阶函数。

fun example(func: (String, Int)-> Unit) {	
//->左边声明什么参数(多个加逗号,没有打一对空格),右边声明函数返回值(没用用Unit)
func("hello",123)
}

下面举个例子:

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("result1 is $result2")
}

Kotlin还支持多种其他方式调用高阶函数,比如Lambda表达式,匿名函数,成员应用等
Lambda表达式是最常见的高阶函数调用方式,也是我们重点学习的内容
上述代码用Lambda表达式实现:

1. 定义高阶函数

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("result1 is $result2")
}
fun num1AndNum2(num1:Int,num2:Int,operation:(Int,Int) -> Int):Int{
    val result = operation(num1,num2)
    return result
}

下面为模仿apply的一个高阶函数

fun StringBuilder.build(block:StringBuilder.()-> Unit):StringBuilder{
    block()
    return this
}
fun main(){
    val list = listOf("apple","banana","orange")
    val result = StringBuilder().build {
        append("Start eating fruits.\n")
        for (fruit in list){
            append(fruit).append("\n")
        }
        append("Ate all fruits.")
    }
    println(result.toString())
}

区别是build只能在StringBuilder类上 而apply可以在所有类上
详细请看:Kotlin-高阶函数

2. 内联函数的作用

内联函数可以将使用Lambda表达式带来的运行时开销(每调用一次Lambda表达式,都会创建一个新的实例)完全消除。它的工作原理并不复杂,就是Kotlin编译器会将内联函数中的代码在编译的时候自动替换到调用它的地方,这样也就不存在运行时的开销了。

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

3. noinline 与 crossinline

 fun printString(str:String,block:(String) -> Unit){
    println("printString begin")
    block(str)
    println("printString end")
}

fun main(){
    println("main start")
    val str = ""
    printString(str){ s ->
        println("lambda start")
        if (s.isEmpty())return@printString
        println(s)
        println("lambda end")
    }
    println("main end")
}

返回值如下:
在这里插入图片描述

inline fun printString(str:String,block:(String) -> Unit){
    println("printString begin")
    block(str)
    println("printString end")
}

fun main(){
    println("main start")
    val str = ""
    printString(str){ s ->
        println("lambda start")
        if (s.isEmpty())return
        println(s)
        println("lambda end")
    }
    println("main end")
}

返回值如下:
在这里插入图片描述
绝大多数高阶函数可以直接声明成内联函数,但是也有少部分例外的情况
在这里插入图片描述
出现该错误的原因:
首先,在runRunnable()函数中,我们创建了一个Runnable对象,并在Runnable的Lambda表达式中调用了传人的函数类型参数。而Lambda表达式在编译的时候会被转换成匿名类的实现方式,也就是说,上述代码实际上是在匿名类中调用了传人的函数类型参数。
而内联函数所引用的Lambda表达式允许使用return 关键字进行函数返回,但是由于我们是在匿名类中调用的丽数类型参数,此时是不可能进行外层调用函数返回的,最多只能对匿名类中的函数调用进行返回,因此这里就提示了上述错误。
在这里插入图片描述

使用了crossinline之后,我们就无法调用runRunnable函数时的Lambda表达式中使用return关键字进行函数返回了
crossinline除了在return关键字的使用上有所区别之外,它保留了内联函数的其他所有特性

6.6 Git时间:初始版本控制工具

国外网站下载都比较慢 推荐去一些镜像网站
配置安装得话就看书了
详细请看:https://blog.csdn.net/qq_36150631/article/details/81038485

附录1 Android常见输入inputType类型:

https://blog.csdn.net/zhangphil/article/details/79970667

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值