十一、信息发送

在本章中,我们将使用安卓广播,并将其用作接收和发送消息的机制。我们将分几个步骤来理解它。首先,我们将解释下面的机制以及如何使用安卓广播消息。然后,我们将倾听一些最常见的信息。因为光听是不够的,所以我们会创作新的并播出。最后,我们将遇到启动、关闭和网络广播消息,因此我们的应用知道这个重要的系统事件。

在本章中,我们将涵盖以下主题:

  • 安卓广播
  • 收听广播
  • 创建广播
  • 监听网络事件

了解安卓广播

安卓应用可以发送或接收消息。消息可以是与系统相关的事件,也可以是我们定义的自定义事件。通过定义适当的意图过滤器和广播接收器,对某些消息注册相关方。当消息被广播时,所有感兴趣的人都会被通知。需要注意的是,一旦您订阅了广播消息(尤其是从Activity类),您必须在某个时候取消订阅。我们什么时候可以使用广播信息?当我们需要跨应用的消息传递系统时,我们使用广播消息。例如,假设您在后台启动了一个长时间运行的进程。在某个时候,您希望向多个上下文通知处理结果。广播消息是一个完美的解决方案。

系统广播

系统广播是安卓系统在各种系统事件发生时发送的广播。我们发送并最终接收到的每一条消息都被包装在包含特定事件信息的Intent类中。每个Intent必须有一个合适的动作集。例如- android.intent.action.ACTION_POWER_CONNECTED。关于事件的信息用捆绑的额外数据表示。例如,我们可能捆绑了一个额外的字符串字段,表示与我们感兴趣的事件相关的特定数据。让我们考虑一个充电和电池信息的例子。每次电池状态发生变化时,都会通知相关方并收到一条广播消息,其中包含有关电池电量的信息:

    val intentFilter = IntentFilter(Intent.ACTION_BATTERY_CHANGED) 
    val batteryStatus = registerReceiver(null, intentFilter) 

    val status = batteryStatus.getIntExtra(BatteryManager.
      EXTRA_STATUS, -1) 

    val isCharging = 
                status == BatteryManager.BATTERY_STATUS_CHARGING || 
                status == BatteryManager.BATTERY_STATUS_FULL 

    val chargePlug =    batteryStatus.getIntExtra(BatteryManager.
      EXTRA_PLUGGED, -1) 
    val usbCharge = chargePlug == BatteryManager.
      BATTERY_PLUGGED_USB 
    val acCharge = chargePlug == BatteryManager.BATTERY_PLUGGED_AC

在本例中,我们注册了电池信息的意图过滤器。然而,我们没有通过广播接收器的实例。为什么呢?这是因为电池数据是粘性的。粘性意图是在执行广播后停留一段时间的意图。注册到该数据将立即返回包含最新数据的意向。我们还可以传递一个广播接收器的实例。让我们开始吧:

    val receiver = object : BroadcastReceiver() { 
      override fun onReceive(p0: Context?, batteryStatus: Intent?) { 
      val status = batteryStatus?.getIntExtra
      (BatteryManager.EXTRA_STATUS, -1) 
                 val isCharging = 
                        status == 
                        BatteryManager.BATTERY_STATUS_CHARGING || 
                        status == BatteryManager.BATTERY_STATUS_FULL 
                        val chargePlug = batteryStatus?.getIntExtra
                       (BatteryManager.EXTRA_PLUGGED, -1) 
                        val usbCharge = chargePlug ==
                       BatteryManager.BATTERY_PLUGGED_USB 
                       val acCharge = chargePlug == 
                       BatteryManager.BATTERY_PLUGGED_AC 
        } 
    } 

    val intentFilter = IntentFilter(Intent.ACTION_BATTERY_CHANGED) 
    registerReceiver(receiver, intentFilter)

每次电池信息发生变化,接收器都会执行我们在其实现中定义的代码;我们还可以在安卓清单中定义我们的接收器:

    <receiver android:name=".OurPowerReceiver"> 
      <intent-filter> 
        <action android:name="android.intent.action.
        ACTION_POWER_CONNECTED"/> 
        <action android:name="android.intent.action.
        ACTION_POWER_DISCONNECTED"/> 
      </intent-filter> 
    </receiver>

收听广播

正如我们在前面的示例中所显示的,我们可以通过以下两种方式之一接收广播:

  • 通过安卓清单注册广播接收器
  • 在上下文中使用registerBroadcast()方法注册广播

