安卓广播BroadcastReceiver机制《第一行代码笔记》
标准广播概念
是一种完全异步执行的广播,在广播发出以后,所有的广播接收器几乎都会在同一时刻接收到这条广播信息,因此他们之间没有先后顺序可言,这样的广播效率会比较高,但同时意味着它是无法被截断的,标准的广播流程如图
有序广播概念
是一种同步执行的广播,在广播发出之后,同一时刻只会有一个广播接收器能够收到这条广播信息,当这个广播接收器逻辑执行完后,广播才会继续传递。所以此时的广播接收器是有先后顺序的,优先级高的广播接收器可以先收到广播信息,并且前面的广播接收器还可以截断正在传递的广播,这样后面的广播接收器就无法收到广播信息了,有序广播的流程如图
动态注册监听网络变化
MainActivity类
package com.yinhao.broadcasttest;
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.view.View;
import android.widget.Button;
public class MainActivity extends AppCompatActivity {
private NetworkChangeReceiver receiver;
private IntentFilter intentFilter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
intentFilter = new IntentFilter();
intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");//action是添加相应的广播的方法
receiver = new NetworkChangeReceiver();
registerReceiver(receiver, intentFilter);
}
@Override
protected void onDestroy() {
super.onDestroy();
unregisterReceiver(receiver);
}
}
定义一个类集成自BroadcastReceiver
package com.yinhao.broadcasttest;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.util.Log;
import android.widget.Toast;
/**
* Created by yinhao on 2017/9/11.
*/
public class NetworkChangeReceiver extends BroadcastReceiver {
private static final String TAG = "NetworkChangeReceiver";
@Override
public void onReceive(Context context, Intent intent) {
//专门管理网络连接的系统服务类
ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
//得到NetworkInfo的实例
NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
if (networkInfo != null && networkInfo.isAvailable()) {//是否有网络的方法
Toast.makeText(context, "网络可用", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(context, "网络不可用", Toast.LENGTH_SHORT).show();
}
}
}
最后加上权限
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
静态注册实现开机启动
写一个类继承自BroadcastReceiver
package com.yinhao.broadcasttest;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import android.widget.Toast;
/**
* Created by yinhao on 2017/9/12.
*/
public class BootCompleteReceiver extends BroadcastReceiver {
private static final String TAG = "BootCompleteReceiver";
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "Boot Complete", Toast.LENGTH_LONG).show();
//Log.i(TAG, "onReceive: Boot Complete");
}
}
在这里注意下,静态注册必须要在manifest中注册,application下
<!-- enabled是否启用这个广播接收器,export是否允许这个广播接收器接收程序以外的广播 -->
<receiver
android:name=".BootCompleteReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
添加接收开机启动状态权限和相应的action(在上面的代码已经注明)
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
发送自定义广播
1.自定义一个类继承自BroadcastReceiver
2.记得要在manifest中注册,不过这次intent-filter标签下action标签的name值要写自己创建的这个类,比如在这里我的类名MyBroadcastReceiver,那么name值可以写为:
<receiver android:name=".MyBroadcastReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="com.yinhao.broadcasttest.MyBroadcastReceiver"/>
</intent-filter>
</receiver>
其实这样定义name值方便自己,里面的值只要是唯一的,你定义什么都可以。
如何发送呢?
我们可以写一个按钮点击事件,当我们点击按钮时:
Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent("com.yinhao.broadcasttest.MyBroadcastReceiver");
sendBroadcast(intent);
}
});
这样我们就完成了发送自定义广播。
发送有序广播
我们需要新建另一个项目,定义一个类继承自BroadcastReceiver,在Manifest里添加上一个项目自定义的receiver的name值
<action android:name="com.yinhao.broadcasttest.MyBroadcastReceiver"/>
这样当我们上一个项目里面的按钮时,就会弹出两个Toast
我们如何判断这是一个有序广播呢
我们可以在第一个项目中添加一个值,priority,这是优先权的意思
<receiver android:name=".MyBroadcastReceiver"
android:enabled="true"
android:exported="true">
<intent-filter android:priority="100">
<action android:name="com.yinhao.broadcasttest.MyBroadcastReceiver"/>
</intent-filter>
</receiver>
我们在MainActiviy中sendBroadcast()方法修改为sendOrderBroadcast()
sendOrderedBroadcast(intent,null);//第一个参数是intent,第二个是跟权限有关的字符串
这样即为发送一个有序广播,而且MyBroadcastReceiver因为优先值高,Toast会优先显示。
最后我们可以在MyBroadcastReceiver中添加一个方法,abortBroadcast()
public class MyBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "received in MyBroadcastReceiver", Toast.LENGTH_SHORT).show();
abortBroadcast();//将这条广播截断,后面的广播接收器将无法再收到此条广播
}
}
这样当我们点击按钮时,只会弹出一个Toast了,因为此方法将后面的广播截断了,原因是我们之前定义了优先级。
发送本地广播
因为安全性的问题,让别的应用无法访问此应用的广播
新建一个类继承自BroadcastReceiver,因为与上面的一些代码,所以不多展开展示
public class MainActivity extends AppCompatActivity {
private IntentFilter intentFilter;
private LocalBroadcastManager localBroadcastManager;
private LocalReceiver localReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
localBroadcastManager = LocalBroadcastManager.getInstance(this);
Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent("com.yinhao.broadcasttest.LocalReceiver");
localBroadcastManager.sendBroadcast(intent);//发送本地广播
}
});
intentFilter = new IntentFilter();
intentFilter.addAction("com.yinhao.broadcasttest.LocalReceiver");
localReceiver = new LocalReceiver();
localBroadcastManager.registerReceiver(localReceiver, intentFilter);//注册本地广播
}
@Override
protected void onDestroy() {
super.onDestroy();
localBroadcastManager.unregisterReceiver(localReceiver);
}
}
实现强制下线Demo
MainActivity
package com.yinhao.broadcastreceiverbestpractice;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
public class MainActivity extends BaseActivity {
private Button forceOffline;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
forceOffline = (Button) findViewById(R.id.force_offline);
forceOffline.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent("com.yinhao.broadcastreceiverbestpractice.FORCE_OFFLINE");
sendBroadcast(intent);
}
});
}
}
R.layout.activity_main
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/force_offline"
android:layout_width="match_parent"
android:layout_height="60dp"
android:text="offline" />
</LinearLayout>
LoginActivity
package com.yinhao.broadcastreceiverbestpractice;
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
public class LoginActivity extends BaseActivity {
private EditText accountEdit;
private EditText passwordEdit;
private Button login;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
accountEdit = (EditText) findViewById(R.id.account);
passwordEdit = (EditText) findViewById(R.id.password);
login = (Button) findViewById(R.id.login);
login.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String account = accountEdit.getText().toString();
String password = passwordEdit.getText().toString();
if (account.equals("admin") && password.equals("123456")) {
startActivity(new Intent(LoginActivity.this, MainActivity.class));
finish();
} else {
Toast.makeText(LoginActivity.this, "account or password invalid", Toast.LENGTH_SHORT).show();
}
}
});
}
}
activity_login
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="60dp"
android:orientation="horizontal">
<TextView
android:layout_width="90dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:text="Account:"
android:textSize="18sp" />
<EditText
android:id="@+id/account"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="60dp"
android:orientation="horizontal">
<TextView
android:layout_width="90dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:text="Password:"
android:textSize="18sp" />
<EditText
android:id="@+id/password"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1" />
</LinearLayout>
<Button
android:id="@+id/login"
android:layout_width="match_parent"
android:layout_height="60dp"
android:text="Login" />
</LinearLayout>
BaseActivity
package com.yinhao.broadcastreceiverbestpractice;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.os.PersistableBundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
/**
* Created by yinhao on 2017/9/12.
*/
public class BaseActivity extends AppCompatActivity {
private ForceOfflineReceiver receiver;
private static final String TAG = "BaseActivity";
@Override
public void onCreate(@Nullable Bundle savedInstanceState, @Nullable PersistableBundle persistentState) {
super.onCreate(savedInstanceState, persistentState);
ActivityCollector.addActivity(this);
}
@Override
protected void onResume() {
Log.i(TAG, "onResume: ");
super.onResume();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("com.yinhao.broadcastreceiverbestpractice.FORCE_OFFLINE");
receiver = new ForceOfflineReceiver();
registerReceiver(receiver, intentFilter);
}
@Override
protected void onPause() {
Log.i(TAG, "onPause: ");
super.onPause();
if (receiver != null) {
unregisterReceiver(receiver);
receiver = null;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
ActivityCollector.removeActivity(this);
}
class ForceOfflineReceiver extends BroadcastReceiver {
@Override
public void onReceive(final Context context, Intent intent) {
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle("Warning");
builder.setMessage("You are force to be offline. Please try to login again.");
builder.setCancelable(false);//将对话框设置为不可取消
builder.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
ActivityCollector.finishAll();//销毁活动
context.startActivity(new Intent(context, LoginActivity.class));//重新启动到登录界面
}
});
builder.show();
}
}
}
ActivityCollector
package com.yinhao.broadcastreceiverbestpractice;
import android.app.Activity;
import java.util.ArrayList;
import java.util.List;
/**
* Created by yinhao on 2017/9/12.
*/
public class ActivityCollector {
public static List<Activity> activities = new ArrayList<>();
public static void addActivity(Activity activity) {
activities.add(activity);
}
public static void removeActivity(Activity activity) {
activities.add(activity);
}
public static void finishAll() {
for (Activity activity : activities) {
activity.finish();
}
}
}
因为始终需要保证处于栈顶的活动才能接收到这条强制下线的广播,非栈顶的活动不应该也没有必要去接收这条广播。当一个活动失去栈顶时,自动取消广播接收器的注册。