`
XiangdongLee
  • 浏览: 86901 次
  • 性别: Icon_minigender_1
  • 来自: 长沙
社区版块
存档分类
最新评论

【攻克Android (33)】四大组件之 BroadcastReceiver

 
阅读更多
本文围绕以下三个部分展开:

一、广播
案例一:系统广播(属于普通广播):手机电池电量小于15%时进行提醒
案例二:自定义普通广播






一、广播

        1. 概念:

        广播(broadcasting)是多点投递的最普遍的形式,它向每一个目的站投递一个分组的拷贝。它可以通过多个单次分组的投递完成,也可以通过单独的连接传递分组的拷贝,直到每个接收方均收到一个拷贝为止。

        每个广播电台播放的内容都不相同。接受广播时广播(发送方)并不在意我们(接收方)接收到广播时如何处理。好比我们收听交通电台的广播,电台中告诉我们现在在交通状况如何,但它并不关心我们接收到广播时做如何做出处理,这不是广播应该关心的问题。

        2. Android 广播的优势:

        (1)扩展了Android中的事件模型,提高了应用程序的可扩展性。

        (2)方便了不同应用程序共享数据。

        (3)提高了应用程序的运行效率。

        3. BroadcastReceiver:

        BroadcastReceiver是Android整个系统中通用的发布/订阅机制(更确切地说,Observer模式)的实现。意思是接收者(Receiver)订阅一些事件,在事件发生时做出一定响应。

        系统自身时时刻刻广播着一些事件。比如收到短信、来了一个电话、电量不足或者系统启动等事件发生的时候,它们都是通过广播传递给每个接收者。

        广播可以在不同的应用之间传递;也可以在同一应用的不同Activity之间传递;还可以在应用与服务之间传递。

        Broadcast Receriver并无任何可见的界面(不同于Activity),也并非常驻于内存中执行(不同于Service)。它只会在事件发生时执行一段代码,做些启动一个Activity/Service之类的操作。

        每次广播消息到来时都会创建BroadcastReceiver实例并执行onReceive() 方法。

        广播接收者(BroadcastReceiver)用于接收广播Intent,通常一个广播Intent可以被订阅了此Intent的多个广播接收者所接收。



        4. 广播的分类。

        (1)系统广播、自定义广播。

        A. 系统内部已经定义了很多广播消息类型,例如电池电量低、屏幕开启或者关闭、系统引导完成等。

        系统内部广播这些消息使用的是 sendBroadcast()。

        当系统发送这些广播后,同样经过 Intent 匹配找到相应的 Receiver 对象并启动。这与 Activity 或者 Service 一样。

        Receiver对象接收的消息本质上是Intent。

        多数应用中,Receiver接收的是系统发出的消息。

        附:系统广播可以捕捉系统发出的行为有:



        B. 自定义广播包括自定义普通广播、自定义有序广播。

        自定义广播无非是给Intent对象的Action字段赋予自定义的值而已,不能与系统内部的消息名称重复,并在Receiver对象的intent-filter中使用相同的Action值进行匹配。一般自定义广播命名时,可以使用本程序包名作为前缀,以免与其它程序定义的广播发生命名冲突。

        (2)普通广播(Normal broadcasts)、有序广播(Ordered broadcasts)。

            A. 前者是完全异步的,所有接收者(逻辑上)都在同一时刻运行,对消息传递的效率而言这是很好的做法。
            但缺点是:接收者不能将处理结果传递给下一个接收者,并且无法终止广播Intent的传播。
            然而后者是逐个执行接收者:系统会按照接收者声明的优先级别
(声明在intent-filter元素的android:priority属性中,数越大优先级别越高,取值范围:-1000到1000。也可以调用IntentFilter对象的setPriority()进行设置),按顺序逐次执行。


            B. 发送广播:

            Context.sendBroadcast()

            发送的是普通广播,所有订阅者都有机会获得并进行处理。

            Context.sendOrderedBroadcast()

            发送的是有序广播,系统会根据接收者声明的优先级别按顺序逐个执行接收者,前面的接收者有权终止广播(BroadcastReceiver.abortBroadcast())。如果广播被前面的接收者终止,后面的接收者就再也无法获取到广播。对于有序广播,前面的接收者可以将处理结果存放进广播Intent,然后传给下一个接收者。

        (3)静态广播、动态广播。

            静态广播:使用静态注册方式注册的广播。

            动态广播:使用动态注册方式注册的广播。

        5. 广播的实现。

        (1)继承BroadcastReceiver,并重写onReceive()方法。

        (2)注册BroadcastReceiver。

        注册的方法有两种:

            1)静态注册(在功能清单文件中的<application>节点里进行注册)。

<receiver android:name=".IncomingSMSReceiver">
    <intent-filter>
         <action android:name="android.provider.Telephony.SMS_RECEIVED"/>
    </intent-filter>
</receiver>


            2)动态注册(使用代码进行进行注册)。

IntentFilter filter = new IntentFilter("android.provider.Telephony.SMS_RECEIVED");
IncomingSMSReceiver receiver = new IncomingSMSReceiver();
registerReceiver(receiver, filter);


            3)两种注册方式的比较:

            A. 动态注册的广播 永远要快于 静态注册的广播。不管静态注册的优先级设置的多高,不管动态注册的优先级有多低。

            B. 动态注册广播不是 常驻型广播 ,也就是说广播跟随activity的生命周期。注意: 在activity结束前,移除广播接收器。
            静态注册是常驻型 ,也就是说当应用程序关闭后,如果有信息广播来,程序也会被系统调用自动运行。

            C. 在同一个优先级下,谁先启动的快,谁将先接收到广播。

        6. 处理耗时任务。

            在Android中,程序的响应(Responsive)被活动管理器(Activity Manager)和窗口管理器(Window Manager)这两个系统服务所监视。当BroadcastReceiver在10秒内没有执行完毕,Android会认为该程序无响应。所以在BroadcastReceiver里不能做一些比较耗时的操作,否侧会弹出ANR(Application No Response)的对话框。

            如果需要完成一项比较耗时的工作,应该通过发送Intent给Service,由Service来完成,而不是使用子线程的方法来解决。因为BroadcastReceiver的生命周期很短(在onReceive() 执行后BroadcastReceiver 的实例就会被销毁),子线程可能还没有结束它就先结束了。当然如果BroadcastReceiver结束了,它的宿主进程还在运行,子线程还会继续执行。但宿主进程此时很容易在系统需要内存时被优先杀死,因为它属于空进程(没有任何活动组件的进程)。

public class IncomingSMSReceiver extends BroadcastReceiver {
	@Override 
	public void onReceive(Context context, Intent intent) {
        //发送Intent启动服务,由服务来完成比较耗时的操作
        Intent service = new Intent(context, XxxService.class);
        context.startService(service);
	}
}



案例一:系统广播(属于普通广播):手机电池电量小于15%时进行提醒







        1. activity_main.xml。写一个 TextView,用于显示当前电池电量。

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/tvBatteryChanged"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/app_name"
        android:textSize="30sp" />

</RelativeLayout>


        2. MainActivity。声明和在onCreate()中初始化 TextView。

    // 1.1 声明变量(显示电池电量的文本)
    private TextView tvBatteryChanged;


        // 1.2 初始化变量(显示电池电量的文本)
        tvBatteryChanged = (TextView) findViewById(R.id.tvBatteryChanged);


        3. MainActivity。【一、定义广播】定义匿名的广播接收者(接收手机电量不足的通知)。

    // 2. 定义匿名的广播接收者(接收手机电量不足的通知)
    private BroadcastReceiver batteryChangedReceiver = new BroadcastReceiver() {
        /**
         * 在 BroadcastReceiver 接收到与之匹配的广播消息后,onReceive()方法会被调用
         *
         *  onReceive()方法必须要在5秒钟执行完毕,
         * 否则 Android 系统会认为该组件失去响应,并提示用户强行关闭该组件
         *
         * @param context  设置BroadcastReceiver实例
         * @param intent   Receiver对象接收的消息
         */
        @Override
        public void onReceive(Context context, Intent intent) {

        }
    };


        4. MainActivity。【二、注册广播】在onCreate()中注册电量不足的广播接收者。

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

        // 1.2 初始化变量(显示电池电量的文本)
        tvBatteryChanged = (TextView) findViewById(R.id.tvBatteryChanged);

        // 3. 注册电量不足的广播接收者
        // 参数一:指定广播接收者
        // 参数二:新建意图过滤器,在过滤器中写入 广播的 action(动作)
        //        Receiver对象接收的消息本质上是Intent
        this.registerReceiver(batteryChangedReceiver,
                new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
    }


        5. MainActivity。【三、处理操作】在onReceive() 方法中。

// 4. 处理操作

            // 经过 Intent 匹配找到相应的 Receiver 对象并启动。
            if (Intent.ACTION_BATTERY_CHANGED.equals(intent.getAction())) {
                /**
                 * getIntExtra (String name, int defaultValue)
                 * Retrieve(再次获得,取回) extended data from the intent.
                 * 获得指定参数的值,若值为 null,则返回默认值 0
                 *
                 */

                // level:当前电池电量
                int level = intent.getIntExtra("level", 0);
                // 电池总电量
                int scale = intent.getIntExtra("scale", 100);
                // 电量文本
                String text = "电池电量:" + (level * 100 / scale) + " %";
                // 在 TextView 中显示出当前电池电量
                tvBatteryChanged.setText(text);

                // 判断:当电量小于等于15%时触发:通过 Toast 输出通知
                if (level <= 15) {
                    // 因为这是在内部类中,而 Toast 输出是在 MainActivity 中,
                    // 因此写为 MainActivity.this
                    Toast.makeText(MainActivity.this, "当前电量已小于15%",
                            Toast.LENGTH_LONG).show();
                }
            }



        代码补充:

        MainActivity。

package com.android.mybroadcast;

import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.TextView;
import android.widget.Toast;

/*
    系统内部定义的广播消息,可以查阅官方文档:
    C:\android\android-sdk-windows\docs\reference\android\content\Intent.html
 */
public class MainActivity extends Activity {
    // 1.1 声明变量(显示电池电量的文本)
    private TextView tvBatteryChanged;

    // 2. 定义匿名的广播接收者(接收手机电量不足的通知)
    private BroadcastReceiver batteryChangedReceiver = new BroadcastReceiver() {
        /**
         * 在 BroadcastReceiver 接收到与之匹配的广播消息后,onReceive()方法会被调用
         *
         *  onReceive()方法必须要在5秒钟执行完毕,
         * 否则 Android 系统会认为该组件失去响应,并提示用户强行关闭该组件
         *
         * @param context  设置BroadcastReceiver实例
         * @param intent   Receiver对象接收的消息
         */
        @Override
        public void onReceive(Context context, Intent intent) {
            // 4. 处理操作

            // 经过 Intent 匹配找到相应的 Receiver 对象并启动。
            if (Intent.ACTION_BATTERY_CHANGED.equals(intent.getAction())) {
                /**
                 * getIntExtra (String name, int defaultValue)
                 * Retrieve(再次获得,取回) extended data from the intent.
                 * 获得指定参数的值,若值为 null,则返回默认值 0
                 *
                 */

                // level:当前电池电量
                int level = intent.getIntExtra("level", 0);
                // 电池总电量
                int scale = intent.getIntExtra("scale", 100);
                // 电量文本
                String text = "电池电量:" + (level * 100 / scale) + " %";
                // 在 TextView 中显示出当前电池电量
                tvBatteryChanged.setText(text);

                // 判断:当电量小于等于15%时触发:通过 Toast 输出通知
                if (level <= 15) {
                    // 因为这是在内部类中,而 Toast 输出是在 MainActivity 中,
                    // 因此写为 MainActivity.this
                    Toast.makeText(MainActivity.this, "当前电量已小于15%",
                            Toast.LENGTH_LONG).show();
                }
            }
        }
    };

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

        // 1.2 初始化变量(显示电池电量的文本)
        tvBatteryChanged = (TextView) findViewById(R.id.tvBatteryChanged);

        // 3. 注册电量不足的广播接收者
        // 参数一:指定广播接收者
        // 参数二:新建意图过滤器,在过滤器中写入 广播的 action(动作)
        //        Receiver对象接收的消息本质上是Intent
        this.registerReceiver(batteryChangedReceiver,
                new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
    }

    // -------------------------------------------------------------------
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }
}



案例二:自定义普通广播







        1. 创建 BroadcastReceiver:NewsReceiver。【一、创建 BroadcastReceiver】

        2. 在功能清单中注册自定义 BroadcastReceiver。(创建的时候,自动注册好了)【二、注册自定义 BroadcastReceiver】

        3. activity_main.xml。写一个按钮。

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/btnSendBroadcast"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="onClick"
        android:text="@string/sendBroadcast" />

</RelativeLayout>


        4. MainActivity。在按钮点击事件中发送广播。(要有 intent意图、附件信息 等)

    /**
     * 按钮点击事件
     * @param view
     */
    public void onClick(View view){
        // (4)发送普通广播

        // (4.1)新建 intent
        Intent intent = new Intent();
        // (4.2)设置 intent 的 action 动作 (设置发送的广播频道)
        intent.setAction("android.intent.action.NEWS");
        // (4.3)在功能清单文件中,注册 意图过滤器(广播接收者 这一端 设置 接收的广播频道)
        // (4.4)附加信息
        intent.putExtra("msg", "今天大部分地区将迎来强降雨天气");
        // (4.5)发送 广播
        sendBroadcast(intent);
    }


        其中,(4.3)如下:

        <!-- (2) 注册自定义 BroadcastReceiver (在功能清单中) -->
        <receiver android:name=".NewsReceiver" >
            <!--
            (4.3)注册 意图过滤器(广播接收者 这一端 设置 接收的广播频道)
            -->
            <intent-filter>
                <action android:name="android.intent.action.NEWS" />
            </intent-filter>
        </receiver>


        如果 注册中的 intent 和 发送的广播中的 intent 是一样的(相当于观众把频道调到了电台广播频道),就可以接收到广播。

        5. NewsReceiver。接收广播。

package com.android.mybroadcast;

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

/**
 * 新闻联播的观众
 */
public class NewsReceiver extends BroadcastReceiver {
    public NewsReceiver() {
    }

    /**
     * 重写 onReceive() 方法
     * @param context
     * @param intent
     */
    @Override
    public void onReceive(Context context, Intent intent) {
        // (5)获得 Intent意图 中的附加信息
        String msg = intent.getStringExtra("msg");
        // (6)Toast显示广播
        Toast.makeText(context, "收到新闻联播的广播:" + msg,
                Toast.LENGTH_LONG).show();
    }
}
  • 大小: 18.7 KB
0
1
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics