从零开始水安卓——安卓四大组件BroadcastReceiver

BroadcastReceiver

概述

BroadcastReceiver翻译过来是"广播接收者”的意思,与字面意思相同,它的功能就是用来接收来自系统和应用中的广播。

下面是API中对其的一些描述:

  • 广播接收器是一种用于响应系统范围广播通知的组件。
  • 许多广播都是由系统发起的 — 例如,通知屏幕已关闭、电池电量不足或已拍摄照片的广播。
  • 应用也可以发起广播 — 例如,通知其他应用某些数据已下载至设备,并且可供其使用。
  • 尽管广播接收器不会显示用户界面,但它们可以创建状态栏通知,在发生广播事件时提醒用户。
  • 但广播接收器更常见的用途只是作为通向其他组件的“通道”,设计用于执行极少量的工作。 例如,它可能会基于事件发起一项服务来执行某项工作。
  • 广播接收器作为 BroadcastReceiver 的子类实现,并且每条广播都作为 Intent 对象进行传递。

在Android系统中,广播体现在方方面面,例如:

  1. 当开机完成后系统会产生一条广播,接收到这条广播就能实现开机启动服务的功能
  2. 当网络状态改变时系统会产生一条广播,接收到这条广播就能及时地做出提示和保存数据等操作
  3. 当电池电量改变时,系统会产生一条广播,接收到这条广播就能在电量低时告知用户及时保存进度

Android中的广播机制设计的非常出色。

很多事情原本需要开发者亲自操作的,现在只需等待广播告知自己就可以了,大大减少了开发的工作量和开发周期。

而作为应用开发者,只需要数练掌握Android系统提供的这个开发利器——BroadcastReceiver。

类型

广播接收器通常分如下三个类型:

  1. 默认广播Normal broadcasts:发送一个默认广播使用Content.sendBroadcast()方法,普通广播对于接收者来说是完全异步的,通常每个接收者都无需等待即可以接收到广播,接收者相互之间不会有影响。对于这种广播,接收者无法终止广播,即无法阻止其他接收者的接收动作。
  2. 有序广播Ordered broadcasts:发送一个有序广播使用Content.sendOrderedBroadcast()方法,有序广播比较特殊,它每次只发送到优先级较高的接收者那里,然后由优先级高的接收者再传播到优先级低的接收者那里,优先级高的接收者有能力终止这个广播
  3. 粘性广播Sticky Broadcast:广播处理完之后,依然存在,直到你把它去掉。主要是为了服务某些动态注册的接收者。

 创建一个广播接收器

准备工作:准备一个Button

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/normal"
        android:onClick="normal"
        android:text="发送一个普通的广播"
        app:layout_constraintTop_toTopOf="parent"/>

除此之外,还需要准备一个Broadcast receiver

从零开始水安卓——安卓四大组件BroadcastReceiver

创建完毕后 配置清单文件会我们注册这个receiver

从零开始水安卓——安卓四大组件BroadcastReceiver

package com.example.a4_5broadcastreceiver;

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

//自定义的广播接收器
public class MyReceiver extends BroadcastReceiver {

    //接收
    @Override
    public void onReceive(Context context, Intent intent) {
        String info=intent.getStringExtra("info");
        Toast.makeText(context,info,Toast.LENGTH_SHORT).show();
    }
}

接着书写onClick事件:在学习intent的时候,我们已经知道,activity、service、broadcast receiver组件的交互都是依靠intent的,所以我们在这里需要构建一个Intent(参数为action,需要对应的在配置清单文件加上这个action,并嵌套一个intent-filter)

从零开始水安卓——安卓四大组件BroadcastReceiver

由于发送的是普通(默认)的广播,所以最后需要使用sendBroadcast()方法。

package com.example.a4_5broadcastreceiver;

import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    //发送一个普通的广播
    public void normal(View view){
        Intent intent=new Intent("com.example.action.MY_BROADCAST");
        intent.putExtra("info","发送了一个普通的广播");
        this.sendBroadcast(intent);
    }
}

从零开始水安卓——安卓四大组件BroadcastReceiver

在8.0及以上版本,上面的方法(静态注册)是不能正常实现的,请换用更低版本的模拟器进行测试,或者跳过这个案例继续看文章。