通过清单声明需要以下条件:

  • 带有android:nameandroid:exported参数的<receiver>元素。
  • 接收器必须包含我们订阅的动作的intent过滤器。看看下面的例子:
        <receiver android:name=".OurBootReceiver"
          android:exported="true"> 
          <intent-filter> 
            <action android:name=
            "android.intent.action.BOOT_COMPLETED"/> 
            ... 
            <action android:name="..."/> 
            <action android:name="..."/> 
            <action android:name="..."/> 

         </intent-filter> 

       </receiver> 

如您所见,name属性代表我们的广播接收器类的名称。导出意味着应用可以或不能从接收者应用之外的来源接收消息。

如果你子类化BroadcastReceiver,应该是这样的例子:

     val receiver = object : BroadcastReceiver() { 
        override fun onReceive(ctx: Context?, intent: Intent?) { 
            // Handle your received code. 

        } 
    }

注意你在onReceive()方法实现中执行的操作不要花费太多时间。否则 ANR 会发生!

从上下文注册

现在,我们将向您展示一个从安卓上下文注册广播接收器的示例。要注册接收器,您需要它的一个实例。假设我们的实例是myReceiver:

    val myReceiver = object : BroadcastReceiver(){ 

      ... 

     }We need intent filter prepared: 
     val filter = IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)
     registerReceiver(myReceiver, filter)

本示例将注册一个接收器,该接收器将侦听连接信息。因为这个接收者是从上下文中注册的,所以只要我们注册的上下文有效,它就有效。也可以使用LocalBroadcastManager类。LocalBroadcastManager有一个目的是注册并向进程内的本地对象发送意向广播。这是一个例子:

    LocalBroadcastManager 
      .getInstance(applicationContext) 
      .registerReceiver(myReceiver, intentFilter)

要注销,请执行以下代码片段:

    LocalBroadcastManager 
      .getInstance(applicationContext) 
      .unregisterReceiver(myReceiver)

对于上下文订阅的接收者,注意注销是很重要的。例如,如果我们在活动的onCreate()方法中注册一个接收者,我们必须在onDestroy()方法中取消注册它。如果我们不这样做,我们将有一个接收器泄漏!同样,如果我们在onResume()注册我们的活动,我们必须在onPause()注销。如果我们不这样做,我们将注册多次!

接收者执行

我们在onReceive()实现中执行的代码被认为是前台进程。广播接收器处于活动状态,直到我们从方法返回。系统将始终运行您在实现中定义的代码,除非出现极端的内存压力。正如我们提到的,您应该只执行短操作!否则,ANR 会发生!收到消息时执行长时间运行操作的一个很好的例子是启动AsyncTask并在那里执行所有工作。接下来,我们将向您展示一个演示这一点的示例:

     class AsyncReceiver : BroadcastReceiver() { 
       override fun onReceive(p0: Context?, p1: Intent?) { 
         val pending = goAsync() 
         val async = object : AsyncTask<Unit, Unit, Unit>() { 
           override fun doInBackground(vararg p0: Unit?) { 
             // Do some intensive work here... 
             pending.finish() 
           } 
       } 
       async.execute() 
      }
    } 

在这个例子中,我们介绍了goAsync()方法的使用。它是做什么的?该方法返回一个PendingResult类型的对象,该对象表示调用API方法的待定结果。安卓系统认为接收器是活动的,直到我们在这个实例上调用finish()方法。使用这种机制,可以在广播接收器中进行异步处理。在我们完成密集的工作后,我们调用finish()向安卓系统表明这个组件可以回收。

发送广播

Android 有以下三种发送广播消息的方式:

  • 使用sendOrderedBroadcast(Intent, String)方法一次向一个接收者发送消息。由于接收器按顺序执行,因此可以将结果传播给下一个接收器。此外,也可以中止广播,这样它就不会被传递给其他接收器。我们可以控制接收器的执行顺序。我们可以使用匹配意图过滤器的android:priority属性来区分优先级。
  • 使用sendBroadcast(Intent)方法向所有接收器发送广播消息。没有订购发送。
  • 使用LocalBroadcastManager.sendBroadcast(Intent)方法将广播发送给与发送者处于同一应用中的接收者。

让我们看一个向所有相关方发送广播消息的示例:

    val intent = Intent() 
    intent.action = "com.journaler.broadcast.TODO_CREATED" 
    intent.putExtra("title", "Go, buy some lunch.") 
    intent.putExtra("message", "For lunch we have chicken.")
    sendBroadcast(intent)

