# BroadcastReceiver的定义 广播是一种广泛运用的在应用程序之间传输信息的机制,主要用来监听系统或者应用发出的广播信息,然后根据广播信息作为相应的逻辑处理,也可以用来传输少量、频率低的数据。 在实现开机启动服务和网络状态改变、电量变化、短信和来电时通过接收系统的广播让应用程序作出相应的处理。 BroadcastReceiver 自身并不实现图形用户界面,但是当它收到某个通知后, BroadcastReceiver 可以通过启动 Service 、启动 Activity 或是 NotificationMananger 提醒用户。 #BroadcastReceiver使用注意 当系统或应用发出广播时,将会扫描系统中的所有广播接收者,通过action匹配将广播发送给相应的接收者,接收者收到广播后将会产生一个广播接收者的实例,执行其中的onReceiver()这个方法;特别需要注意的是这个实例的生命周期只有10秒,如果10秒内没执行结束onReceiver(),系统将会报错。 在onReceiver()执行完毕之后,该实例将会被销毁,所以不要在onReceiver()中执行耗时操作,也不要在里面创建子线程处理业务(因为可能子线程没处理完,接收者就被回收了,那么子线程也会跟着被回收掉);正确的处理方法就是通过in调用activity或者service处理业务。 #BroadcastReceiver的注册 BroadcastReceiver的注册方式有且只有两种,一种是静态注册(推荐使用),另外一种是动态注册,广播接收者在注册后就开始监听系统或者应用之间发送的广播消息。 **接收短信广播示例**: 定义自己的BroadcastReceiver 类 ``` public class MyBroadcastReceiver extends BroadcastReceiver { // action 名称 String SMS_RECEIVED = "android.provider.Telephony.SMS_RECEIVED" ; public void onReceive(Context context, Intent intent) { if (intent.getAction().equals( SMS_RECEIVED )) { // 一个receiver可以接收多个action的,即可以有多个intent-filter,需要在onReceive里面对intent.getAction(action name)进行判断。 } } } ``` ##静态方式 在AndroidManifest.xml的application里面定义receiver并设置要接收的action。 ``` < receiver android:name = ".MyBroadcastReceiver" > < intent-filter android:priority = "777" > <action android:name = "android.provider.Telephony.SMS_RECEIVED" /> </ intent-filter > </ receiver > ``` 这里的priority取值是 -1000到1000,值越大优先级越高,同时注意加上系统接收短信的限权。 ``` < uses-permission android:name ="android.permission.RECEIVE_SMS" /> ``` 静态注册的广播接收者是一个常驻在系统中的全局监听器,当你在应用中配置了一个静态的BroadcastReceiver,安装了应用后而无论应用是否处于运行状态,广播接收者都是已经常驻在系统中了。同时应用里的所有receiver都在清单文件里面,方便查看。 要销毁掉静态注册的广播接收者,可以通过调用PackageManager将Receiver禁用。 ##动态方式 在Activity中声明BroadcastReceiver的扩展对象,在onResume中注册,onPause中卸载. ``` public class MainActivity extends Activity { MyBroadcastReceiver receiver; @Override protected void onResume() { // 动态注册广播 (代码执行到这才会开始监听广播消息,并对广播消息作为相应的处理) receiver = new MyBroadcastReceiver(); IntentFilter intentFilter = new IntentFilter( "android.provider.Telephony.SMS_RECEIVED" ); registerReceiver( receiver , intentFilter); super.onResume(); } @Override protected void onPause() { // 撤销注册 (撤销注册后广播接收者将不会再监听系统的广播消息) unregisterReceiver(receiver); super.onPause(); } } ``` ##静态注册和动态注册的区别 1、静态注册的广播接收者一经安装就常驻在系统之中,不需要重新启动唤醒接收者; 动态注册的广播接收者随着应用的生命周期,由registerReceiver开始监听,由unregisterReceiver撤销监听,如果应用退出后,没有撤销已经注册的接收者应用应用将会报错。 2、当广播接收者通过intent启动一个activity或者service时,如果intent中无法匹配到相应的组件。 动态注册的广播接收者将会导致应用报错 而静态注册的广播接收者将不会有任何报错,因为自从应用安装完成后,广播接收者跟应用已经脱离了关系。 #发送BroadcastReceiver 发送广播主要有两种类型: ##普通广播 应用在需要通知各个广播接收者的情况下使用,如 开机启动 使用方法:sendBroadcast() ``` Intent intent = new Intent("android.provider.Telephony.SMS_RECEIVED"); //通过intent传递少量数据 intent.putExtra("data", "finch"); // 发送普通广播 sendBroadcast(Intent); ``` 普通广播是完全异步的,可以在同一时刻(逻辑上)被所有接收者接收到,所有满足条件的 BroadcastReceiver 都会随机地执行其 onReceive() 方法。 同级别接收是先后是随机的;级别低的收到广播; 消息传递的效率比较高,并且无法中断广播的传播。 ##有序广播 应用在需要有特定拦截的场景下使用,如黑名单短信、电话拦截。 使用方法: sendOrderedBroadcast(intent, receiverPermission); receiverPermission :一个接收器必须持以接收您的广播。如果为 null ,不经许可的要求(一般都为null)。 ``` //发送有序广播 sendOrderedBroadcast(intent, null); ``` 在有序广播中,我们可以在前一个广播接收者将处理好的数据传送给后面的广播接收者,也可以调用abortBroadcast()来终结广播的传播。 ``` public void onReceive(Context arg0, Intent intent) { //获取上一个广播的bundle数据 Bundle bundle = getResultExtras(true);//true:前一个广播没有结果时创建新的Bundle;false:不创建Bundle bundle.putString("key", "777"); //将bundle数据放入广播中传给下一个广播接收者 setResultExtras(bundle); //终止广播传给下一个广播接收者 abortBroadcast(); } ``` 高级别的广播收到该广播后,可以决定把该广播是否截断掉。 同级别接收是先后是随机的,如果先接收到的把广播截断了,同级别的例外的接收者是无法收到该广播。 ##异步广播 使用方法:sendStickyBroadcast() : 发出的Intent当接收Activity(动态注册)重新处于onResume状态之后就能重新接受到其Intent.(the Intent will be held to be re-broadcast to future receivers)。就是说sendStickyBroadcast发出的最后一个Intent会被保留,下次当Activity处于活跃的时候又会接受到它。 发这个广播需要权限: ``` <uses-permission android:name="android.permission.BROADCAST_STICKY" /> ``` 卸载该广播: ``` removeStickyBroadcast(intent); ``` 在卸载之前该intent会保留,接收者在可接收状态都能获得。 ##异步有序广播 使用方法:sendStickyOrderedBroadcast(intent, resultReceiver, scheduler, initialCode, initialData, initialExtras): 这个方法具有有序广播的特性也有异步广播的特性; 同时需要限权: ``` <uses-permission android:name="android.permission.BROADCAST_STICKY" /> ``` #总结 - 静态广播接收的处理器是由PackageManagerService负责,当手机启动或者新安装了应用的时候,PackageManagerService会扫描手机中所有已安装的APP应用,将AndroidManifest.xml中有关注册广播的信息解析出来,存储至一个全局静态变量当中。 - 动态广播接收的处理器是由ActivityManagerService负责,当APP的服务或者进程起来之后,执行了注册广播接收的代码逻辑,即进行加载,最后会存储在一个另外的全局静态变量中。需要注意的是: 1、 这个并非是一成不变的,当程序被杀死之后,已注册的动态广播接收器也会被移出全局变量,直到下次程序启动,再进行动态广播的注册,当然这里面的顺序也已经变更了一次。 2、这里也并没完整的进行广播的排序,只记录的注册的先后顺序,并未有结合优先级的处理。 - 广播发出的时候,广播接收者接收的顺序如下: 1.当广播为**普通广播**时,有如下的接收顺序: 无视优先级 动态优先于静态 同优先级的动态广播接收器,**先注册的大于后注册的** 同优先级的静态广播接收器,**先扫描的大于后扫描的** 2.如果广播为**有序广播**,那么会将动态广播处理器和静态广播处理器合并在一起处理广播的消息,最终确定广播接收的顺序: 优先级高的先接收 同优先级的动静态广播接收器,**动态优先于静态** 同优先级的动态广播接收器,**先注册的大于后注册的** 同优先级的静态广播接收器,**先扫描的大于后扫描的** #一些常用的系统广播的action 和permission - 开机启动 ``` <action android:name="android.intent.action.BOOT_COMPLETED"/> ``` ``` <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> ``` - 网络状态 ``` <action android:name="android.net.conn.CONNECTIVITY_CHANGE"/> ``` ``` <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> ``` 网络是否可用的方法: ``` public static boolean isNetworkAvailable(Context context) { ConnectivityManager mgr = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo[] info = mgr.getAllNetworkInfo(); if (info != null) { for (int i = 0; i < info.length; i++) { if (info[i].getState() == NetworkInfo.State.CONNECTED) { return true; } } } return false; } ``` - 电量变化 ``` <action android:name="android.intent.action.BATTERY_CHANGED"/> ``` BroadcastReceiver 的onReceive方法: ``` public void onReceive(Context context, Intent intent) { int currLevel = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0); //当前电量 int total = intent.getIntExtra(BatteryManager.EXTRA_SCALE, 1); //总电量 int percent = currLevel * 100 / total; Log.i(TAG, "battery: " + percent + "%"); } ```
#1.概述 Fragment是Activity中用户界面的一个行为或者是一部分。主要是支持在大屏幕上动态和更为灵活的去组合或是交换UI组件,通过将activity的布局分割成若干个fragment,可以在运行时编辑activity的呈现,并且那些变化会被保存在由activity管理的后台栈里面。 **Fragment必须总是被嵌入到一个activity之中**,并且fragment的生命周期直接受其宿主activity的生命周期的影响。你可以认为fragment是activity的一个模块零件,它有自己的生命周期,接收它自己的输入事件,并且可以在activity运行时添加或者删除。 应该将每一个fragment设计为模块化的和可复用化的activity组件。也就是说,你可以在多个activity中引用同一个fragment,因为fragment定义了它自己的布局,并且使用它本身生命周期回调的行为。 #2.Fragment的生命周期 先看fragment生命周期图:  fragment所生存的activity生命周期直接影响着fragment的生命周期,由此针对activity的每一个生命周期回调都会引发一个fragment类似的回调。例如,当activity接收到onPause()时,这个activity之中的每个fragment都会接收到onPause()。 [这有Activity的详细说明](http://blog.youkuaiyun.com/amazing7/article/details/51244219) Fragment有一些额外的生命周期回调方法(创建和销毁fragment界面). - onAttach() 当fragment被绑定到activity时调用(Activity会被传入)。 - onCreateView() 将本身的布局构建到activity中去(fragment作为activity界面的一部分) - onActivityCreated() 当activity的onCreate()函数返回时被调用。 - onDestroyView() 当与fragment关联的视图体系正被移除时被调用。 - onDetach() 当fragment正与activity解除关联时被调用。 当activity接收到它的onCreate()回调时,activity之中的fragment接收到onActivityCreated()回调。 一旦activity处于resumed状态,则可以在activity中自由的添加或者移除fragment。因此,只**有当activity处于resumed状态时**,fragment的生命周期才可以独立变化。 fragment会在 activity离开恢复状态时 再一次被activity推入它的生命周期中。 **管理fragment生命周期**与管理activity生命周期很相像。像activity一样,fragment也有三种状态: - Resumed fragment在运行中的activity可见。 - Paused 另一个activity处于前台且得到焦点,但是这个fragment所在的activity仍然可见(前台activity部分透明,或者没有覆盖全屏)。 - Stopped fragment不可见。要么宿主activity已经停止,要么fragment已经从activity上移除,但已被添加到后台栈中。一个停止的fragment仍然活着(所有状态和成员信息仍然由系统保留着)。但是,它对用户来讲已经不再可见,并且如果activity被杀掉,它也将被杀掉。 如果activity的进程被杀掉了,在activity被重新创建时,你需要恢复fragment状态。可以执行fragment的onSaveInstanceState()来保存状态(注意在fragment是在onCreate(),onCreateView(),或onActvityCreate()中进行恢复)。 在生命周期方面,activity与fragment之间一个**很重要的不同**,就是在各自的后台栈中是如何存储的。 当activity停止时,**默认**情况下activity被安置在由系统管理的activity后台栈中; fragment仅当在一个事务被移除时,通过显式调用addToBackStack()请求保存的实例,该fragment才被置于由宿主activity管理的后台栈。 **要创建一个fragment**,必须创建一个fragment的子类。一般情况下,我们至少需要实现以下几个fragment生命周期方法: > onCreate() 在创建fragment时系统会调用此方法。在实现代码中,你可以初始化想要在fragment中保持的那些必要组件,当fragment处于暂停或者停止状态之后可重新启用它们。 >onCreateView() 在第一次为fragment绘制用户界面时系统会调用此方法。为fragment绘制用户界面,这个函数必须要返回所绘出的fragment的根View。如果fragment没有用户界面可以返回空。 ``` @Override public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) { // Inflate the layout for this fragment return inflater.inflate(R.layout.example_fragment, container, false); } ``` inflate()函数需要以下三个参数: ①要inflate的布局的资源ID。 ②被inflate的布局的父ViewGroup。 ③一个布尔值,表明在inflate期间被infalte的布局是否应该附上ViewGroup(第二个参数container)。(在这个例子中传入的是false,因为系统已经将被inflate的布局插入到容器中(container)——传入true会在最终的布局里创建一个多余的ViewGroup。) > onPause() 系统回调用该函数作为用户离开fragment的第一个预兆(尽管这并不总意味着fragment被销毁)。在当前用户会话结束之前,通常要在这里提交任何应该持久化的变化(因为用户可能不再返回)。 #3.将fragment添加到activity之中 可以通过在activity布局文件中声明fragment,用fragment标签把fragment插入到activity的布局中,或者是用应用程序源码将它添加到一个存在的ViewGroup中。 但fragment并不是一个定要作为activity布局的一部分,fragment也可以为activity隐身工作。 ##3.1在activity的布局文件里声明fragment 可以像为view一样为fragment指定布局属性。例如: ``` <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="match_parent"> <fragment android:name="com.example.test.FragmentOne" android:id="@+id/fo" android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout> ``` fragment标签中的android:name 属性指定了布局中实例化的Fragment类。 当系统创建activity布局时,它实例化了布局文件中指定的每一个fragment,并为它们调用onCreateView()函数,以获取每一个fragment的布局。系统直接在<fragment>元素的位置插入fragment返回的View。 注意:每个fragment都需要一个唯一的标识,如果重启activity,系统可用来恢复fragment(并且可用来捕捉fragment的事务处理,例如移除)。为fragment提供ID有三种方法: - 用android:id属性提供一个唯一的标识。 - 用android:tag属性提供一个唯一的字符串。 - 如果上述两个属性都没有,系统会使用其容器视图(view)的ID。 ##3.2通过编码将fragment添加到已存在的ViewGroup中 在activity运行的任何时候,你都可以将fragment添加到activity布局中。 要管理activity中的fragment,可以使用FragmentManager。可以通过在activity中调用getFragmentManager()获得。使用FragmentManager 可以做如下事情,包括: - 使用findFragmentById()(用于在activity布局中提供有界面的fragment)或者findFragmentByTag()获取activity中存在的fragment(用于有界面或者没有界面的fragment)。 - 使用popBackStack()(模仿用户的BACK命令)从后台栈弹出fragment。 - 使用addOnBackStackChangedListener()注册一个监听后台栈变化的监听器。 在Android中,对Fragment的事务操作都是通过FragmentTransaction来执行。操作大致可以分为两类: - 显示:add() replace() show() attach() - 隐藏:remove() hide() detach() > 说明: 调用show() & hide()方法时,Fragment的生命周期方法并不会被执行,仅仅是Fragment的View被显示或者隐藏。 > 执行replace()时(至少两个Fragment),会执行第二个Fragment的onAttach()方法、执行第一个Fragment的onPause()-onDetach()方法,同时containerView会detach第一个Fragment的View。 > add()方法执行onAttach()-onResume()的生命周期,相对的remove()就是执行完成剩下的onPause()-onDetach()周期。 可以像下面这样从Activity中取得FragmentTransaction的实例: ``` FragmentManager fragmentManager = getFragmentManager() FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); ``` 可以用add()函数添加fragment,并指定要添加的fragment以及要将其插入到哪个视图(view)之中(注意commit事务): ``` ExampleFragment fragment = new ExampleFragment(); fragmentTransaction.add(R.id.fragment_container, fragment); fragmentTransaction.commit(); ``` ##3.3添加没有界面的fragment 也可以使用fragment为activity提供后台动作,却不呈现多余的用户界面。 想要添加没有界面的fragment ,可以使用add(Fragment, String)(为fragment提供一个唯一的字符串“tag”,而不是视图(view)ID)。这样添加了fragment,但是,因为还没有关联到activity布局中的视图(view) ,收不到onCreateView()的调用。所以不需要实现这个方法。 对于无界面fragment,字符串标签是**唯一识别**它的方法。如果之后想从activity中取到fragment,需要使用findFragmentByTag()。 #4.fragment事务后台栈 在调用commit()之前,可以将事务添加到fragment事务后台栈中(通过调用addToBackStatck())。这个后台栈由activity管理,并且允许用户通过按BACK键回退到前一个fragment状态。 下面的代码中一个fragment代替另一个fragment,并且将之前的fragment状态保留在后台栈中: ``` Fragment newFragment = new ExampleFragment(); FragmentTransaction transaction = getFragmentManager().beginTransaction(); transaction.replace(R.id.fragment_container, newFragment); transaction.addToBackStack(null); transaction.commit(); ``` > 注意: > > 如果添加多个变更事务(例如另一个add()或者remove())并调用addToBackStack(),那么在调用commit()之前的所有应用的变更被作为一个单独的事务添加到后台栈中,并且BACK键可以将它们一起回退。 > > 当移除一个fragment时,如果调用了addToBackStack(),那么之后fragment会被停止,如果用户回退,它将被恢复过来。 > > 调用commit()并不立刻执行事务,相反,而是采取预约方式,一旦activity的界面线程(主线程)准备好便可运行起来。然而,如果有必要的话,你可以从界面线程调用executePendingTransations()立即执行由commit()提交的事务。 > > 只能在activity保存状态(当用户离开activity时)之前用commit()提交事务。如果你尝试在那时之后提交,会抛出一个异常。这是因为如果activity需要被恢复,提交后的状态会被丢失。对于这类丢失提交的情况,可使用commitAllowingStateLoss() #5.与Activity交互 - Activity中已经有了该Fragment的引用,直接通过该引用进行交互。 -如果没引用可以通过调用fragment的函数findFragmentById()或者findFragmentByTag(),从FragmentManager中获取Fragment的索引,例如: ``` ExampleFragment fragment = (ExampleFragment) getFragmentManager().findFragmentById(R.id.example_fragment); ``` - 在Fragment中可以通过getActivity得到当前绑定的Activity的实例。 - 创建activity事件回调函数,在fragment内部定义一个回调接口,宿主activity来实现它。