安卓学习————广播

669 阅读6分钟

安卓学习之六--广播机制

广播机制简介

Android应用程序都可以对自己感兴趣的广播进行注册,,这样程序就可以接受自己所关心的广播内容,这些内容可能来自系统,也可能来自应用程序,在Android中提供一套完整的API,允许程序接受和发送广播,接受广播需要用到--BroadcasReceiver

首先广播方式分为标准广播和有序广播

标准广播:完全异步执行的广播,在发送广播后,所有BroadcasReceiver会几乎同时接受广播消息,所有效率高,但是不可以截断。

有序广播:感觉就像排队一样,同一时刻只有一个BroadcasReceiver接受广播消息,但这个BroadcasReceiver逻辑执行完毕后,后一个BroadcasReceiver在接受,依次传递出去,优先级别高的BroadcasReceiver先接受,依次内推,有序广播是可以被截断的,一当截断后面的就无法接受消息。

动态注册监听时间变化

动态注册是在代码中注册,首先我们兴建一个BroadcastTest项目,在ManActivity中定义一个内部类去继承BroadcasReceiver,并且重写父类onReceive方法,这样随着时间变化这个方法就会得到执行

package com.example.broadcastreceiver

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Toast

class MainActivity : AppCompatActivity() {
    lateinit var timeChangerReceiver:TimeChangeRecevier
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        //获取IntentFilter实例
        val  intentFilter=IntentFilter()
        //但时间发生变化,系统会发出一条值为"android.intent.action.TIME_TICK"广播
        //也就是我么的BroadcastReceiver想要监听什么广播
        intentFilter.addAction("android.intent.action.TIME_TICK")
        timeChangerReceiver=TimeChangeRecevier()
        //这个方法是进行注册,这样才可以接受值为android.intent.action.TIME_TICK广播
        registerReceiver(timeChangerReceiver,intentFilter)


    }

    override fun onDestroy() {
        super.onDestroy()
        //这里取消注册,不然会导致内存溢出
        unregisterReceiver(timeChangerReceiver)
    }
    inner class TimeChangeRecevier:BroadcastReceiver(){
        override fun onReceive(context: Context?, intent: Intent?) {
            Toast.makeText(context,"This has changed",Toast.LENGTH_SHORT).show()
        }
    }
}

随着时间变化而发出广播效果如下

截屏2021-05-10 下午6.54.24.png

静态注册实现开机启动

动态注册虽然灵活,但是要在要在程序启动后才能接受,反之就是使用静态注册.

理论上动态注册能够监听到广播,那么静态也应该可以,在以前确实可以,但是因为有大量恶意程序利用这个机制在为启动程序时监听广播系统,导致手机频繁被后台唤醒,严重影响了手机的性能和电量,所以每次安卓版本更新,几乎都会削弱静态注册功能,在Android8.0之后所以的隐士广播都不允许使用静态注册了。

隐士广播:是指没有具体指定发送给那个应用程序的广播

当然一些特殊的广播是允许的:例如开机广播

android.intent.action.BOOT_COMPLETED

动态注册我们是通过内部类,现在我们就通过Android Studio快捷方式创建, 右击com.example.broadcastreceiver->new->other->Broadcast Receiver 这里我把类名改为BootCompleteReceiver

Exprodted:表示允许这个BroadcasReceive接受本程序以外的广播

Enabled:表示是否启用这个BroadcasReceive

截屏2021-05-10 下午7.18.57.png

下面修改BootCompleteReceiver类的代码,在onReceived方法中使用Toast弹出提示消息

package com.example.broadcastreceiver

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.widget.Toast

class BootCompleteReceiver : BroadcastReceiver() {

    override fun onReceive(context: Context, intent: Intent) {
        // This method is called when the BroadcastReceiver is receiving an Intent broadcast.
        Toast.makeText(context,"Boot Complete",Toast.LENGTH_SHORT).show()
    }
}

静态注册的BroadcasReceiv一定要在ActivityManifest.xml中注册,由于我们是通过快捷方式注册,所以直接注册好了,实例如下:

 <receiver
            android:name=".BootCompleteReceiver"
            android:enabled="true"
            android:exported="true"></receiver>

要想收到开机广播,还得修改ActivityManifest.xml代码 因为权限问题要加上

   <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>

最后添加<receiver标签下添加action

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

发送标准广播 首先定义一个BroadcastReceiver接受广播

package com.example.broadcastreceiver

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.widget.Toast

class MyBoardcastReceiver : BroadcastReceiver() {

 override fun onReceive(context: Context, intent: Intent) {
     // This method is called when the BroadcastReceiver is receiving an Intent broadcast.
     Toast.makeText(context,"recevied in MYboardcastReciver",Toast.LENGTH_SHORT).show()
 }
}

然后在ActivityManifest对BroadcastReceiver进行修改,这里让BoardcastReceiver接受com.example.broadcasttest.MY_BRPADCAST广播

    <receiver
            android:name=".MyBoardcastReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="com.example.broadcasttest.MY_BRPADCAST"/>
            </intent-filter>
        </receiver>

我们要发送这样一条广播,接下来修改Man.xml代码,增加一个按钮布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
     android:orientation="vertical">

<Button
   android:id="@+id/button"
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
    android:text="Send Broadcast"/>

</LinearLayout>

然后在ManActivity中设置监听器

首先构建一个Intent对象,把发送广播传入

调用 intent.setPackage(packageName)传入当前应用程序的包名

sendBroadcast(intent)指定这条广播发送给具体应用程序

