Android笔记之广播
为了容易理解,可以将广播代入到事件模型中,发送广播消息看做是触发event,BroadcastReceiver是处理事件的回调逻辑。
广播这种模型中涉及到两个角色,就是广播的发送者和接收者,所以会涉及到如何发送和如何接收广播。
同时因为系统中可能会有很多的广播,为了不被乱七八糟的东西混淆视听,每个广播给它一个action,这样广播接收器就可以使用action来过滤出自己感兴趣的广播,也可以将action看做是一个频道,每个广播都有一个自己的频道,广播接收器为了不串台就只收听自己感兴趣的一个或多个频道。
广播的分类
按作用域划分
广播按照作用域可以分为全局广播和本地广播。
全局广播的作用域超出此应用程序,发出的全局广播可以被所有应用程序接收,也可以接收其它应用程序发出的全局广播。
本地广播就是作用域限定在本应用中,发出的广播只能在应用内部传递,同样也只接收应用内部的本地广播。
这样是因为我们在自己的应用内传递关键信息,如果不限制作用域的话就有可能被其它应用收到,这样很容易引起安全性问题。
按接收顺序划分
按接收顺序分为普通广播(无序广播)和有序广播。
普通广播:使用Context#sendBroadcast(Intent intent)发送,普通广播是异步的(所以又叫无序广播),广播接收者的顺序无法确定,因为是异步的,所以不能够被停止掉,这种方式保证每个广播接收器都能够接收到广播,并且收到的就是原始的广播信息(因为从发送者直接到接收者,中间没有经过其它人)。
有序广播:使用Context#sendOrderedBroadcast方法发送,所有要接收此条广播的接收器要排队接收,类似于一条处理链,链上的每个接收器都可以选择从这里终止不再向下传递,所以有序广播是可以被终止的,不保证每个接收器都一定能够接收到广播,同样的,因为链式向后面传递,那么前面的接收者也可以对广播修改后再往下传递,所以此方式除链上的第一个节点外其他接收器收到的数据都有可能被篡改过。另外既然有序广播接收的时候需要排队,那么排队的依据是什么呢,就是在注册的时候intent-filter的android:priority来决定。
粘性广播:使用Context#sendStickyBroadcast发送,粘性广播被发送后,最后一个粘性广播将被粘在系统上,在一段时间内如果有新的广播接收器注册的话那么它将能够接收到这个被粘住的广播,尽管在这个广播被发送的时候它还没有注册,但就是粘了一下收到了。
发送广播
发送全局广播
sendBroadcast()方法第一个参数接收一个Intent,第二个参数是与权限相关的字符串。
发送全局无序广播: Context#sendBroadcast
发送全局有序广播: Context#sendOrderedBroadcast
Intent intent = new Intent("foo.BAR"); sendOrderedBroadcast(intent, null);
发送本地广播
本地广播使用LocalBroadcastManager来管理。
发送本地有序广播:LocalBroadcastManager.getInstance(this).sendBroadcast
发送本地无序广播:LocalBroadcastManager.getInstance(this).sendBroadcastSync
接收广播
接收广播的套路
1. 要接收广播需要创建一个类继承android.content.BroadcastReceiver,并在其onReceive方法中实现对广播事件的处理逻辑
2. 然后将创建的广播接收器注册,注册的方式有静态注册(AndroidManifest.xml)和动态注册(Java代码)两种,如果接收广播需要权限的话还要声明使用相应权限。
3. 然后当有符合条件的广播到来的时候会自动调用广播接收器的onReceive方法
静态注册和动态注册的区别
静态注册:在应用程序关闭后,当有广播来临时仍然能够接收到被调用,应用场景是需要时刻监听广播(即使在应用程序退出后)。
动态注册:短命鬼,广播接收器的生命周期跟随组件在变,应用场景是只在某段时间才需要监听广播。
拦截广播
要在有序广播接收器的处理链上拦截广播,在onReceive方法中调用aboryBroadcast即可拦截广播不再向后传递而是从此处停止。
@Override public void onReceive(Context context, Intent intent) { boolean foo = intent.getBooleanExtra("foo", false); if (foo) { abortBroadcast(); } }
本地广播的注册
本地广播只能通过动态注册的方式。因为静态广播主要是为了让应用程序在不启动的时候也能够接收到广播,而本地广播因为都是在应用程序内传递的,所以本地广播都是在应用启动时才有的,所以本地广播不能使用静态注册的方式。
注册本地广播接收器: LocalBroadcastManager.getInstance(this).registerReceiver();
取消注册本地广播接收器:LocalBroadcastManager.getInstance(this).unregisterReceiver();
onReceive的耗时操作
onReceive的执行时间最多只有10秒钟,当超过10秒的时候将会报错,所以不应该在其中执行耗时的方法,正确的方式是启动一个Service执行耗时操作。
静态注册
在AndroidManifest.xml文件中注册广播接收器:
<!-- 静态注册广播接收器 --> <receiver android:name=".FooBroadcastReceiver" android:enabled="true" android:exported="true"> <intent-filter android:priority="1000"> <action android:name="cc11001100.foo" /> </intent-filter> </receiver>
name: 继承了BroadcastReceiver的广播接收器
enable: 是否启动此接收器
exported:是否允许接收此应用以外的广播,如果为false表示只接收此应用内的广播,即本地广播接收器,否则为全局广播接收器。
intent-filter:增加action过滤广播,其属性priority用于设置此广播接收器的优先级,范围是[-1000, 1000]
action ,系统中会有很多乱七八糟的广播,这个是用来过滤只接收自己需要的广播,intent-filter下可以有多个action
对于静态注册,如果使用的是Android Studio的话,可以通过:
创建的类会继承BroadcastReceiver并且自动在AndroidManifest.xml文件中静态注册。
下面是一个静态注册的例子:
广播接收器:
package cc11001100.androidstudy_005; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.util.Log; import android.widget.Toast; /** * @author CC11001100 */ public class FooBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { int value = intent.getIntExtra("foo", 0); Toast.makeText(context, Integer.toString(value), Toast.LENGTH_LONG).show(); Log.i("FooBroadcastReceiver", "onReceive: " + value); } }
布局文件:
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="cc11001100.androidstudy_005.MainActivity"> <Button android:id="@+id/sendBroadcastBtn" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Send Broadcast" tools:ignore="MissingConstraints" android:onClick="sendBroadcastBtn"/> </android.support.constraint.ConstraintLayout>
AndroidManifest.xml:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="cc11001100.androidstudy_005"> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <!-- 静态注册广播接收器 --> <receiver android:name=".FooBroadcastReceiver" android:enabled="true" android:exported="false"> <intent-filter> <action android:name="cc11001100.foo" /> </intent-filter> </receiver> </application> </manifest>
MainActivity:
package cc11001100.androidstudy_005; import android.content.Intent; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.View; import java.util.Random; public class MainActivity extends AppCompatActivity { private static final String TAG = MainActivity.class.getName(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } /** * 绑定的按钮事件,发送广播事件供接收 */ public void sendBroadcastBtn(View view) { Intent intent = new Intent(); intent.setAction("cc11001100.foo"); intent.putExtra("foo", new Random().nextInt()); sendBroadcast(intent); Log.i(TAG, "sendBroadcastBtn: "); } }
前面提到过静态注册的广播接收器即使在应用退出后仍然可以接收广播,那么有没有办法停掉它呢?
PackageManager
动态注册
在程序运行的时候使用Java代码注册,称为动态注册,动态注册要记得在组件的onDestroy中unregisterReceiver广播接收器。
动态注册的步骤:
1. 定义广播接收器类
2. 创建IntentFilter,通过setAction设置所要接收的广播
3. 使用Context#registerReceiver(BroadcastReceiver receiver, IntentFilter filter)方法注册接收器
下面是一个动态注册的例子,广播接收器,对接收到的广播做处理:
package cc11001100.androidstudy_005; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.util.Log; import android.widget.Toast; /** * @author CC11001100 */ public class FooBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { int value = intent.getIntExtra("foo", 0); Toast.makeText(context, Integer.toString(value), Toast.LENGTH_LONG).show(); Log.i("FooBroadcastReceiver", "onReceive: " + value); } }
布局文件,放一个按钮,每单击一次就发送一个广播供广播接收器接收:
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="cc11001100.androidstudy_005.MainActivity"> <Button android:id="@+id/sendBroadcastBtn" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Send Broadcast" tools:ignore="MissingConstraints" android:onClick="sendBroadcastBtn"/> </android.support.constraint.ConstraintLayout>
MainActivity:
package cc11001100.androidstudy_005; import android.content.Intent; import android.content.IntentFilter; import android.support.v4.content.LocalBroadcastManager; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.view.View; import java.util.Random; public class MainActivity extends AppCompatActivity { private static final String TAG = MainActivity.class.getName(); private FooBroadcastReceiver fooBroadcastReceiver; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 动态注册广播接收器 IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction("cc11001100.foo"); fooBroadcastReceiver = new FooBroadcastReceiver(); registerReceiver(fooBroadcastReceiver, intentFilter); } @Override protected void onDestroy() { super.onDestroy(); unregisterReceiver(fooBroadcastReceiver); } /** * 绑定的按钮事件,发送广播事件供接收 */ public void sendBroadcastBtn(View view) { Intent intent = new Intent(); intent.setAction("cc11001100.foo"); intent.putExtra("foo", new Random().nextInt()); sendBroadcast(intent); Log.i(TAG, "sendBroadcastBtn: "); } }
.