1.广播和广播接收者

    Android中的广播和广播接收者相当于现实生活中的电台和听收音机的人,

   Android系统内部相当于已经定义好了电台, 就是内部定义好了一些事件(外拨电话,短信到来 

 电池电量低 sd卡状态 卸载安装  开机启动等等),我们开发者只需要注册这个事件就ok了(注册事

 件就是创建广播接收者之后在清单里配置意图过滤器的action属性,相当于听收音机的人调频一样)

   更确切的说,广播接收者就是Android系统的全局监听者,可以监听系统的很多事件,只要广播

 接收者注册了这些事件,就会被这些事件触发。

   ★广播接收者被触发,底层原理应该是广播事件会利用隐式意图启动符合条件的广播接收者. 

 

2.注册广播接收者

   注册广播接收者有2种方式,而且一个广播接收者配置多个过滤动作Action。

   ★第1种:在清单里注册

    在配置文件中注册的接收者的特点是即使应用程序已被关闭,该接收者依然可接受它感兴趣的广播

                上面红色特性,已经验证过,确实如此!

    一般大多数的广播接收者都是在清单文件中注册,只有少量的广播接收者是在代码里注册


    当然可以在清单里注册的广播接收者,也一定能够在代码里注册。  

    wKioL1W6rLrBDM5LAAJEDutJOFw011.jpg

   ★第2种:在Activity里注册

     在Activity中绑定接收者必须依附该应用程序存在,或者一个BroadcastReceiver用于更新UI,就没有必要在

                程序关闭时接收者还运行,故无需在AndroidManifest.xml中注册而可以放在Activity中注册。 


      操作特别频繁的广播事件  比如 锁屏和解锁这种广播接收者在清单文件里面注册是无效的

    必需在代码里注册,否则会报下面这个异常。

      MainActivity has leaked IntentReceiver

      ScreenReceiver@b65bb500 that was originally registered here. Are you missing a

       call to unregisterReceiver()? 


      代码里注册广播接收者

      首先,定义一个类继承BroadcastReceiver:

        public class ScreenReceiver extends BroadcastReceiver

      下一步,在一个Activity中注册上面定义的广播接收者  

        ScreenReceiver screenReceiver = new ScreenReceiver();
		//创建意图对象过滤器
		IntentFilter intentFilter = new IntentFilter();
		intentFilter.addAction("android.intent.action.SCREEN_OFF");
		intentFilter.addAction("android.intent.action.SCREEN_ON");
		//注册锁屏和解锁广播
		registerReceiver(screenReceiver, intentFilter);

 
3.广播接收者开发步骤

   第1步:定义一个类继承广播接收者,并在清单或代码里注册,包括receiver的name属性、

       过滤器的Action属性。

    第2步:复写定义的广播接收者类的onReceive方法,定义广播接收者被触发后想实现的逻辑。

       一般如果广播接收者注册了多个事件,还得先判断一下事件的类型。

            public void onReceive(Context context, Intent intent) {  
                //得到事件的类型
		String action = intent.getAction();
            }


案例:注意不同的案例获取数据的方式

4.案例1_IP拨号器(外拨电话)

   ■清单配置

    <receiver android:name="com.itheima.ipdialerListener.OutGoingReceiver" >
      <intent-filter>
         <!-- 配置action new outgoing call -->
         <action android:name="android.intent.action.NEW_OUTGOING_CALL"/>
      </intent-filter>
    </receiver>

   ■权限 

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

   ■广播接收者的接收后的逻辑  

    public class OutGoingReceiver extends BroadcastReceiver {

	@Override
	public void onReceive(Context context, Intent intent) {
		//获取广播事件的数据-电话号码
		String phoneNumber = getResultData();   //直接调用BroadcastReceiver的方法
		System.out.println("phoneNumber:" + phoneNumber);
		
		//假如电话号码开头是0,就是长途。
		if(phoneNumber.startsWith("0"))
		{
			phoneNumber = "95128" + phoneNumber;
		}
		
		//改变广播当前的数据-用修改过后电话号码替换。
		setResultData(phoneNumber);   //注意这个方法只能与有序广播联用 
	}
    }

  将应用程序安装好,拨打一个手机号以0开始的电话号码0123,点击拨打按钮,效果如下所示:

  wKiom1XA2BHASpx-AADrCjxS6ls348.jpg