我们创建了一个广播消息,其中包含关于我们创建的(标题和消息)的额外数据。所有相关方都需要一个适当的IntentFilter实例:

    com.journaler.broadcast.TODO_CREATED

不要混淆开始活动和发送广播消息。Intent类只是用作我们信息的包装器。这两个操作完全不一样!您可以使用本地广播机制实现同样的目的:

    val ctx = ... 
    val broadcastManager = LocalBroadcastManager.getInstance(ctx) 
    val intent = Intent() 
    intent.action = "com.journaler.broadcast.TODO_CREATED" 
    intent.putExtra("title", "Go, buy some lunch.") 
    intent.putExtra("message", "For lunch we have chicken.") 
    broadcastManager.sendBroadcast(intent)

现在,当我们向您展示广播消息传递的最重要方面时,我们将继续扩展我们的应用。Journaler 将发送和接收包含数据的自定义广播消息,并与系统广播交互,如系统启动、关闭和网络。

创建您自己的广播消息

您可能还记得,我们为NoteActivity类进行了代码重构。让我们展示最后一个状态的重要部分,以便进一步演示:

     class NoteActivity : ItemActivity() { 
       ... 
       private val locationListener = object : LocationListener { 
         override fun onLocationChanged(p0: Location?) { 
           p0?.let { 
                LocationProvider.unsubscribe(this) 
                location = p0 
                val title = getNoteTitle() 
                val content = getNoteContent() 
                note = Note(title, content, p0) 

                // Switching to intent service. 
                val dbIntent = Intent(this@NoteActivity,
                DatabaseService::class.java) 
                dbIntent.putExtra(DatabaseService.EXTRA_ENTRY, note) 
                dbIntent.putExtra(DatabaseService.EXTRA_OPERATION,
                MODE.CREATE.mode) 
                startService(dbIntent) 
                sendMessage(true) 
            } 
        } 

        override fun onStatusChanged(p0: String?, p1: Int, p2: Bundle?)
        {} 
        override fun onProviderEnabled(p0: String?) {} 
        override fun onProviderDisabled(p0: String?) {} 
      } 
      ... 
      private fun updateNote() { 
        if (note == null) { 
            if (!TextUtils.isEmpty(getNoteTitle()) &&
             !TextUtils.isEmpty(getNoteContent())) { 
               LocationProvider.subscribe(locationListener) 
            } 
        } else { 
            note?.title = getNoteTitle() 
            note?.message = getNoteContent() 

            // Switching to intent service. 
            val dbIntent = Intent(this@NoteActivity,
            DatabaseService::class.java) 
            dbIntent.putExtra(DatabaseService.EXTRA_ENTRY, note) 
            dbIntent.putExtra(DatabaseService.EXTRA_OPERATION,
            MODE.EDIT.mode) 
            startService(dbIntent) 
            sendMessage(true) 
        } 
      } 
      ... 
    }

如果您再看一下这个,您会注意到我们在执行时将intent发送给了我们的服务,但是由于我们没有得到返回值,所以我们只执行了以布尔true为参数的sendMessage()方法。这里,我们期望一个代表 CRUD 操作结果的值,即成功或失败。我们将使用广播消息将我们的服务与NoteActivity连接起来。每次我们插入或更新note广播,都会触发一条消息。我们在NoteActivity中定义的监听器将响应该消息并触发sendMessage()方法。让我们更新我们的代码!打开Crud界面,用包含动作常量和 CRUD 操作结果的companion对象进行扩展:

    interface Crud<T> { 
      companion object { 
        val BROADCAST_ACTION = "com.journaler.broadcast.crud" 
        val BROADCAST_EXTRAS_KEY_CRUD_OPERATION_RESULT = "crud_result" 
      } 
       ... 
    }

