【0047】Android基础-34-广播接收者BroadCastReceiver相关

【注意】

【1】在sd卡的广播和安装包apk的事件广播时,除了配置action时,还需要配置data数据;详见4.2和7.1;

【2】动态注册广播和静态注册广播的区别和好处;

【3】有序广播和无序广播:无序广播不可以被终止  数据不可以被修改

【4】红色日志的信息并非是错误;

【1】为什么需要广播接收者?

  ① Android系统内部相当于已经有一个电台,定义了好多的广播事件  

  比如外拨电话 短信到来 sd卡状态  电池电量变化....

  ②谷歌工程师给我们定义了一个组件专门用来接收这些事件的 

  ③谷歌工程师为什么要设计这样一个组件  目的就是为了方便开发者进行开发

【2】IP电话拨号器实例

【2.1】新建类起名称的时候以Receiver结尾,表明是一个广播接收者;

【2.2】同样定义一个广播接收者需要在清单文件中配置:配置一个action

当此动作发生的时候,就会调用override的receiver方法

 

可以配置多个action,这几个action中的一个触发,则都可以使得被复写OnReceive方法被调用;

【2.3】需要增加打电话的权限

【2.4】当有外拨电话的时候该onReceiver方法被执行;

【2.5】取到拨出去的电话号:getResultData()方法会拿到之前在配置清单中配置的action发生的receiver的数据

 

【2.6】增加要拨打的号:增加了判断,如果是以“0”开头的就加拨17951,否则不加拨;

【2.7】将加拨的号码可以通过用户输入完成

【2.7.1】页面布局

 

【2.7.2】业务逻辑,将用户输入的号码保存到sharedPreference中;

【注意】实际在MainActivity中获取到的sharedPreference实例是通过this(此对象拿到的),即MainActivity的上下文,因为MainActivity继承于context;

但是在OutGoingCallReceiver类中无法拿到sharedPreference实例;但是在onReceiver方法中的参数已经存在context上下文

通过上下文就可以拿到sharedPreference实例

【2.7.3】效果演示

【2.8】完整源码

【activity_main.xml】源码

 1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 2     xmlns:tools="http://schemas.android.com/tools"
 3     android:layout_width="match_parent"
 4     android:layout_height="match_parent"
 5     android:orientation="vertical"
 6     tools:context=".MainActivity" >
 7 
 8     <EditText
 9         android:id="@+id/et_number"
10         android:layout_width="match_parent"
11         android:layout_height="wrap_content"
12         android:hint="请输入要保存的ip号码" />
13 
14     <Button
15         android:onClick="click"
16         android:layout_width="wrap_content"
17         android:layout_height="wrap_content"
18         android:text="保存" />
19 
20 </LinearLayout>

 

【MainActivity.java】源码

 1 package com.itheima.ipdail;
 2 
 3 import android.os.Bundle;
 4 import android.app.Activity;
 5 import android.content.SharedPreferences;
 6 import android.view.Menu;
 7 import android.view.View;
 8 import android.widget.EditText;
 9 import android.widget.Toast;
10 
11 public class MainActivity extends Activity {
12 
13     private EditText et_number;
14 
15     @Override
16     protected void onCreate(Bundle savedInstanceState) {
17         super.onCreate(savedInstanceState);
18         setContentView(R.layout.activity_main);
19         et_number = (EditText) findViewById(R.id.et_number);
20     }
21 
22     // 点击按钮 获取用户输入的iP号码 进行保存
23     public void click(View v) {
24         // [1]获取到用户输入的number
25         String number = et_number.getText().toString().trim();
26         // [2]使用sp 保存起来
27         SharedPreferences sp = getSharedPreferences("config", 0);
28         // [3]存数据
29         sp.edit().putString("ipnumber", number).commit();
30         Toast.makeText(getApplicationContext(), "保存成功", 0).show();
31 
32     }
33 
34 }

 