5.案例2_sd卡状态监听

      

   ■清单配置

     注意过滤器里多了一个<data android:scheme="file"/>

     <receiver android:name="com.itheima.sdcard.SDCardReceiver" >
            <intent-filter>
                <action android:name="android.intent.action.MEDIA_MOUNTED"/>
                <action android:name="android.intent.action.MEDIA_UNMOUNTED"/>
                <data android:scheme="file"/>
            </intent-filter>
        </receiver>

   ■权限 

     无

   ■广播接收者的接收后的逻辑  

    public class SDCardReceiver extends BroadcastReceiver {

        	@Override
        	public void onReceive(Context context, Intent intent) {
        	        //因为清单里有2个action,所以要获取action的类型。
        		String action = intent.getAction();
        		if(Intent.ACTION_MEDIA_MOUNTED.equals(action))
        		{
        			System.out.println("sd卡挂载了");
        		}else if(Intent.ACTION_MEDIA_UNMOUNTED.equals(action)){
        			System.out.println("sd卡卸载了");
        		}else {
        			System.out.println("sd监听出错了");
        		}
        	}
        }

   进入2.3这些低版本的模拟器,settings-storage里面可以模拟sd的挂载与卸载。

    wKioL1XA3XjB7DcVAAB1Pmxdrmk528.jpg



6.案例3_短信监听器

    短信监听的底层原理,应该是短信数据库内容提供者者安插了内容观察者,一旦手机接收到短

  信,势必短信数据库会发生变化,内容观察者分析到了就会发送一条短信接收的广播,并且把短

  信相关的信息封装在了意图对象里。  

   ■清单配置

      高版本的ADT提示里没有action对应的选项,应从低版本ADT拷贝action值到清单中。

    <receiver android:name="com.itheima.msglistener.MsgListener" >
            <intent-filter>
                <action android:name="android.provider.Telephony.SMS_RECEIVED"/>
            </intent-filter>
        </receiver>

   ■权限 

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

   ■广播接收者的接收后的逻辑  

     注意SmsMessage对象所在的包   

    //获取短信数据,可能有多条短信数据吧。
	//由此可见,短信的数据在intent里的封装的格式是Bundle,也就是map集合。
		Object[] object = (Object[]) intent.getExtras().get("pdus");
		
	//创建SmsMessage对象来分离短信的各项内容 ,注意SmsMessage所在的包。
		for (Object obj : object) {
		    //创建SmsMessage实例,解析短信信息:发送者、短信内容
			SmsMessage smsMessage = SmsMessage.createFromPdu((byte[]) obj);
			String messageBody = smsMessage.getMessageBody();
			String originatingAddress = smsMessage.getOriginatingAddress();
			System.out.println("短信发送者:" + originatingAddress);
			System.out.println("短信内容:" + messageBody);
		}

   

    关于短信监听,android2.3版本时没有考虑到安全问题,4.0之后,考虑到安全问题,广播接

  收者需要有一个启动界面才能生效(但不一定要求界面就要启动,也就是说只要有个activity就行了

  )。

    对于用户来说,在2.3这些低版本设备上,对于没有界面的短信监听应用,如果一旦有短信发送过

  来,应用就会被激活,在setting中的forcestop按钮也才会变得可选,可以在setting中点击这个按

  钮就能关闭应用

    wKioL1XCG7SQ_Fs_AAC328auDug639.jpg

    对于流氓程序员来说,不但可以做个没有启动界面和图标的应用,更可以结合卸载监听事件,不让

  安装的流氓应用卸载,还可以加个密码确认之后才能输入,要多流氓就有多流氓。

 