现在,打开DatabaseService,用一个负责在 CRUD 操作执行时发送广播消息的方法进行扩展:

     class DatabaseService : IntentService("DatabaseService") { 
       ... 
       override fun onHandleIntent(p0: Intent?) { 
          p0?.let { 
            val note = p0.getParcelableExtra<Note>(EXTRA_ENTRY) 
            note?.let { 
                val operation = p0.getIntExtra(EXTRA_OPERATION, -1) 
                when (operation) { 
                    MODE.CREATE.mode -> { 
                        val result = Db.insert(note) 
                        if (result) { 
                            Log.i(tag, "Note inserted.") 
                        } else { 
                            Log.e(tag, "Note not inserted.") 
                        } 
                        broadcastResult(result) 
                    } 
                    MODE.EDIT.mode -> { 
                        val result = Db.update(note) 
                        if (result) { 
                            Log.i(tag, "Note updated.") 
                        } else { 
                            Log.e(tag, "Note not updated.") 
                        } 
                        broadcastResult(result) 
                    } 
                    else -> { 
                        Log.w(tag, "Unknown mode [ $operation ]") 
                    } 
                 } 
             } 
           } 
        } 
        ... 
        private fun broadcastResult(result: Boolean) { 
          val intent = Intent() 
          intent.putExtra( 
                Crud.BROADCAST_EXTRAS_KEY_CRUD_OPERATION_RESULT, 
                if (result) { 
                    1 
                } else { 
                    0 
                } 
          ) 
        } }

我们介绍了一种新方法。其他都一样。我们将 CRUD 操作结果作为消息进行广播。NoteActivity会监听它:

   class NoteActivity : ItemActivity() { 
     ... 
     private val crudOperationListener = object : BroadcastReceiver() { 
        override fun onReceive(ctx: Context?, intent: Intent?) { 
            intent?.let { 
                val crudResultValue =
                intent.getIntExtra(MODE.EXTRAS_KEY, 0) 
                sendMessage(crudResultValue == 1) 
            } 
        } 
      } 
      ... 
      override fun onCreate(savedInstanceState: Bundle?) { 
        .... 
        registerReceiver(crudOperationListener, intentFiler) 
      } 

      override fun onDestroy() { 
        unregisterReceiver(crudOperationListener) 
        super.onDestroy() 
      } 
      ... 
      private fun sendMessage(result: Boolean) { 
         Log.v(tag, "Crud operation result [ $result ]") 
         val msg = handler?.obtainMessage() 
         if (result) { 
            msg?.arg1 = 1 
         } else { 
            msg?.arg1 = 0 
         } 
         handler?.sendMessage(msg) 
     } }

这既简单又容易!我们将原来的sendMessage()方法与 CRUD 操作结果重新连接。在接下来的部分中,我们将考虑通过收听引导、关闭和网络广播消息,我们的应用可以实现的一些重大改进。

开机和关机时使用广播

有时,服务在应用启动时运行是至关重要的。此外,有时在我们终止它之前做一些清理工作是很重要的。在下面的例子中,我们将扩展 Journaler 应用来监听这些广播消息并做一些工作。我们首先要做的是创建两个扩展BroadcastReceiver类的类:

  • BootReceiver:这是处理系统启动事件
  • ShutdownReceiver:这是处理系统关机事件

将它们登记在您的manifest文件中,如下所示:

    <manifest  
     ... 
    > 
    ... 
    <receiver 
        android:name=".receiver.BootReceiver" 
        android:enabled="true" 
        android:exported="false"> 
        <intent-filter> 
          <action android:name=
           "android.intent.action.BOOT_COMPLETED" /> 
        </intent-filter> 

        <intent-filter> 
          <action android:name=
          "android.intent.action.PACKAGE_REPLACED" /> 
          data android:scheme="package" /> 
        </intent-filter> 

        <intent-filter> 
          <action android:name=
          "android.intent.action.PACKAGE_ADDED" /> 
          <data android:scheme="package" /> 
        </intent-filter> 
     </receiver> 

    <receiver android:name=".receiver.ShutdownReceiver"> 

      <intent-filter> 
        <action android:name=
        "android.intent.action.ACTION_SHUTDOWN" /> 
        <action android:name=
        "android.intent.action.QUICKBOOT_POWEROFF" /> 
      </intent-filter> 
    </receiver> 
     ...
    </manifest> 

当我们启动或替换应用时,将触发BootReceiver类。当我们关闭设备时会触发关机。让我们创建适当的实现。打开BootReceiver类,这样定义:

     package com.journaler.receiver 

     import android.content.BroadcastReceiver 
     import android.content.Context 
     import android.content.Intent 
     import android.util.Log 

     class BootReceiver : BroadcastReceiver() { 

       val tag = "Boot receiver" 

       override fun onReceive(p0: Context?, p1: Intent?) { 
         Log.i(tag, "Boot completed.") 
         // Perform your on boot stuff here. 
       } 

     } 