【OutGoingCallReceiver】源码

 1 package com.itheima.ipdail;
 2 
 3 import android.content.BroadcastReceiver;
 4 import android.content.Context;
 5 import android.content.Intent;
 6 import android.content.SharedPreferences;
 7 
 8 /**
 9  * 定义一个广播接收者 就要在清单文件里面配置一下
10  * 
11  * @author jhon
12  * 
13  */
14 public class OutGoingCallReceiver extends BroadcastReceiver {
15 
16     // 当接收到外拨电话的事件的时候回执行这个方法
17     @Override
18     public void onReceive(Context context, Intent intent) {
19 
20         // [0]获取到我们保存的ip号码
21         SharedPreferences sp = context.getSharedPreferences("config", 0);
22         // [0.1]获取我们保存的ip号码
23         String ipnumber = sp.getString("ipnumber", "");
24 
25         // [1]获取当前我们要拨打的电话号码
26         String currentNumber = getResultData();
27 
28         // [1.1]判断拨打的电话是否是长途
29         if (currentNumber.startsWith("0")) {
30             // [2]在当前的号码前面加上一个17951
31             setResultData(ipnumber + currentNumber);
32         }
33 
34     }
35 
36 }

 

【AndroidManifest.xml】源码

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
 3     package="com.itheima.ipdail"
 4     android:versionCode="1"
 5     android:versionName="1.0" >
 6 
 7     <uses-sdk
 8         android:minSdkVersion="8"
 9         android:targetSdkVersion="17" />
10     <uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"/>
11 
12     <application
13         android:allowBackup="true"
14         android:icon="@drawable/ic_launcher"
15         android:label="@string/app_name"
16         android:theme="@style/AppTheme" >
17         <activity
18             android:name="com.itheima.ipdail.MainActivity"
19             android:label="@string/app_name" >
20             <intent-filter>
21                 <action android:name="android.intent.action.MAIN" />
22 
23                 <category android:name="android.intent.category.LAUNCHER" />
24             </intent-filter>
25         </activity>
26         <!--配置广播接收者   -->
27         
28         <receiver android:name="com.itheima.ipdail.OutGoingCallReceiver">
29             <intent-filter >
30                 <action android:name="android.intent.action.NEW_OUTGOING_CALL"/>
31             </intent-filter>
32         </receiver>
33         
34     </application>
35 
36 </manifest>

 

【3】总结BroadCastReceiver的使用方法

【3.1】(1)定义一个类继承BroadCastReceiver 

【3.2】(2)在清单文件里面配置一下 

【3.3】当我们配置的action 的事件发生了  onReceive方法就会执行

【4】sd卡状态的监听

【4.1】案例效果

【4.2】【清单文件的配置】必须配置data中的sheme项:在常用的操作中配置data选项的地方只有两个,此处算一处;

【4.3】源码

【SdcardReceiver.java】源码

 1 package com.itheima.sdcardstate;
 2 
 3 import android.content.BroadcastReceiver;
 4 import android.content.Context;
 5 import android.content.Intent;
 6 
 7 public class SdcardReceiver extends BroadcastReceiver {
 8 
 9     // 当sd卡被卸载 或者被挂载上来的时候 会执行
10     @Override
11     public void onReceive(Context context, Intent intent) {
12 
13         // [1]获取到当前广播的事件类型
14         String action = intent.getAction();
15         // [2]对action做一个判断
16         if ("android.intent.action.MEDIA_UNMOUNTED".equals(action)) {
17             System.out.println("说明sd卡 卸载了");
18 
19         } else if ("android.intent.action.MEDIA_MOUNTED".equals(action)) {
20 
21             System.out.println("说明sd卡挂载了");
22         }
23 
24     }
25 
26 }

【配置清单】源码

1  <receiver android:name="com.itheima.sdcardstate.SdcardReceiver">
2             <intent-filter >
3                 <action android:name="android.intent.action.MEDIA_UNMOUNTED"/>
4                 <action android:name="android.intent.action.MEDIA_MOUNTED"/>
5                  <action android:name="android.intent.action."/>
6                 <!--想让上面的这2个事件生效 必须的加上这样的一个data   -->
7                 <data android:scheme="file"/>
8             </intent-filter>
9  </receiver>

【5】广播接收者的工作原理

【说明】实际的监听程序在后台仍然可以进行监听事件的发生;(sd卡监听程序在后台运行,当sd卡发生变化时,仍可以判断状态的变化)

 其实:杀死本监听程序之后,仍然可以接收到事件发生时传来的状态;

这样做的好处利于开发者的开发,当然也有不好的地方,下面的短信监听器就是案例;

【6】短监听案例

【6.1】配置权限-此处的权限配置在列表中没有,但是可以复制粘贴使用;

1 <!--配置拦截短信的action-->
2         <receiver android:name=".SMSListenerReceiver">
3             <intent-filter>
4                 <action android:name="android.provider.Telephony.SMS_RECEIVED"/>
5             </intent-filter>
6 </receiver>

 

【6.2】配置权限

【6.3】在接收到短信的OnReceive方法确实被调用了;