7.案例4_应用安装与卸载的监听

   

   ■清单配置

      注意过滤器多了一个<data android:scheme="package" />

     <receiver android:name="com.itheima.packageaddandremove.PackageReceiver" >
            <intent-filter>
                <action android:name="android.intent.action.PACKAGE_ADDED" />
                <action android:name="android.intent.action.PACKAGE_REMOVED" />
                <data android:scheme="package" />
            </intent-filter>
        </receiver>

   ■权限 

     无

   ■广播接收者的接收后的逻辑  

    //得到包名
		Uri packageName  = intent.getData();
		
	//获取事件类型
		String action = intent.getAction();
		if("android.intent.action.PACKAGE_ADDED".equals(action))
		{
			System.out.println(packageName + "安装了。。。。。");
		}
		else if("android.intent.action.PACKAGE_REMOVED".equals(action))
		{
			System.out.println(packageName + "卸载了。。。。。");
		}

   

8.案例5_开机启动监听

  如果想在广播接收者里启动activity,必须要为意图指定一个任务栈标记。

   ■清单配置

      wKioL1XCIhGBur5qAAD0JOa1w8g131.jpg


   ■权限 

    wKiom1XCID3j72fZAAB28IFVRfw490.jpg

   ■广播接收者的接收后的逻辑  

    wKioL1XCIpuSe-_UAAE_TeXcGWM926.jpg

  开机启动广播监听,可以与很多地方相结合。如启动activity或service,可以在service里又与

 其它的监听事件如短信、电话监听相结合,做出电话窃听器这样子之类的应用。

   另外,开机启动监听比较的费时,有时候其实是实现了,但是 Log就是不出信息,可以采用开

 启Activity的方式(记得intent要setFlags),这样效果就明显了。




9.电源键长按广播

   定义广播类:

package com.done.assistcarrecord.receiver;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;

import com.done.assistcarrecord.MyApplication;
import com.done.assistcarrecord.common.Constants;
import com.done.assistcarrecord.util.ShellUtil;
import com.done.assistcarrecord.util.log.ExceptionLogCatcher;

/**
 * Created by Administrator on 2018/4/15 0015.
 * 监听电源长按,防止模拟点击事件时误操作点击了飞行模式。
 */
public class PowerBroadcastReceiver extends BroadcastReceiver {
    private static final String TAG = "PowerBroadcastReceiver";
    public static boolean isPowerDialogShowed = false;                //是否出现电源长按界面

    @Override
    public void onReceive(Context context, Intent intent) {
        //你自己先把 reasons == homekey 和 长按homekey 排除,剩下的做下面的处理
        String reason = intent.getStringExtra("reason");
        if (intent.getAction().equals(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)){
            System.out.println("Intent.ACTION_CLOSE_SYSTEM_DIALOGS : " + intent.getStringExtra("reason"));

            if (intent.getExtras()!=null && intent.getExtras().getBoolean("myReason")){

            }else if (reason != null){

                if (reason.equalsIgnoreCase("globalactions")){

                    //监听电源长按键的方法:
                    Intent myIntent = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
                    myIntent.putExtra("myReason", true);
                    context.sendOrderedBroadcast(myIntent, null);
                    Log.e(TAG, "onReceive: 电源  键被长按");
                    ExceptionLogCatcher.getInstance().put("电源  键被长按");
                    isPowerDialogShowed = true;
                    MyApplication.gHANDLER.postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            ShellUtil.execCommand(Constants.CLICK_CANCEL_POWER_LONG_PRESS_DIALOG, true);
                        }
                    },500);
                    isPowerDialogShowed = false;
                    ExceptionLogCatcher.getInstance().put("模拟关闭电源对话框成功");

                }else if (reason.equalsIgnoreCase("homekey")){

                    //<span style="font-family:Arial, Helvetica, sans-serif;">监听</span><span style="font-family:Arial, Helvetica, sans-serif;">Home键的方法</span>
                    //在这里做一些你自己想要的操作,比如重新打开自己的锁屏程序界面,这样子就不会消失了
                    Log.e(TAG, "onReceive: Home 键被触发");
                }else if (reason.equalsIgnoreCase("recentapps")){
                    //监听Home键长按的方法
                    Log.e(TAG, "onReceive: Home 键被长按");
                }
            }
        }
    }
}

    在代码里注册(好像在清单里注册,是接收不到广播的)

