第六章 广播

448 阅读6分钟

第六章 广播

  1. 广播分类

    1. 标准广播

      异步执行的广播,在广播发出后,所有BroadcastReceiver几乎在同一时刻接收到这条广播。

    2. 有序广播

      同步执行的广播,在广播发出后,同一时刻只会有一个BroadcastReceiver能够接收到这条广播。当这个BroadcastReceiver中的逻辑执行完毕后,广播才会继续传递。

  2. 接收广播

    1. 注册方式

      1. 动态注册,在代码中注册

      2. 静态注册,AndroidManifest.xml中注册

    2. 动态注册

      class TimeChangeReceiverActivity : AppCompatActivity() {
          private lateinit var timeChangedReceiver: TimeChangedReceiver
      
          override fun onCreate(savedInstanceState: Bundle?) {
              super.onCreate(savedInstanceState)
              setContentView(R.layout.activity_time_change_receiver)
              timeChangedReceiver = TimeChangedReceiver()
              val intentFilter = IntentFilter()
              intentFilter.addAction("android.intent.action.TIME_TICK")
              registerReceiver(timeChangedReceiver, intentFilter)
          }
      
          override fun onDestroy() {
              super.onDestroy()
              unregisterReceiver(timeChangedReceiver)
          }
      
          class TimeChangedReceiver : BroadcastReceiver() {
              override fun onReceive(context: Context?, intent: Intent?) {
                  Toast.makeText(context, "Time has changed", Toast.LENGTH_SHORT).show()
              }
          }
      }
      

      ⚠️动态注册的BroadcastReceiver一定要取消注册。否则会内存泄漏。

    3. 静态注册

      Android8.0之后所有隐式广播不允许静态注册。隐式广播是指那些没有具体指定发送给哪个应用程序的广播。

      • New->Other->BroadcastReceiver创建BroadcastReceiver文件。

        class BootCompletedReceiver : BroadcastReceiver() {
        
            override fun onReceive(context: Context, intent: Intent) {
                // This method is called when the BroadcastReceiver is receiving an Intent broadcast.
                TODO("BootCompletedReceiver.onReceive() is not implemented")
            }
        }
        
      • AndroidManifest.xml文件注册

        <manifest xmlns:android="http://schemas.android.com/apk/res/android"
            xmlns:tools="http://schemas.android.com/tools"
            package="com.youngly.firstlineofcode">
            
            <!--   权限声明    -->
            <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/Theme.FirstLineOfCode">
                <!--   enabled 是否启用    exported 是否允许接收本程序外的广播    -->
                <receiver
                    android:name=".chapter6.BootCompletedReceiver"
                    android:enabled="true"
                    android:exported="true">
                    <intent-filter>
                        <action android:name="android.intent.action.BOOT_COMPLETED"/>
                    </intent-filter>
                </receiver>
                ...
            </application>
        </manifest>
        

      ⚠️不要在onReceive()方法中执行耗时操作,BroadcastReceiver不允许开启线程。

  3. 发送自定义标准广播

    <receiver
        android:name=".chapter6.standardbroadcast.MyStandardReceiver"
        android:enabled="true"
        android:exported="true">
        <intent-filter>
            <action android:name="com.youngly.firstlineofcode.MyStandardReceiver" />
        </intent-filter>
    </receiver> 
    
    class MyStandardReceiver : BroadcastReceiver() {
    
        override fun onReceive(context: Context, intent: Intent) {
            Toast.makeText(context, "received in MyStandardReceiver", Toast.LENGTH_SHORT).show()
        }
    }
    
    fun myStandardBroadcastReceiver(view: View) {
        val intent = Intent("com.youngly.firstlineofcode.MyStandardReceiver")
        intent.`package` = packageName
        sendBroadcast(intent)
    }
    

    ⚠️Android 8.0系统之后,静态注册的BroadcastReceiver是无法接收隐式广播的,而默认情况下我们发出的自定义广播恰恰都是自定义广播。因此这里调用setPackage()方法,指定这条广播是发送给哪个应用程序的,从而使它变成一条显式广播。

  4. 发送有序广播

    <receiver
        android:name=".chapter6.OrderBroadcast.OrderReceiver"
        android:enabled="true"
        android:exported="true">
        <intent-filter android:priority="1000">
            <action android:name="com.youngly.firstlineofcode.MyStandardReceiver" />
        </intent-filter>
    </receiver>
    
    val intent = Intent("com.youngly.firstlineofcode.MyStandardReceiver")
    intent.`package` = packageName
    sendOrderedBroadcast(intent, null)
    
    class OrderReceiver : BroadcastReceiver() {
    
        override fun onReceive(context: Context, intent: Intent) {
            // This method is called when the BroadcastReceiver is receiving an Intent broadcast.
            Toast.makeText(context, "received in OrderReceiver", Toast.LENGTH_SHORT).show()
            abortBroadcast()
        }
    }
    
    • android:priority设置优先级
    • sendOrderedBroadcast(intent, null)发送有序广播
    • abortBroadcast()截断广播
  5. Kotlin课堂

    1. 高阶函数

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

      函数类型的定义:

      (String, Int) -> Unit
      

      函数类型最关键的是声明该函数接收的参数返回值->左边是函数参数,多个参数用,分隔。如果不接收任何参数,写一对空括号即可。->右边部分用于声明函数的返回值类型。

      fun example(func: (String, Int) -> Unit) {
          
      }
      

      上例中,example()函数就是高阶函数,它接收一个函数作为参数

      fun main() {
          println(operatorInts(1, 2, ::plus))
      }
      
      fun operatorInts(a: Int, b: Int, operator: (Int, Int) -> Int): Int {
          return operator(a, b)
      }
      
      fun plus(a: Int, b: Int): Int {
          return a + b
      }
      

      ::plus 函数引用方式的写法,表示将函数作为参数传递给函数。

    2. inline 内联函数

      1. 高阶函数原理解析:

        fun main() {
            operatorInts(4, 2) { a, b ->
                a / b
            }
        }
        
        fun operatorInts(a: Int, b: Int, operator: (Int, Int) -> Int): Int {
            return operator(a, b)
        }
        

        上述代码调用operatorInts()函数,并通过Lambda表达式指定对传入的两个整形参数进行求商操作。Kotlin代码最终还是编译成Java字节码,但Java中没有高阶函数的概念。

        原理是Java编译器将高阶函数的语法转化成Java支持的语法结构。

        public class HighOrderFuncTest {
        
            public static void main(String[] args) {
                int result = operatorIntegers(4, 2, new Function2<Integer, Integer, Integer>() {
                    @Override
                    public Integer invoke(Integer integer, Integer integer2) {
                        return integer / integer2;
                    }
                });
            }
        
            public static int operatorIntegers(int a, int b, Function2<Integer, Integer, Integer> operator) {
                return operator.invoke(a, b);
            }
        }
        

        在这里Lambda表达式变成了Function2接口,这是Kotlin内置的接口,里面有一个待实现的invoke()函数。

        这里会发现我们一直使用的Lambda表达式在底层被转换成了匿名内部类的实现方式。这就表明我们每调用一次Lambda表达式,都会创建一个新的匿名内部类实例。当然也会造成额外的内存和性能开销。

        Kotlin提供了内联函数的功能,它可以将Lambda表达式带来的运行时开销完全消除。

      2. 内联函数的用法

        inline fun operatorInts(a: Int, b: Int, operator: (Int, Int) -> Int): Int {
            return operator(a, b)
        }
        

        用法很简单,只需要在定义高阶函数时加上inline关键字声明即可

      3. 内联函数工作原理

        fun main() {
            val a = 4
            val b = 2
            a / b
        }
        

        相当于将operatorInts()函数平铺开来

    3. noinline

      一个高阶函数如果接收两个或者更多函数类型的参数,这时我们需要给函数加上inline关键字,那么Kotlin编译器自动将所有引用的Lambda表达式全部进行内联。

      这时我们只想内联其中一个Lambda表达式时,可以使用noinline关键字

      inline fun inlineTest(block1: () -> Unit, noinline block2: () -> Unit) {
          
      }
      

      因为内联的函数类型参数在编译的时候被代码替换,因此它没有真正的属性参数

      inline fun inlineTest(block1: () -> Unit,  block2: () -> Unit): () -> Unit {
        	// 编译报错,block2会被代码替换,不再是函数类型
          return block2
      }
      
      // 添加noinline关键字,编译👌
      inline fun inlineTest(block1: () -> Unit, noinline block2: () -> Unit): () -> Unit {
          return block2
      }
      
    4. crossinline

      看下下方代码:

      fun main() {
          println("main start")
          inLineTest {
              println("lambda run")
              return
          }
          println("main end")
      }
      
      inline fun inLineTest(block: () -> Unit) {
          println("out method start")
          block()
          println("out method end")
      }
      

      实际输出:

      main start
      out method start
      lambda run
      

      因为之前inline的原理中已经说明,相当于将函数平铺,所以此处return,是直接return的main()函数

      fun main() {
          println("main start")
      
          noinlineTest({ -> print("") }, { ->
              print("noinline lambda run")
              // 此处return编译报错
              return
          })
          println("main end")
      }
      
      inline fun noinlineTest(block: () -> Unit, noinline block2: () -> Unit) {
          println("out method start")
          block2()
          println("out method end")
      }
      

      为何上面return会编译报错?

      在上例inline的return中,return返回的是最外层的调用,这就导致一个问题,内联函数的return会结束最外层函数的调用,当使用return时需要确认是否是内联函数,所以Kotlin规定只有内联函数所引用的Lambda表达式才可以使用return关键字来进行函数返回

      那上面例子有办法使用return返回吗?可以使用return@函数名进行局部返回,修改如下

      fun main() {
          println("main start")
      
          noinlineTest({ -> print("") }, { ->
              print("noinline lambda run")
              return@noinlineTest
          })
          println("main end")
      }
      
      inline fun noinlineTest(block: () -> Unit, noinline block2: () -> Unit) {
          println("out method start")
          block2()
          println("out method end")
      }
      

      实际输出:

      main start
      out method start
      noinline lambda runout method end
      main end
      

      查看下方示例:

      inline fun inlineWithRunable(block: () -> Unit) {
          Runnable() {
          		// 编译报错
              block()
          }
      }
      

      内联函数引用的Lambda表达式允许使用return关键字进行返回,但是由于我们在匿名类中使用,此时不可能进行最外层调用函数返回了,只能对匿名类中的函数进行返回,可以添加crossinline关键字,保证内联函数引用的Lambda中不使用return关键字进行最外层函数返回,但可以使用return@内联函数名进行局部返回。

      inline fun inlineWithRunable(crossinline block: () -> Unit) {
          Runnable() {
              block()
          }
      }