【6.4】获取短信的发送联系人和短信的数据

【pdu】一条短信信息的封装结果就是一个pdu;

 1 package com.example.administrator.a10smslistener;
 2 
 3 import android.content.BroadcastReceiver;
 4 import android.content.Context;
 5 import android.content.Intent;
 6 import android.telephony.SmsManager;
 7 import android.telephony.SmsMessage;
 8 
 9 /**
10  * Created by Administrator on 2017-10-29.
11  */
12 
13 public class SMSListenerReceiver extends BroadcastReceiver {
14     @Override
15     public void onReceive(Context context, Intent intent) {
16         System.out.println("haha");
17 
18         //1.获取短信的发送的号码和内容
19         Object[] objects = (Object[]) intent.getExtras().get("pdus");
20         for (Object pdu : objects) {
21             //2.获取smsMessage实例
22             SmsMessage message = SmsMessage.createFromPdu((byte[]) pdu);
23             //3.获取短信的内容
24             String messageBody = message.getMessageBody();
25             //4.获取发送者
26             String originatingAddress = message.getOriginatingAddress();
27             System.out.println("body" + messageBody + "========" + originatingAddress);
28         }
29 
30     }
31 }

 

 

【6.5】删除此配置选项则不会出现app登陆的界面;

在Android4.0之后,如果在第一次程序安装的时候没有此登陆界面,则该应用无法接受到广播;

【6.6】在Android4.0之后,点击了forceStop则应用的广播事件则无法生效

【7】广播接收者案例_卸载安装

 这样做的意义:可以收集安装包的包名,然后可以收集大数据;

【7.1】配置data【清单文件的配置】必须配置data中的sheme项:在常用的操作中配置data选项的地方只有两个,此处算第2处处;

【7.2】程序安装和卸载的判断

【7.3】getData

【8】手机重启案例

【应用场景】在某些特殊的行业开发的APP,在设备上开机之后,只是用当前公司开发的APP;

 方法:开机之后监测

【8.1】清单配置

 

【8.2】权限配置

【8.3】bug:从BroadCastReceiver中跳转到到Activity中需要配置flags;

【出现问题的现象】

【原因】需要intent的flag;一般在进行跳转的时候都是在一个Activity中跳转到另外一个Activity;

此处是在BroadCastReceiver中跳转到到Activity中;需要配置flags;

【说明】在android7.1系统中该选项改为了警告:

不添加也是可以的,但是最好还是添加上;

【解决办法】

【8.4】通过隐式意图可以做一些开机之后就进入的事情:比如屏蔽掉导航按键、通知栏的功能的等等;

【9】自定义广播:有序广播和无序广播

【9.1】无序广播:只要发送广播就行,不用管是否有人接收;

【9.1.1】 无序广播:类似新闻联播 

【源码】6_发送一条无序广播/src/com/itheima/sendbroadcast/MainActivity.java

 1 package com.itheima.sendbroadcast;
 2 
 3 import android.os.Bundle;
 4 import android.app.Activity;
 5 import android.content.Intent;
 6 import android.view.Menu;
 7 import android.view.View;
 8 
 9 public class MainActivity extends Activity {
10 
11     @Override
12     protected void onCreate(Bundle savedInstanceState) {
13         super.onCreate(savedInstanceState);
14         setContentView(R.layout.activity_main);
15     }
16 
17     //点击按钮发送一条无序广播
18     public void click(View v) {
19         
20         Intent intent = new Intent();
21         //设置action 
22         intent.setAction("com.itheima.custombroadcast");
23         
24         intent.putExtra("name", "新闻联播每天晚上7点准时开整!!!");
25         //发送一条广播 发送无序广播
26         sendBroadcast(intent);
27         
28     }
29 }

 

 

【注】在发送无序广播的时候需要设置action;否则在接收广播的程序中无法配置清单文件中的action选项;

【9.1.2】无序广播不可以被终止  数据不可以被修改

【红色日志的信息】报告说:在无序广播中无需终止;并非是错误;

【9.2】接受无序广播