@Override
public void onCreate() {

    //监听长按事件
    final IntentFilter homeFilter = new IntentFilter(
            Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
    powerBroadcastReceiver = new PowerBroadcastReceiver();
    registerReceiver(powerBroadcastReceiver, homeFilter);

    一般在onDestroy方法里注销广播

if(powerBroadcastReceiver != null){
    unregisterReceiver(powerBroadcastReceiver);
    powerBroadcastReceiver = null;
}

    

10.TIME_TICK 广播

     https://blog.csdn.net/mrleeapple/article/details/50525236  (系统源码明确指出不能静态注册)



####################################################################################

上面讲到几个案例,都有一个共同的特点:广播都是由系统发送的。那么可以自己定义并发送广播吗?


查看API会发现Context类有发送广播的方法,Activity和Service都继承了Context,所以这两个对象

都可以发送广播,或者只要有Context对象的对象都可以发送广播。广播就是在发送Intent,Intent用

来封装数据并且过滤广播接收者,发送广播有2种方式:(系统发送广播也是用的这2个方法)


1.发送无序广播

   ■发送广播代码:sendBroadcast

 public void sendUnOrder(String content)
    {
    	Intent intent = new Intent();
    	//注意设置意图对象的action,以便过滤发送给哪个广播接收者。
    	intent.setAction("com.itheima.UNORDER_SEND");
    	intent.putExtra("content", content);
    	//无序广播的发送方法
    	sendBroadcast(intent);
    	
    }

   广播接收者清单配置:    

   只要定义一个广播接收者,在清单里配置它的action属性和发送代码里的action参数一样,发送广

 播的方法就会将意图Intent发送给符合条件的广播接收者的onReceive参数里,这也印证了为什么广播

 接收者的接收方法里会有一个Intent参数的原因:onReceive参数里的Intent就是发送这个广播的对象

 传递的Intent。

   

2.发送有序广播

  ■发送广播代码:sendOrderedBroadcast    

 public void sendOrder(String content)
    {
    	Intent intent = new Intent();
    	intent.setAction("com.itheima.ORDER_SEND");
    	//注意有序广播的发送方法
    	sendOrderedBroadcast(intent, null, new FinalReceiver(), null, 1, content, null);
    }

  广播接收者清单配置:  

    和接收无序广播的广播接收者不同,接收有序广播的广播接收者的过滤器除了要配置action

   标签之外,过滤器本身还要设置priority属性,表示过滤器的优先级,优先级高的过滤器的广播

   接收者会优先接收到广播。

    终极接收者无须在清单配置任何信息,连receiver也不用配置。

     wKioL1XCLInC4xqKAAJtgxAbmuk336.jpg

   广播接收者接收广播代码: 

     wKiom1XCLJuC0dUGAAInhvkbZB4753.jpg  

    

3.有序广播与无序广播的区别(**********重点***********)

  ■有序广播 

    广播的数据可以被修改,广播可以被拦截,但是终极接收者仍然可以接受到。

  ■无序广播  

     广播的数据不可以被修改,广播不可以被拦截。

 



--------------------------------广播的一些异常------------------------------------

1)IllegalStateException: Can not perform this action after onSaveInstanceState

    http://www.tuicool.com/articles/yU3Yji

 http://stackoverflow.com/questions/7575921/illegalstateexception-can-not-perform-this-action-after-onsaveinstancestate-wit

  

  一是要解注册广播,二是在fragmentManager提交的时候采用

transaction.commitAllowingStateLoss();


2)https://blog.csdn.net/yuanyuan_186/article/details/17389699


3)收不到开机广播:    

     https://blog.csdn.net/baidu_27196493/article/details/78269674