注册广播接收器的两种方式

 

  1. 静态注册:在AndroidManifest.xml文件中配置。(即上面演示的案例)
  2. 动态注册:需要在代码中动态的指定广播地址并注册,通常我们是在Activity或Service注册一个广播

注意:
动态注册的优先级高于静态注册

 

下面给出一个动态注册的案例

依旧需要需要一个Broadcast receiver,之前提到配置清单文件会自动注册,由于选择了动态注册,所以把配置清单文件里的注册的部分去掉(或注释掉)

我们简单的写一下:

package com.example.a4_5broadcastreceiver;

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

public class MyReceiver2 extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        Toast.makeText(context, "动态注册的广播接收器", Toast.LENGTH_LONG).show();
    }
}

重点是MainActivity如何注册Broadcast receiver:

先new一个Broadcast receiver对象

在onResume中广播注册(通过IntentFilter过滤动作,通过registerReceiver注册)

在onPause中解除广播注册

package com.example.a4_5broadcastreceiver;

import android.content.Intent;
import android.content.IntentFilter;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;

public class MainActivity extends AppCompatActivity {

    private MyReceiver2 myReceiver2 = new MyReceiver2();
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    //发送一个普通的广播
    public void normal(View view){
        Intent intent=new Intent("com.example.action.MY_BROADCAST");
        intent.putExtra("info","发送了一个普通的广播");
        this.sendBroadcast(intent);
    }
    //该方法中进行广播注册(动态)
    @Override
    protected void onResume() {
        super.onResume();
        IntentFilter filter = new IntentFilter();
        filter.addAction("com.example.action.MY_BROADCAST");
        registerReceiver(myReceiver2, filter);
    }
    //在该方法中解除广播注册
    @Override
    protected void onPause() {
        super.onPause();
        unregisterReceiver(myReceiver2);
    }
}

从零开始水安卓——安卓四大组件BroadcastReceiver

 

有序广播

之前两个案例都是关于默认广播的,现在给出有序广播的案例,下面是一些注意事项和补充说明。

  • 需要用到android:priority属性:这个属性可以控制优先级,范围在-1000到1000,数值越大, 优先级越高。
  • 如果是同级别接收先后将是随机的。
  • 使用sendOrderedBroadcast方法发送有序广播时,需要一个权限参数。这样做是从安全角度考虑的,例如系统的短信就是有序广播的形式,一个应用可能是具有栏截垃圾短信的功能,当短信到来时它可以先接受到短信广播,必要时终止广播传递,这样的软件就必须声明接收短信的权限。

参数如下:

null——不要求接收者声明指定的权限。

不为null——接收者若要接收此广播,需声明指定权限。

 

为了更好的体现有序广播的特性,我们建立两个接收器,并重写onReceive方法....

    @Override
    public void onReceive(Context context, Intent intent) {
        Toast.makeText(context, "低优先级的Receive", Toast.LENGTH_LONG).show();
    }
    @Override
    public void onReceive(Context context, Intent intent) {
        Toast.makeText(context,"高优先级的Receive",Toast.LENGTH_LONG).show();
    }

同样准备一个按钮,并完成对应的点击事件 

    //发送一个有序的广播
    public void order(View view){
        Intent intent=new Intent("com.example.action.MY_BROADCAST2");
        //参数:intent ,接收权限具体细节见上面的补充说明
        this.sendOrderedBroadcast(intent,null);
    }

 再对它们配置清单文件的android:priority属性进行设置。

从零开始水安卓——安卓四大组件BroadcastReceiver

效果会是,先出现高优先级的广播,再出现低优先级的广播。

从零开始水安卓——安卓四大组件BroadcastReceiver从零开始水安卓——安卓四大组件BroadcastReceiver

当然这样子还不能完全体现出有序广播的全部特殊之处。我们进一步研究下去。

说明:

  • 终止广播传递:abortBroadcast(),高优先级的接收者可以终止广播传递。这个就不测试了,效果就是如果被终止,后面的广播就不显示了。
  • 在广播接收器中使用setResultExtras方法将一个Bundle对象设置为结果集对象。传递到下一个接收者那里,这样优先级低的接收者可以用getResultExtras获取到最新的经过处理的信息集合。

修改两个接收器

package com.example.a4_5broadcastreceiver;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.widget.Toast;