【/7_接收自定义广播/src/com/itheima/receivebroadcast/ReceiveCustomReceiver.java】源码

 1 package com.itheima.receivebroadcast;
 2 
 3 import android.content.BroadcastReceiver;
 4 import android.content.Context;
 5 import android.content.Intent;
 6 import android.widget.Toast;
 7 
 8 public class ReceiveCustomReceiver extends BroadcastReceiver {
 9 
10     //当我发送自定义广播的时候 这个方法就会接收到 
11     @Override
12     public void onReceive(Context context, Intent intent) {
13 
14         //[0]终止广播
15 //        abortBroadcast();
16         
17         //[1]取出我们发送广播携带的数据 
18         String content = intent.getStringExtra("name");
19         //[2]把获取到的数据展示到toast上
20         Toast.makeText(context, content, 0).show();
21         
22     }
23 
24 }

【7_接收自定义广播/AndroidManifest.xml】源码

1         <receiver android:name="com.itheima.receivebroadcast.ReceiveCustomReceiver">
2             <intent-filter >
3                 <action android:name="com.itheima.custombroadcast"/>
4             </intent-filter>
5         </receiver>

【9.3】有序广播:类似中央发送的红头文件  按照优先级一级一级的接收 有序广播可以被终止 数据可以被修改 

【9.3.1】发有序广播

 

【配置优先级】可以层层递进,每层的优先级不同:1000到-1000;

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
 3     package="com.itheima.receiverice"
 4     android:versionCode="1"
 5     android:versionName="1.0" >
 6 
 7     <uses-sdk
 8         android:minSdkVersion="8"
 9         android:targetSdkVersion="17" />
10 
11     <application
12         android:allowBackup="true"
13         android:icon="@drawable/ic_launcher"
14         android:label="@string/app_name"
15         android:theme="@style/AppTheme" >
16         <activity
17             android:name="com.itheima.receiverice.MainActivity"
18             android:label="@string/app_name" >
19             <intent-filter>
20                 <action android:name="android.intent.action.MAIN" />
21 
22                 <category android:name="android.intent.category.LAUNCHER" />
23             </intent-filter>
24         </activity>
25         
26         <!--配置广播接收者  -->
27         <receiver android:name="com.itheima.receiverice.ProvienceReceiver">
28             <intent-filter 
29                 android:priority="1000"
30                 >
31                 <action android:name="com.itheima.rice"/>
32             </intent-filter>
33         </receiver>
34         
35          <receiver android:name="com.itheima.receiverice.CityReceiver">
36             <intent-filter 
37                 android:priority="800"
38                 >
39                 <action android:name="com.itheima.rice"/>
40             </intent-filter>
41         </receiver>
42         
43              <receiver android:name="com.itheima.receiverice.CountryReceiver">
44             <intent-filter 
45                 android:priority="100"
46                 >
47                 <action android:name="com.itheima.rice"/>
48             </intent-filter>
49         </receiver>
50          <receiver android:name="com.itheima.receiverice.NongMinReceiver">
51             <intent-filter 
52                 android:priority="10"
53                 >
54                 <action android:name="com.itheima.rice"/>
55             </intent-filter>
56         </receiver>
57         
58     </application>
59 
60 </manifest>

 

【/9_接收大米/src/com/itheima/receiverice/ProvienceReceiver.java】源码

 1 package com.itheima.receiverice;
 2 
 3 import android.content.BroadcastReceiver;
 4 import android.content.Context;
 5 import android.content.Intent;
 6 import android.widget.Toast;
 7 
 8 public class ProvienceReceiver extends BroadcastReceiver {
 9 
10     @Override
11     public void onReceive(Context context, Intent intent) {
12         
13         //[1]获取到发送广播携带的数据 
14         String content = getResultData();
15         
16         //[2]展示到Toast上
17         Toast.makeText(context, "省:"+content, 1).show();
18         
19         //[2.1]终止广播 
20 //        abortBroadcast();
21         
22         //[3]修改数据 (扣留大米)
23         setResultData("给每个村民发了500斤大米");
24         
25         
26     }
27 
28 }

 

【/9_接收大米/src/com/itheima/receiverice/CityReceiver.java】源码

 1 package com.itheima.receiverice;
 2 
 3 import android.content.BroadcastReceiver;
 4 import android.content.Context;
 5 import android.content.Intent;
 6 import android.widget.Toast;
 7 
 8 public class CityReceiver extends BroadcastReceiver {
 9 
10     @Override
11     public void onReceive(Context context, Intent intent) {
12 
13         //[1]获取到发送广播携带的数据 
14         String content = getResultData();
15         
16         //[2]展示到Toast上
17         Toast.makeText(context, "市:"+content, 1).show();
18         
19         //[3]修改数据 (扣留大米)
20         setResultData("给每个村民发了200斤大米");
21         
22     }
23 
24 }

 