如您所见,我们为这两个类定义了receiver包。对于ShutdownReceiver,这样定义类:

    package com.journaler.receiver 

    import android.content.BroadcastReceiver 
    import android.content.Context 
    import android.content.Intent 
    import android.util.Log 

    class ShutdownReceiver : BroadcastReceiver() { 

      val tag = "Shutdown receiver" 

      override fun onReceive(p0: Context?, p1: Intent?) { 
        Log.i(tag, "Shutting down.") 
        // Perform your on cleanup stuff here.   
      } }

为了做到这一点,我们需要再做一次改变;否则,应用将崩溃。通过启动Application类的main服务进入主活动onCreate()方法。这是Journaler类的第一次更新:

    class Journaler : Application() { 
      ... 
      override fun onCreate() { // We removed start service method
        execution. 
        super.onCreate() 
        ctx = applicationContext 
        Log.v(tag, "[ ON CREATE ]") 
     } 
     // We removed startService() method implementation. 
     ... 
    }

然后通过在onCreate()方法的末尾添加行来扩展MainActivity类:

    class MainActivity : BaseActivity() { 
      ... 
      override fun onCreate(savedInstanceState: Bundle?) { 
        ... 
        val serviceIntent = Intent(this, MainService::class.java) 
        startService(serviceIntent) 
     } 
    ... } }

构建并运行您的应用。首先,关闭手机,然后开机。过滤你的日志,使它只显示你的应用的日志。您应该有以下输出:

... I/Shutdown receiver: Shutting down. 
... I/Boot receiver: Boot completed.

Keep in mind that, sometimes, it requires more than two minutes to receive on an boot event!

监听网络事件

我们希望的最后一项改进是我们的应用能够在连接建立后执行同步。在同一个NetworkReceiver包中创建新的类。确保您有这样的实现:

    class NetworkReceiver : BroadcastReceiver() {
      private val tag = "Network receiver"
      private var service: MainService? = null

      private val serviceConnection = object : ServiceConnection { 
        override fun onServiceDisconnected(p0: ComponentName?) { 
          service = null 
        } 

        override fun onServiceConnected(p0: ComponentName?, binder:
        IBinder?) { 
            if (binder is MainService.MainServiceBinder) { 
                service = binder.getService() 
                service?.synchronize() 
            } 
        } 
       } 

       override fun onReceive(context: Context?, p1: Intent?) { 
       context?.let { 

            val cm = context.getSystemService
           (Context.CONNECTIVITY_SERVICE) as ConnectivityManager 

            val activeNetwork = cm.activeNetworkInfo 
            val isConnected = activeNetwork != null &&
            activeNetwork.isConnectedOrConnecting 
            if (isConnected) { 
                Log.v(tag, "Connectivity [ AVAILABLE ]") 
                if (service == null) { 
                    val intent = Intent(context,
                    MainService::class.java) 
                    context.bindService( 
                        intent, serviceConnection,
                        android.content.Context.BIND_AUTO_CREATE 
                    ) 
                } else { 
                    service?.synchronize() 
                } 
            } else { 
                Log.w(tag, "Connectivity [ UNAVAILABLE ]") 
                context.unbindService(serviceConnection) 
            } 
          } 
        } 
    }

当连接事件发生时,此接收器将接收消息。每次当我们有上下文和连接时,我们将绑定到服务并触发同步。不要为频繁的同步触发而烦恼,因为在接下来的章节中,我们将在同步方法实现本身中保护自己免受其害。通过更新日志应用类来注册您的侦听器,如下所示:

     class Journaler : Application() { 
       ... 
       override fun onCreate() { 
          super.onCreate() 
          ctx = applicationContext 
          Log.v(tag, "[ ON CREATE ]") 
          val filter =  
          IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION) 
          registerReceiver(networkReceiver, filter) 
      } 
      ... 
    } 

构建并运行应用。关闭您的连接(无线、移动)并再次打开。请注意以下 Logcat 输出:

... V/Network receiver: Connectivity [ AVAILABLE ] 
... V/Network receiver: Connectivity [ AVAILABLE ] 
... V/Network receiver: Connectivity [ AVAILABLE ] 
... W/Network receiver: Connectivity [ UNAVAILABLE ] 
... V/Network receiver: Connectivity [ AVAILABLE ] 
... V/Network receiver: Connectivity [ AVAILABLE ] 
... V/Network receiver: Connectivity [ AVAILABLE ] 

摘要

在本章中,我们学习了如何使用广播消息。我们还学习了如何收听系统广播消息以及我们自己创建的消息。Journaler 应用得到了显著改进,变得更加灵活。我们不会止步于此,但我们将通过学习新的东西和扩展我们的代码,在安卓框架中继续前进。