public class MyReceiver4 extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        Toast.makeText(context,"高优先级的Receive",Toast.LENGTH_LONG).show();
        Bundle data=new Bundle();
        data.putString("info","放入了一段内容");
        this.setResultExtras(data);
    }
}
package com.example.a4_5broadcastreceiver;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.widget.Toast;

public class MyReceiver3 extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        Bundle data= getResultExtras(false);
        String info= data.getString("info");
        Toast.makeText(context, "低优先级的Receive/"+info, Toast.LENGTH_LONG).show();

    }
}

从零开始水安卓——安卓四大组件BroadcastReceiver可以看到,低优先级的Receive收到了经过处理之后的最新广播。

 

粘性广播

下面是关于粘性广播的案例。

注意事项:

  • 使用sendStickyBroadcast()来发送粘性广播

  • 使用sendStickyOrderedBroadcast()来发送兼具有序广播和粘性广播的特性的广播(优先级情况和之前类似)

  • 粘性广播需要在配置清单提供<uses-permission android:name="android.permission.BROADCAST_STICKY" />的权限

  • 使用removeStickyBroadcast()来解除粘性广播

在配置清单加入权限

从零开始水安卓——安卓四大组件BroadcastReceiver

一个按钮????及其点击事件方法

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/sticky"
        android:onClick="sticky"
        android:text="发送一个粘性广播"
        app:layout_constraintTop_toBottomOf="@id/order"/>
    //发送一个粘性广播
    public void sticky(View view) {
        Intent intent = new Intent("com.example.action.MY_BROADCAST3");
        this.sendStickyBroadcast(intent);
    }

一个广播接收器 (并进行动态注册)

package com.example.a4_5broadcastreceiver;

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

public class MyReceiver5 extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        Toast.makeText(context,"一个粘性的广播",Toast.LENGTH_SHORT).show();
    }
}

为了测试粘性广播特性,额外加一个Activity并额外加一个按钮启动这个Activity

package com.example.a4_5broadcastreceiver;

import android.content.IntentFilter;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

public class Main2Activity extends AppCompatActivity {

    private MyReceiver5 myReceiver5 = new MyReceiver5();
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);
    }

    @Override
    protected void onResume() {
        super.onResume();
        IntentFilter filter = new IntentFilter("com.example.action.MY_BROADCAST3");
        registerReceiver(myReceiver5,filter);
    }

    @Override
    protected void onPause() {
        super.onPause();
        unregisterReceiver(myReceiver5);
    }
}

效果如下: 

从零开始水安卓——安卓四大组件BroadcastReceiver从零开始水安卓——安卓四大组件BroadcastReceiver

显然接收器注册在发送广播之后,但是依然收到了广播。

系统广播

安卓内置了不少系统级的广播,使用步骤与前面的案例无异,下面简单的提供一些案例。

开机启动服务

一个接收器

package com.example.a4_5broadcastreceiver;

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

//开机启动服务
public class MyReceiver6 extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        Toast.makeText(context, "开机启动服务", Toast.LENGTH_SHORT).show();
    }
}

还需要设置权限

从零开始水安卓——安卓四大组件BroadcastReceiver

配置action

从零开始水安卓——安卓四大组件BroadcastReceiver

如此一来就可以在开机启动的时候推送广播了。

然后模拟器上可能看不出来效果...orz

网络状态变化

准备一个接收器

package com.example.a4_5broadcastreceiver;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.widget.Toast;

public class MyReceiver7 extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        //系统级服务,网络管理服务
        ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
        //网络活动信息,可以从info中拿到网络类型名
        NetworkInfo info = cm.getActiveNetworkInfo();
        if (info != null) {
            String name = info.getTypeName() + "";
            Toast.makeText(context, name, Toast.LENGTH_LONG).show();
        }
    }
}

设置权限

从零开始水安卓——安卓四大组件BroadcastReceiver

对应的action

从零开始水安卓——安卓四大组件BroadcastReceiver

然后模拟器上可能同样看不出来....

电量变化

同样一个接收器

package com.example.a4_5broadcastreceiver;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.BatteryManager;
import android.widget.Toast;

//电池电量变化
public class MyReceiver8 extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        //当前电量
        int curr = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0);
        //总电量
        int total = intent.getIntExtra(BatteryManager.EXTRA_SCALE, 1);
        //计算百分比
        int percent = curr * 100 / total;
        Toast.makeText(context, "当前电量为" + percent, Toast.LENGTH_SHORT).show();
    }
}

