(一)广播机制简介
1、Android广播的分类:
如图所示:
2、发送广播:使用Intent;接收广播:Broadcast Receiver。
(二)接收系统广播
1、动态注册监听网络变化
示例程序:
(1)MainActivity(注:以下代码中的ToastUtil是自己简单封装的Toast显示功能的类):
1 package com.example.broadcasttest; 2 3 import android.app.Activity; 4 import android.content.BroadcastReceiver; 5 import android.content.Context; 6 import android.content.Intent; 7 import android.content.IntentFilter; 8 import android.net.ConnectivityManager; 9 import android.net.NetworkInfo; 10 import android.os.Bundle; 11 import android.view.Menu; 12 import android.view.MenuItem; 13 14 public class MainActivity extends Activity { 15 16 private IntentFilter intentFilter; 17 private NetworkChangeReceiver networkChangeReceiver; 18 19 @Override 20 protected void onCreate(Bundle savedInstanceState) { 21 super.onCreate(savedInstanceState); 22 setContentView(R.layout.activity_main); 23 24 // 1.创建IntentFilter实例 25 intentFilter = new IntentFilter(); 26 // 2.用addAction方法添加action 27 intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE"); 28 29 // 3.创建内部类NetworkChangeReceiver实例 30 networkChangeReceiver = new NetworkChangeReceiver(); 31 // 4.注册 32 registerReceiver(networkChangeReceiver, intentFilter); 33 } 34 35 class NetworkChangeReceiver extends BroadcastReceiver { 36 37 @Override 38 public void onReceive(Context context, Intent intent) { 39 // 创建ConnectivityManager实例 40 ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); 41 // 创建NetworkInfo对象(需要申请权限ACCESS_NETWORK_STATE) 42 NetworkInfo networkInfo = connectivityManager 43 .getActiveNetworkInfo(); 44 45 // 判断NetworkInfo的状态,即网络是否可用 46 if (networkInfo != null && networkInfo.isAvailable()) { 47 ToastUtil.showShort(MainActivity.this, "网络可用!"); 48 } else { 49 ToastUtil.showShort(MainActivity.this, "网络不可用!"); 50 } 51 52 } 53 } 54 55 @Override 56 protected void onDestroy() { 57 super.onDestroy(); 58 unregisterReceiver(networkChangeReceiver); 59 } 60 61 }
(2)申请权限
1 <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
(3)xml文件:不需要添加什么内容。
2、静态注册实现开机启动
动态注册的一个缺点就是,必须要在程序启动之后才能接收到广播,而静态注册就可以在程序还未启动时就能接收到广播,利用这一点就可以实现诸如开机启动程序的功能。
示例程序:
(1)新建类BootCompleteReceiver继承自BroadcastReceiver(注:onReceive方法中红不能放过于耗时的逻辑,因为其中不允许使用线程):
1 package com.example.broadcasttest; 2 3 import android.content.BroadcastReceiver; 4 import android.content.Context; 5 import android.content.Intent; 6 7 public class BootCompleteReceiver extends BroadcastReceiver { 8 9 @Override 10 public void onReceive(Context context, Intent intent) { 11 ToastUtil.showShort(context, "BroadcastTest开机启动"); 12 } 13 14 }
(2)在AndroidManifest.xml静态注册广播:
1 <application 2 android:allowBackup="true" 3 android:icon="@drawable/ic_launcher" 4 android:label="@string/app_name" 5 android:theme="@style/AppTheme" > 6 ... 7 <receiver android:name=".BootCompleteReceiver" > 8 <intent-filter> 9 <action android:name="android.intent.action.BOOT_COMPLETED" /> 10 </intent-filter> 11 </receiver> 12 </application>
(3)申请权限:
1 <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
(三)发送自定义广播
1、发送标准广播
(1)在BroadcastTest项目中:
①创建MyBroadcastReceiver:
1 public class MyBroadcastReceiver extends BroadcastReceiver { 2 3 @Override 4 public void onReceive(Context context, Intent intent) { 5 ToastUtil.showShort(context, "在MyBroadcastReceiver中接收到了自定义广播!"); 6 } 7 8 }
②在AndroidManifest.xml中注册广播接收器:
1 <receiver android:name=".MyBroadcastReceiver" > 2 <intent-filter> 3 <action android:name="com.example.broadcasttest.MY_BROADCAST" /> 4 </intent-filter> 5 </receiver>
③activity_main.xml:
1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 android:layout_width="match_parent" 3 android:layout_height="match_parent" 4 android:orientation="vertical" > 5 6 <Button 7 android:id="@+id/send_broadcast_btn" 8 android:layout_width="match_parent" 9 android:layout_height="wrap_content" 10 android:text="发送自定义广播" /> 11 12 </LinearLayout>
④MainActivity:
1 package com.example.broadcasttest; 2 3 import android.app.Activity; 4 import android.content.BroadcastReceiver; 5 import android.content.Context; 6 import android.content.Intent; 7 import android.content.IntentFilter; 8 import android.net.ConnectivityManager; 9 import android.net.NetworkInfo; 10 import android.os.Bundle; 11 import android.view.Menu; 12 import android.view.MenuItem; 13 import android.view.View; 14 import android.view.View.OnClickListener; 15 import android.widget.Button; 16 17 public class MainActivity extends Activity implements OnClickListener { 18 19 private Button sendBroadcast; 20 21 @Override 22 protected void onCreate(Bundle savedInstanceState) { 23 super.onCreate(savedInstanceState); 24 setContentView(R.layout.activity_main); 25 26 sendBroadcast = (Button) findViewById(R.id.send_broadcast_btn); 27 sendBroadcast.setOnClickListener(this); 28 } 29 30 @Override 31 public void onClick(View v) { 32 switch (v.getId()) { 33 case R.id.send_broadcast_btn: 34 Intent intent = new Intent("com.example.broadcasttest.MY_BROADCAST"); 35 sendBroadcast(intent); 36 break; 37 default: 38 break; 39 } 40 } 41 }
(2)创建BroadcastTest2项目,在其中:
①创建AnotherBroadcastReceiver:
1 public class AnotherBroadcastReceiver extends BroadcastReceiver { 2 3 @Override 4 public void onReceive(Context context, Intent intent) { 5 ToastUtil.showShort(context, "在AnotherBroadcastReceiver中接收到了自定义广播!"); 6 } 7 8 }
②在AndroidManifest.xml中注册广播接收器:
1 <receiver android:name=".AnotherBroadcastReceiver" > 2 <intent-filter> 3 <action android:name="com.example.broadcasttest.MY_BROADCAST" /> 4 </intent-filter> 5 </receiver>
(3)同时运行BroadcastTest和BroadcastTest2程序,然后在BroadcastTest中点击“发送自定义广播”按钮,然后就会发现弹出两次Toast显示接收到了广播。
2、发送有序广播
在1中BroadcastTest项目的基础上,做以下修改即可(红色加下划线的代码为新增或修改的代码):
(1)MainActivity中:
1 @Override 2 public void onClick(View v) { 3 switch (v.getId()) { 4 case R.id.send_broadcast_btn: 5 Intent intent = new Intent("com.example.broadcasttest.MY_BROADCAST"); 6 sendOrderedBroadcast(intent, null); 7 break; 8 default: 9 break; 10 } 11 }
(2)AndroidManifest.xml中:
1 <receiver android:name=".MyBroadcastReceiver" > 2 <intent-filter android:priority="100" > 3 <action android:name="com.example.broadcasttest.MY_BROADCAST" /> 4 </intent-filter> 5 </receiver>
(3)MyBroadcastReceiver类中:
1 public class MyBroadcastReceiver extends BroadcastReceiver { 2 3 @Override 4 public void onReceive(Context context, Intent intent) { 5 ToastUtil.showShort(context, "在MyBroadcastReceiver中接收到了自定义广播!"); 6 abortBroadcast(); 7 } 8 9 }
(4)再运行两个程序,点击发送广播按钮后,发现只看到了一个Toast提示,因为另一个广播接收被截断了。
(四)使用本地广播
以上的广播都是全局广播,也就是任何应用程序都能接收到。而这会引发安全性问题,如果只希望在当前应用程序内部传递广播,就要使用本地广播了。
本地广播的关键是使用LocalBroadcastManager来发送广播。示例程序:
1、xml文件:
1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 android:layout_width="match_parent" 3 android:layout_height="match_parent" 4 android:orientation="vertical" > 5 6 <Button 7 android:id="@+id/send_broadcast_btn" 8 android:layout_width="match_parent" 9 android:layout_height="wrap_content" 10 android:text="发送自定义广播" /> 11 12 </LinearLayout>
2、MainActivity:
1 package com.example.broadcasttest; 2 3 import android.app.Activity; 4 import android.content.BroadcastReceiver; 5 import android.content.Context; 6 import android.content.Intent; 7 import android.content.IntentFilter; 8 import android.net.ConnectivityManager; 9 import android.net.NetworkInfo; 10 import android.os.Bundle; 11 import android.support.v4.content.LocalBroadcastManager; 12 import android.view.Menu; 13 import android.view.MenuItem; 14 import android.view.View; 15 import android.view.View.OnClickListener; 16 import android.widget.Button; 17 18 public class MainActivity extends Activity implements OnClickListener { 19 20 private Button sendBroadcast; 21 22 private IntentFilter intentFilter; 23 24 private LocalReceiver localReceiver; 25 private LocalBroadcastManager localBroadcastManager; 26 27 @Override 28 protected void onCreate(Bundle savedInstanceState) { 29 super.onCreate(savedInstanceState); 30 setContentView(R.layout.activity_main); 31 32 // 1.获取localBroadcastManager实例 33 localBroadcastManager = LocalBroadcastManager.getInstance(this); 34 35 sendBroadcast = (Button) findViewById(R.id.send_broadcast_btn); 36 37 // 2.在点击事件中用LocalBroadcastManager的sendBroadcast方法发送广播 38 sendBroadcast.setOnClickListener(this); 39 40 // 3.注册IntentFilter 41 intentFilter = new IntentFilter(); 42 intentFilter.addAction("com.example.broadcasttest.LOCAL_BROADCAST"); 43 localReceiver = new LocalReceiver(); 44 localBroadcastManager.registerReceiver(localReceiver, intentFilter); 45 } 46 47 @Override 48 public void onClick(View v) { 49 switch (v.getId()) { 50 case R.id.send_broadcast_btn: 51 Intent intent = new Intent( 52 "com.example.broadcasttest.LOCAL_BROADCAST"); 53 localBroadcastManager.sendBroadcast(intent); 54 break; 55 default: 56 break; 57 } 58 } 59 60 @Override 61 protected void onDestroy() { 62 super.onDestroy(); 63 localBroadcastManager.unregisterReceiver(localReceiver); 64 } 65 }
3、注册广播接收器:
1 <receiver android:name=".LocalReceiver" > 2 <intent-filter> 3 <action android:name="com.example.broadcasttest.LOCAL_BROADCAST" /> 4 </intent-filter> 5 </receiver>
4、这时如果也让另一个程序接收LOCAL_BROADCAST这个广播,会发现是接收不到的。
5、本地广播的优点:
(1)不用担心机密数据泄露。
(2)其他程序无法将广播发送到我们程序的内容,不用担心安全漏洞的问题。
(3)比全局广播更高效。
(五)最佳实践——实现强制下线功能
在登录页面输入账号密码进入主界面后,点击强制下线按钮会弹出强制下线Dialog,并且该Dialog不能被取消,当用户点击确定后会发出强制下线广播,再次跳转到登录界面。
1、login.xml文件:
1 <?xml version="1.0" encoding="utf-8"?> 2 <TableLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 android:layout_width="match_parent" 4 android:layout_height="match_parent" 5 android:stretchColumns="1" > 6 7 <TableRow> 8 9 <TextView 10 android:layout_height="wrap_content" 11 android:text="用户名:" /> 12 13 <EditText 14 android:id="@+id/user_name_et" 15 android:layout_height="wrap_content" 16 android:hint="请输入用户名" /> 17 </TableRow> 18 19 <TableRow> 20 21 <TextView 22 android:layout_height="wrap_content" 23 android:text="密码:" /> 24 25 <EditText 26 android:id="@+id/password_et" 27 android:layout_height="wrap_content" > 28 </EditText> 29 </TableRow> 30 31 <TableRow> 32 33 <Button 34 android:id="@+id/login_bt" 35 android:layout_height="wrap_content" 36 android:layout_span="2" 37 android:text="登录" /> 38 </TableRow> 39 40 </TableLayout>
2、ActivityCollector类:
1 public class ActivityCollector { 2 public static List<Activity> activities = new ArrayList<Activity>(); 3 4 public static void addActivity(Activity activity) { 5 activities.add(activity); 6 } 7 8 public static void removeActivity(Activity activity) { 9 activities.remove(activity); 10 } 11 12 public static void finishAll() { 13 for (Activity activity : activities) { 14 if (!activity.isFinishing()) { 15 activity.finish(); 16 } 17 } 18 } 19 }
3、BaseActivity类:
1 public class BaseActivity extends Activity { 2 @Override 3 protected void onCreate(Bundle savedInstanceState) { 4 super.onCreate(savedInstanceState); 5 6 ActivityCollector.addActivity(this); 7 } 8 9 @Override 10 protected void onDestroy() { 11 super.onDestroy(); 12 ActivityCollector.removeActivity(this); 13 } 14 }
4、LoginActivity类:
1 public class LoginActivity extends BaseActivity { 2 3 private EditText userNameEt; 4 private EditText passwordEt; 5 private Button loginBt; 6 7 @Override 8 protected void onCreate(Bundle savedInstanceState) { 9 super.onCreate(savedInstanceState); 10 setContentView(R.layout.login); 11 12 userNameEt = (EditText) findViewById(R.id.user_name_et); 13 passwordEt = (EditText) findViewById(R.id.password_et); 14 loginBt = (Button) findViewById(R.id.login_bt); 15 16 loginBt.setOnClickListener(new OnClickListener() { 17 18 @Override 19 public void onClick(View v) { 20 String userName = userNameEt.getText().toString(); 21 String password = passwordEt.getText().toString(); 22 23 // 如果用户名是admin且密码是123456,就认为登录成功 24 if (userName.equals("110") && password.equals("123456")) { 25 Intent intent = new Intent(LoginActivity.this, 26 MainActivity.class); 27 startActivity(intent); 28 finish(); 29 } else { 30 Toast.makeText(LoginActivity.this, "用户名或密码错误!", 31 Toast.LENGTH_SHORT).show(); 32 } 33 } 34 }); 35 } 36 }
5、activity_main.xml:
1 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 xmlns:tools="http://schemas.android.com/tools" 3 android:layout_width="match_parent" 4 android:layout_height="match_parent" 5 android:paddingBottom="@dimen/activity_vertical_margin" 6 android:paddingLeft="@dimen/activity_horizontal_margin" 7 android:paddingRight="@dimen/activity_horizontal_margin" 8 android:paddingTop="@dimen/activity_vertical_margin" 9 tools:context="com.example.broadcastbestpractice.MainActivity" > 10 11 <TextView 12 android:layout_width="wrap_content" 13 android:layout_height="wrap_content" 14 android:text="这里是主界面" /> 15 16 <Button 17 android:id="@+id/force_offline_bt" 18 android:layout_width="match_parent" 19 android:layout_height="wrap_content" 20 android:layout_margin="40dp" 21 android:text="发送一个强制下线广播" /> 22 23 </RelativeLayout>
6、MainActivity类:
1 public class MainActivity extends Activity { 2 3 @Override 4 protected void onCreate(Bundle savedInstanceState) { 5 super.onCreate(savedInstanceState); 6 setContentView(R.layout.activity_main); 7 8 Button forceOfflineBt = (Button) findViewById(R.id.force_offline_bt); 9 forceOfflineBt.setOnClickListener(new OnClickListener() { 10 11 @Override 12 public void onClick(View v) { 13 Intent intent = new Intent( 14 "com.example.broadcastbestpractice.FORCE_OFFLINE"); 15 sendBroadcast(intent); 16 } 17 }); 18 } 19 20 }
7、ForceOfflineReceiver:
1 public class ForceOfflineReceiver extends BroadcastReceiver { 2 3 @Override 4 public void onReceive(final Context context, Intent intent) { 5 AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(context); 6 dialogBuilder.setTitle("警告"); 7 dialogBuilder.setMessage("你将要被强制下线!请重新登录!"); 8 dialogBuilder.setCancelable(false); 9 dialogBuilder.setPositiveButton("确定", 10 new DialogInterface.OnClickListener() { 11 12 @Override 13 public void onClick(DialogInterface dialog, int which) { 14 ActivityCollector.finishAll(); 15 Intent intent = new Intent(context, LoginActivity.class); 16 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 17 context.startActivity(intent); 18 } 19 }); 20 21 AlertDialog alertDialog = dialogBuilder.create(); 22 alertDialog.getWindow().setType( 23 WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); 24 alertDialog.show(); 25 } 26 }
8、AndroidManifest.xml:
1 ... 2 <activity 3 android:name=".LoginActivity" 4 android:label="@string/app_name" > 5 <intent-filter> 6 <action android:name="android.intent.action.MAIN" /> 7 8 <category android:name="android.intent.category.LAUNCHER" /> 9 </intent-filter> 10 </activity> 11 <activity android:name=".MainActivity" > 12 </activity> 13 14 <receiver 15 android:name=".ForceOfflineReceiver" 16 android:exported="false" > 17 <intent-filter> 18 <action android:name="com.example.broadcastbestpractice.FORCE_OFFLINE" /> 19 </intent-filter> 20 </receiver> 21 ...