【/9_接收大米/src/com/itheima/receiverice/NongMinReceiver.java】源码

 1 package com.itheima.receiverice;
 2 
 3 import android.content.BroadcastReceiver;
 4 import android.content.Context;
 5 import android.content.Intent;
 6 import android.widget.Toast;
 7 
 8 public class NongMinReceiver extends BroadcastReceiver {
 9 
10     @Override
11     public void onReceive(Context context, Intent intent) {
12         
13         //[1]获取到发送广播携带的数据 
14         String content = getResultData();
15         
16         //[2]展示到Toast上
17         Toast.makeText(context, "农民:"+content, 1).show();
18     }
19 
20 }

 

 【9.3.2】终止有序广播-广播终止与某一级

 

【9.3.3】定义广播的最终接受者:

 

【9.4】判断有序广播和无序广播的方法:

     添加终止语句,如果接收不到信息则为有序广播,否则为无序广播;

【10】 特殊广播接收者: 比如操作特别频繁的广播事件 屏幕的锁屏和解锁 电池电量的变化 这样的广播接收者在清单文件里面注册无效

09-08 07:25:42.239: E/ActivityThread(2173): Activity com.itheima.screen.MainActivity has leaked IntentReceiver com.itheima.screen.ScreenReceiver@b6486088 that was originally registered here. Are you missing a call to
 unregisterReceiver()?

【10.1】【实例】特殊的事件接受者:在清单文件中注册无效

【10.2】动态注册

【/10_特殊的广播接收者/src/com/itheima/screen/MainActivity.java】源码

 1 package com.itheima.screen;
 2 
 3 import android.os.Bundle;
 4 import android.app.Activity;
 5 import android.content.IntentFilter;
 6 import android.view.Menu;
 7 
 8 public class MainActivity extends Activity {
 9 
10     private ScreenReceiver screenReceiver;
11 
12     @Override
13     protected void onCreate(Bundle savedInstanceState) {
14         super.onCreate(savedInstanceState);
15         setContentView(R.layout.activity_main);
16 
17         /*
18          * <receiver android:name="com.itheima.screen.ScreenReceiver">
19          * <intent-filter > <action
20          * android:name="android.intent.action.SCREEN_OFF"/> <action
21          * android:name="android.intent.action.SCREEN_ON"/> </intent-filter>
22          * </receiver>
23          */
24 
25         // [1]动态的去注册屏幕解锁和锁屏的广播
26         screenReceiver = new ScreenReceiver();
27         // [2]创建intent-filter对象
28         IntentFilter filter = new IntentFilter();
29         // [3]添加要注册的action
30         filter.addAction("android.intent.action.SCREEN_OFF");
31         filter.addAction("android.intent.action.SCREEN_ON");
32         // [4]注册广播接收者
33         this.registerReceiver(screenReceiver, filter);
34 
35     }
36 
37     @Override
38     protected void onDestroy() {
39         // 当activity销毁的时候 取消注册广播接收者
40         unregisterReceiver(screenReceiver);
41 
42         super.onDestroy();
43     }
44 
45 }

【/10_特殊的广播接收者/src/com/itheima/screen/ScreenReceiver.java】源码

 1 package com.itheima.screen;
 2 
 3 import android.content.BroadcastReceiver;
 4 import android.content.Context;
 5 import android.content.Intent;
 6 
 7 public class ScreenReceiver extends BroadcastReceiver {
 8 
 9     @Override
10     public void onReceive(Context context, Intent intent) {
11 
12         // [1]获取到当前广播的事件类型
13         String action = intent.getAction();
14         // [2]对当前广播事件类型做一个判断
15 
16         if ("android.intent.action.SCREEN_OFF".equals(action)) {
17 
18             System.out.println("屏幕锁屏了");
19         } else if ("android.intent.action.SCREEN_ON".equals(action)) {
20 
21             System.out.println("说明屏幕解锁了~~~");
22         }
23 
24     }
25 
26 }

 

 

【10.3】这种程序的特点:在程序退出之后,广播发生时,广播接受者将无法接受到广播;

【原因】这种做的好处是节省设备的内存;

 静态注册的程序在广播在发生的时候,会直接唤醒应用程序,占用内存;

如果是动态注册的程序仍然唤醒应用程序,则一天会频繁的占用内存,对内存的占用空间较大;

如果同时100个应用同时是动态注册,则系统可能会出现崩溃;

 

posted @ 2017-10-28 16:28  OzTaking  阅读(516)  评论(0)    收藏  举报