设action(这个不用额外设置权限)

从零开始水安卓——安卓四大组件BroadcastReceiver

然后模拟器上还是可能看不出来

不过这个可以抢救一下

改成动态注册(不监测电量变化,而是直接输出当前电量)

无视第三第四行...(是之前的遗留产物)

   @Override
    protected void onResume() {
        super.onResume();
        IntentFilter filter = new IntentFilter();
        filter.addAction("com.example.action.MY_BROADCAST");
        registerReceiver(myReceiver2, filter);

        //立即获取电量信息的方法
        IntentFilter intentFilter=new IntentFilter();
        intentFilter.addAction("android.intent.action.BATTERY_CHANGED");
        Intent intent=getApplicationContext().registerReceiver(null,intentFilter);
        //当前电量
        int curr = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0);
        //总电量
        int total = intent.getIntExtra(BatteryManager.EXTRA_SCALE, 1);
        //计算百分比
        int percent = curr * 100 / total;
        Toast.makeText(this, "当前电量为" + percent, Toast.LENGTH_SHORT).show();
    }

从零开始水安卓——安卓四大组件BroadcastReceiver效果如????

收发短信

安卓的收发短信其实也是通过广播(有序广播)来实现的,下面带来一组收发短信的案例。

发送短信

安卓中已经自带发送短信的应用,所以我们只需要进行集成调用即可。

  • 获取默认的消息管理器:SmsManager manager=SmsManager.getDefault()
  • 拆分长短信:ArrayList list=manager.divideMessage(String txt)
  • 发送短信:manager.sendTextMessage(String phone,null,String content,null,null)
  • 发送短信还需要设置权限  <uses-permission android:name="android.permission.SEND_SMS"/>

准备一个按钮

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/send"
        android:onClick="send"
        app:layout_constraintTop_toTopOf="parent"
        android:text="发送短信"/>

书写点击事件

package com.example.a4_5message;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.telephony.SmsManager;
import android.view.View;

import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    public void send(View view) {
        //获取短信管理器
        SmsManager smsManager = SmsManager.getDefault();
        String message = "你好,这是一条测试短信";
        //拆分长短信
        ArrayList<String> list = smsManager.divideMessage(message);
        int size = list.size();
        for (int i = 0; i < size; i++) {
            //第一个参数为电话号码
            smsManager.sendTextMessage("10086", null, list.get(i), null, null);
        }
    }
}

从零开始水安卓——安卓四大组件BroadcastReceiver 从零开始水安卓——安卓四大组件BroadcastReceiver

接收短信

之前提到了,安卓的短信也是靠广播(有序广播)来实现的,主要指的就是接收广播。

  • 接收该广播的Action:android.provider.Telephony.SMS_RECEIVED
  • 接收短信也需要设置权限  <uses-permission android:name="android.permission.RECEIVE_SMS"/>

创建接收器并在配置清单设置对应的权限及Action,见????(过程不表)

package com.example.a4_5message;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.telephony.SmsMessage;
import android.widget.Toast;

public class MyReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        Bundle bundle=intent.getExtras();
        if (bundle!=null){
            //通过pdus获得接收到的所有短信消息,获取短信内容
            Object[] objs= (Object[]) bundle.get("pdus");
            //构建短信对象数组
            SmsMessage[] smsMessages=new SmsMessage[objs.length];
            for (int i=0;i<objs.length;i++){
                //获取单挑短信内容,以pdu格式存储,并生成短信对象
                smsMessages[i]=SmsMessage.createFromPdu((byte[])objs[i]);
                //发送方的号码
                String number=smsMessages[i].getDisplayOriginatingAddress();
                System.out.println(number);
                //获取短信的内容
                String content=smsMessages[i].getDisplayMessageBody();
                System.out.println(content);
                Toast.makeText(context, number+"---"+content, Toast.LENGTH_LONG).show();
            }
            //黑名单...
            abortBroadcast();
        }
    }
}

注意:亲测上述方案只支持API23及以下

效果如下: 

从零开始水安卓——安卓四大组件BroadcastReceiver

从零开始水安卓——安卓四大组件BroadcastReceiver