class MainActivity : AppCompatActivity() {
    lateinit var timeChangerReceiver:TimeChangeRecevier
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val button:Button=findViewById(R.id.button)
        button.setOnClickListener{
            val intent=Intent("comcom.example.broadcasttest.MY_BRPADCAST")
             intent.setPackage(packageName)
            sendBroadcast(intent)
        }
        .....

有序广播

在快捷方式创建一个BoradcastReceiver接受同样的广播

package com.example.myapplication

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.widget.Toast

class MyReceiver2 : BroadcastReceiver() {

    override fun onReceive(context: Context, intent: Intent) {
       //接受到广播消息的弹出消息变成up up
        Toast.makeText(context,"up up ", Toast.LENGTH_SHORT).show()
    }
}

既然是有序广播就可以该优先级,在AndroidManifest.xml修改,改成100,这个广播最先接受到消息

 <intent-filter android:priority="100">

然后可以截断广播,不在传递

class MyReceiver : BroadcastReceiver() {

    override fun onReceive(context: Context, intent: Intent) {
        // This method is called when the BroadcastReceiver is receiving an Intent broadcast.
      Toast.makeText(context,"up up day day",Toast.LENGTH_SHORT).show()
         abortBroadcast()//截断广播
    }
}

BroadcastReceiver最佳实践

模拟登录后的的强制下线功能

1.首先我们创建一个类来管理所有的Activity

package com.example.broadcastreceiver

import android.app.Activity
//这个类来管理所有的Activity关闭和添加
object Activityall {
    private val activityaes=ArrayList<Activity>()
    fun addActivty(activity: Activity){
        activityaes.add(activity)
    }
    fun removeactivity(activity: Activity){
        activityaes.remove(activity)
    }
    fun allActivity(){
        for (activity in activityaes){
            if (!activity.isFinishing){
                activity.finish()
            }
        }
        activityaes.clear()
    }
}

然后创建BaseActivity这个类来作为所有Activity的父类,因为我们强制下线功能是你不管在哪个类,都要强制退出,所以退出的逻辑和接受注册广播和取消注册都在这个类中完成

package com.example.broadcastreceiver

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Bundle
import android.os.PersistableBundle
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity

open  class BaseActivity: AppCompatActivity() {
    lateinit var recevier:ForReceiver
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Activityall.addActivty(this)

    }

    override fun onResume() {
        super.onResume()
        val intentFilter=IntentFilter()
          intentFilter.addAction("com.example.FOLL_PO")
          recevier=ForReceiver()
          registerReceiver(recevier,intentFilter)

    }

    override fun onPause() {
        super.onPause()
        unregisterReceiver(recevier)
    }
    override fun onDestroy() {
        super.onDestroy()
        Activityall.removeactivity(this)
    }


inner class ForReceiver:BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {

            AlertDialog.Builder(context).apply {
                setTitle("下线")
                setMessage("你被挤了,下线把")
                setCancelable(false)//不能强制退出
                setPositiveButton("Ok") { _, _ ->
                    Activityall.allActivity()
                    val i = Intent(context, LoginActivity::class.java)
                    context.startActivity(i)


                }
                show()
            }
    }
}
}

接下来兴建一个 LoginActivity作为登录界面

package com.example.broadcastreceiver

import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
import android.widget.EditText
import android.widget.Toast
import androidx.core.content.withStyledAttributes

class LoginActivity : BaseActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_login)
        val login:Button=findViewById(R.id.login)
        login.setOnClickListener {
            val zh:EditText=findViewById(R.id.zh)
            val zh2=zh.text.toString();
            val mm:EditText=findViewById(R.id.mm)
            val mm2=mm.text.toString();
            if (zh2=="abc"&&mm2=="123"){
                val intent=Intent(this,MainActivity::class.java)
                startActivity(intent)
            }else{
                Toast.makeText(this,"小伙子密码或账号错误",Toast.LENGTH_SHORT).show()
            }
        }
    }
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    >

    <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="账号:" />

        <EditText
            android:id="@+id/zh"
            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="密码" />

        <EditText
            android:id="@+id/mm"
            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="登录" />


</LinearLayout>

在 LoginActivity来验证账号和密码的正确性

package com.example.broadcastreceiver

import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
import android.widget.EditText
import android.widget.Toast
import androidx.core.content.withStyledAttributes

class LoginActivity : BaseActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_login)
        val login:Button=findViewById(R.id.login)
        login.setOnClickListener {
            val zh:EditText=findViewById(R.id.zh)
            val zh2=zh.text.toString();
            val mm:EditText=findViewById(R.id.mm)
            val mm2=mm.text.toString();
            if (zh2=="abc"&&mm2=="123"){
               //这里就是强制下线后,又跳转到登录界面
                val intent=Intent(this,MainActivity::class.java)
                startActivity(intent)
            }else{
                Toast.makeText(this,"小伙子密码或账号错误",Toast.LENGTH_SHORT).show()
            }
        }
    }
}

在ManActivity中添加一个用于触发强制下线的按钮

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
   >
    <Button
        android:id="@+id/tc"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="tuichu"/>
</LinearLayout>

在修改MainActivity的代码

package com.example.broadcastreceiver

import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button

class MainActivity : BaseActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val tuichu:Button=findViewById(R.id.tc)
        tuichu.setOnClickListener {
            val intent=Intent("com.example.FOLL_PO")
            //发送广播
            sendBroadcast(intent)
        }
    }
}

最后一定注意把 LoginActivity这个登录界面改为主Activity

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

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

最后运行,如果密码错误

截屏2021-06-10 下午7.48.48.png

如果正确就会跳转到登录后的界面

截屏2021-06-10 下午7.49.51.png

点击这个按钮,模拟强制下线

截屏2021-06-10 下午7.50.53.png

点击ok重新回到登录界面