android四大元件之Broadcast(廣播)使用詳解
一 概述
Broadcast作為android中的四大元件,其重要性可想而知, 在android系統中,廣播(Broadcast)是在元件之間傳播資料(Intent)的一種機制;這些元件甚至是可以位於不同的程序中,這樣它就像Binder機制一樣,起到程序間通訊的作用。android廣播機制,本質上它就是一種元件間的通訊方式,如果是兩個元件位於不同的程序當中,那麼可以用Binder機制來實現,如果兩個元件是在同一個程序中,那麼它們之間可以用來通訊的方式就更多了,這樣看來,廣播機制似乎是多餘的。然而,廣播機制卻是不可替代的,它和Binder機制不一樣的地方在於,廣播的傳送者和接收者事先是不需要知道對方的存在的,這樣帶來的好處便是,系統的各個元件可以鬆耦合地組織在一起,這樣系統就具有高度的可擴充套件性,容易與其它系統進行整合。
下面是廣播的特性:
1.廣播接收者的生命週期是非常短暫的,在接收到廣播的時候建立,onReceive()方法結束之後銷燬
2.廣播接收者中不要做一些耗時的工作,否則會彈出Application No Response錯誤對話方塊
3.最好也不要在廣播接收者中建立子執行緒做耗時的工作,因為廣播接收者被銷燬後進程就成為了空程序,很容易被系統殺掉
4.耗時的較長的工作最好放在服務中完成
說到Broadcast,那還必須說下BroadcastReceiver,從單詞意思可以看出是個廣播接受器,對它的作用就是接受廣播的一個元件,BroadcastReceiver是一種可以讓使用者接受廣播通知且沒有使用者介面的元件,在android系統中
Broadcast也有四種不同的分類:
- 普通廣播(Normal Broadcast)
- 系統廣播(System Broadcast)
- 有序廣播(Ordered Broadcast)
- 粘性廣播(Sticky Broadcast)
- App應用內廣播(Local Broadcast)
二 普通廣播(Normal Broadcast)
這種廣播所有監聽該廣播的接收器都可以監聽到該廣播,同一個級別的接受順序是隨機的或則說是無序的,接收器不能對收到的廣播做任何處理,也不能截斷廣播繼續傳播。該種類的廣播用sendBroadcast傳送。下面做個簡單的例子:package com.example.unorderbroadcasereceiver;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;
/**
* @author ZMC
*
*/
public class MyBroadcaseReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
// TODO Auto-generated method stub
Toast.makeText(context, "Normal broadcast", Toast.LENGTH_SHORT).show();
}
}
作為四大元件,還要記得在AndroidManifest中註冊:
<receiver android:name="com.example.unorderbroadcasereceiver.MyBroadcaseReceiver">
<intent-filter >
<action android:name="unorderbroadcast"/>
</intent-filter>
</receiver>
2.佈局檔案
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.example.unorderbroadcasereceiver.MainActivity" >
<Button android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/btn"
android:text="sendunordermessage"/>
</RelativeLayout>
這裡簡單的定義一個按鈕,點擊發送廣播
3. MainActivity.java
package com.example.unorderbroadcasereceiver;
import android.app.Activity;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
/**
* @author ZMC
*
*/
public class MainActivity extends Activity {
private MyBroadcaseReceiver myBroadcaseReceiver;
private Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
myBroadcaseReceiver = new MyBroadcaseReceiver();
//方法一:動態註冊
//方法二:在Androidmanifest.xml中註冊
IntentFilter filter = new IntentFilter();
filter.addAction("unorderbroadcast");
registerReceiver(myBroadcaseReceiver, filter );
//按鈕
button = (Button)findViewById(R.id.btn);
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
Intent intent = new Intent();
intent.setAction("unorderbroadcast");
sendBroadcast(intent);
}
});
}
@Override
protected void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
unregisterReceiver(myBroadcaseReceiver);
}
}
上述程式碼中也運用了動態註冊MyBroadcaseReceiver,如果使用該方法,就不用在AndroidManifest中另外註冊了,但是使用該方法,要記得在onDestory方法中記得unregisterReceiver,要不然在程式退出的時候會報如下錯誤:
4 結果
當點選按鈕傳送廣播後,因為BroadcastReceiver一直在後臺待命接收,當收到action為unorderbroadcast的廣播是,就做出響應。這裡就簡單的Toast。
三 系統廣播(System Broadcast)
android中內建了多個系統廣播:只要涉及到手機的基本操作(如開機、網路狀態變化、拍照等等),都會發出相應的廣波,每個廣播都有特定的Intent - Filter(包括具體的action),android常用系統廣播action如下:
Operation | action |
---|---|
監聽網路變化 | android.net.conn.CONNECTIVITY_CHANGE |
關閉或開啟飛航模式 | Intent.ACTION_AIRPLANE_MODE_CHANGED |
充電時或電量發生變化 | Intent.ACTION_BATTERY_CHANGED |
電池電量低 | Intent.ACTION_BATTERY_LOW |
電池電量充足(即從電量低變化到飽滿時會發出廣播 | Intent.ACTION_BATTERY_OKAY |
系統啟動完成後(僅廣播一次) | Intent.ACTION_BOOT_COMPLETED |
按下照相時的拍照按鍵(硬體按鍵)時 | Intent.ACTION_CAMERA_BUTTON |
螢幕鎖屏 | Intent.ACTION_CLOSE_SYSTEM_DIALOGS |
裝置當前設定被改變時(介面語言、裝置方向等) | Intent.ACTION_CONFIGURATION_CHANGED |
插入耳機時 | Intent.ACTION_HEADSET_PLUG |
未正確移除SD卡但已取出來時(正確移除方法:設定–SD卡和裝置記憶體–解除安裝SD卡) | Intent.ACTION_MEDIA_BAD_REMOVAL |
插入外部儲存裝置(如SD卡) | Intent.ACTION_MEDIA_CHECKING |
成功安裝APK | Intent.ACTION_PACKAGE_ADDED |
成功刪除APK | Intent.ACTION_PACKAGE_REMOVED |
重啟裝置 | Intent.ACTION_REBOOT |
螢幕被關閉 | Intent.ACTION_SCREEN_OFF |
螢幕被開啟 | Intent.ACTION_SCREEN_ON |
關閉系統時 | Intent.ACTION_SHUTDOWN |
重啟裝置 | Intent.ACTION_REBOOT |
四 有序廣播(Ordered Broadcast)
android中的有序廣播,也是一種比較常用的廣播,該種類的廣播用sendOrderedBroadcast傳送。,該中廣播主要有一下特性:按照接收者的優先順序來接收廣播,優先級別在intent-filter中的priority中宣告,-
1000
到
1000
之間,值越大優先順序越高,
- 可以終止廣播的繼續傳播,接受者可以修改intent的內容。
- 同級別接收順序是隨機的,級別低的後收到
- 能截斷廣播的繼續傳播,高級別的廣播接收器接收廣播後能決定時候截斷。能處理廣播
- 同級別動態註冊高於靜態註冊
package com.example.orderbroadcastreceiver;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.widget.Toast;
public class MyBroadcastReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
// TODO Auto-generated method stub
String data = intent.getStringExtra("data");
Toast.makeText(context, data, Toast.LENGTH_SHORT).show();
Bundle bundle = new Bundle();
bundle.putString("data", "from MyBroadcastReceiver");
setResultExtras(bundle );
}
}
SecondBroadcastReceiver.javapackage com.example.orderbroadcastreceiver;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.widget.Toast;
public class SecondBroadcastReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
// TODO Auto-generated method stub
Bundle bundle = getResultExtras(false);
String data = bundle.getString("data");
Toast.makeText(context, data, Toast.LENGTH_SHORT).show();
}
}
2.佈局檔案
這裡也是簡單的定義一個按鈕傳送有序廣播
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.example.orderbroadcastreceiver.MainActivity" >
<Button android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/btn"
android:text="sendordermessage"/>
</RelativeLayout>
3.MainActivity.java
package com.example.orderbroadcastreceiver;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
public class MainActivity extends Activity {
private MyBroadcastReceiver myBroadcastReceiver;
private Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button = (Button)findViewById(R.id.btn);
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
Intent intent = new Intent();
intent.putExtra("data", "from activity");
intent.setAction("orderbroadcast");
sendOrderedBroadcast(intent, null);
}
});
}
}
4.AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.orderbroadcastreceiver"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="14"
android:targetSdkVersion="21" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name=".MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver android:name="com.example.orderbroadcastreceiver.MyBroadcastReceiver">
<intent-filter android:priority="1000">
<action android:name="orderbroadcast"/>
</intent-filter>
</receiver>
<receiver android:name="com.example.orderbroadcastreceiver.SecondBroadcastReceiver">
<intent-filter android:priority="500">
<action android:name="orderbroadcast"/>
</intent-filter>
</receiver>
</application>
</manifest>
在這個例子中直接使用了靜態註冊的方式,並分別為兩個Receiver設定了優先順序。
4.結果
當程式之後,有MyBroadcastReceiver和SecondBroadcastReceiver在後臺待命接收action為orderbroadcast的廣播,點擊發送按鈕之後這兩個廣播就能先後接收到了。因為MyBroadcastReceiver的優先順序為1000,SecondBroadcastReceiver的優先順序為500,所以MyBroadcastReceiver先收到廣播,而SecondBroadcastReceiver後接收到廣播,同時SecondBroadcastReceiver接收到的是MyBroadcastReceiver處理後的廣播,因此它們兩個接收到的訊息不一樣。另外,優先順序高的Receiver可以對廣播進行截斷,截斷後比它優先順序低的Receiver就不能接收到該廣播了,若想截斷廣播,就只需在Receiver中的onReceive方法中執行abortBroadcast()方法即可。
五 粘性廣播(Sticky Broadcast)
粘性廣播的特點是Intent會一直保留到廣播事件結束,而這種廣播也沒有所謂的10秒限制,10秒限制是指普通的廣播如果onReceive方法執行時間太長,超過10秒的時候系統會將這個廣播置為可以幹掉的candidate,一旦系統資源不夠的時候,就會幹掉這個廣播而讓它不執行。該廣播用sendStickyBroadcast傳送。粘性廣播例子: 1.自定義MyBroadcastReceiver MyBroadcastReceiver.java
package com.example.stickybroadcastdemo;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;
public class MyBroadcastReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
// TODO Auto-generated method stub
Toast.makeText(context, "stickybroadcast", Toast.LENGTH_SHORT).show();
}
}
2.佈局檔案
定義兩個按鈕,一個按鈕傳送粘性廣播,一個按鈕註冊Receiver
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:orientation="vertical"
tools:context="com.example.stickybroadcastdemo.MainActivity" >
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/btn"
android:text="sendstickybroadcast" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/btn1"
android:text="registerReceiver" />
</LinearLayout>
3.MainActivity.java
package com.example.stickybroadcastdemo;
import android.app.Activity;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
/**
* @author ZMC
*
*/
public class MainActivity extends Activity {
private Button button,button1;
MyBroadcastReceiver myBroadcastReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button = (Button)findViewById(R.id.btn);
button1 = (Button)findViewById(R.id.btn1);
//傳送廣播
button.setOnClickListener(new OnClickListener() {
@SuppressWarnings("deprecation")
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
Intent intent = new Intent("stickybroadcast");
sendStickyBroadcast(intent);
}
});
//點選註冊Receiver
button1.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
reg(v);
}
});
}
//動態註冊Receiver
private void reg(View view){
IntentFilter filter = new IntentFilter();
filter.addAction("stickybroadcast");
myBroadcastReceiver = new MyBroadcastReceiver();
registerReceiver(myBroadcastReceiver, filter);
}
@Override
protected void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
unregisterReceiver(myBroadcastReceiver);
}
}
上述程式碼中button1按鈕動態註冊Receiver,button傳送粘性廣播,同時特別注意:傳送粘性廣播要使用這個api需要許可權android.Manifest.permission.BROADCAST_STICKY,在AndroidManifest.xml中配置如下:
<uses-permission android:name="android.permission.BROADCAST_STICKY"/>
4.結果當點選sendbroadcast按鈕是,因為沒有註冊Receiver,所以沒有響應,這時再點選registerReceiver按鈕時,動態註冊Receiver,這時就顯示之前傳送廣播的訊息。因此這種廣播就是Intent會一直保留到廣播事件結束,即使Recceiver沒有註冊。
六 App應用內廣播(Local Broadcast)
因為android中的廣播是可以跨域的(跨App),因此可能存在一下問題:- 其他App針對性發出與當前App intent-filter相匹配的廣播,由此導致當前App不斷接收廣播並處理;
- 其他App註冊與當前App一致的intent-filter用於接收廣播,獲取廣播具體資訊; 即會出現安全性 & 效率性的問題。
app test的程式碼
test app中只有一個MyBroadcaseReceiver來接受另一個app傳送的廣播: MyBroadcaseReceiver.javapackage com.example.unorderbroadcasereceiver;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;
/**
* @author ZMC
*
*/
public class MyBroadcaseReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
// TODO Auto-generated method stub
Toast.makeText(context, "App 2:Normal broadcast", Toast.LENGTH_SHORT).show();
}
}
同時註冊Receiver
<receiver android:name="com.example.test.MyBroadcaseReceiver">
<intent-filter >
<action android:name="unorderbroadcast"/>
</intent-filter>
</receiver>
App 2的程式碼:
1.自定義一個廣播接收器MyBroadcaseReceiver.package com.example.unorderbroadcasereceiver;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;
/**
* @author ZMC
*
*/
public class MyBroadcaseReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
// TODO Auto-generated method stub
Toast.makeText(context, "App 2:Normal broadcast", Toast.LENGTH_SHORT).show();
}
}
2.佈局檔案
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.example.unorderbroadcasereceiver.MainActivity" >
<Button android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/btn"
android:text="sendunordermessage"/>
</RelativeLayout>
這裡簡單的定義一個按鈕,點擊發送廣播
3. MainActivity.java
package com.example.unorderbroadcasereceiver;
import android.app.Activity;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
/**
* @author ZMC
*
*/
public class MainActivity extends Activity {
private MyBroadcaseReceiver myBroadcaseReceiver;
private Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
myBroadcaseReceiver = new MyBroadcaseReceiver();
//方法一:動態註冊
IntentFilter filter = new IntentFilter();
filter.addAction("unorderbroadcast");
registerReceiver(myBroadcaseReceiver, filter );
//按鈕
button = (Button)findViewById(R.id.btn);
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
Intent intent = new Intent();
intent.setAction("unorderbroadcast");
sendBroadcast(intent);
}
});
}
@Override
protected void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
unregisterReceiver(myBroadcaseReceiver);
}
}
4.結果:
以上沒有設定廣播為App內廣播,以此另一個app也能接受到廣播。 5.下面將App 2中的廣播改為App應用內廣播,只需修改MainActivity.java中的程式碼即可,改後的程式碼如下:
package com.example.unorderbroadcasereceiver;
import android.app.Activity;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.support.v4.content.LocalBroadcastManager;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
/**
* @author ZMC
*
*/
public class MainActivity extends Activity {
private MyBroadcaseReceiver myBroadcaseReceiver;
LocalBroadcastManager localBroadcastManager;
private Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
myBroadcaseReceiver = new MyBroadcaseReceiver();
//方法一:動態註冊
IntentFilter filter = new IntentFilter();
filter.addAction("unorderbroadcast");
//例項化LocalBroadcastManager的例項
localBroadcastManager = LocalBroadcastManager.getInstance(this);
//呼叫LocalBroadcastManager單一例項的registerReceiver()方法進行動態註冊
localBroadcastManager.registerReceiver(myBroadcaseReceiver, filter);
//按鈕
button = (Button)findViewById(R.id.btn);
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
//傳送應用內廣播
Intent intent = new Intent();
intent.setAction("unorderbroadcast");
localBroadcastManager.sendBroadcast(intent);
}
});
}
@Override
protected void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
//取消註冊應用內廣播接收器
localBroadcastManager.unregisterReceiver(myBroadcaseReceiver);
}
}
需要注意的是對於LocalBroadcastManager方式傳送的應用內廣播,只能通過LocalBroadcastManager動態註冊,不能靜態註冊。 6.結果:
. 設定廣播為App應用內廣播,廣播就不能跨域了,一次只能在傳送廣播的app(App2)內接受,而另外一個app(app test)接收不到app2傳送的廣播。
七 總結
android中的廣播分為五種:普通廣播、系統廣播、有序廣播、粘性廣播、App應用內廣播,不用的廣播都有不同的使用場景。BroadcastReceiver有靜態註冊和動態註冊兩種方式,當用動態註冊方式時要記得銷燬的時候取消註冊。對於不同註冊方式的廣播接收器回撥OnReceive(Context context,Intent intent)中的context返回值是不一樣的:
- 對於靜態註冊(全域性+應用內廣播),回撥onReceive(context, intent)中的context返回值是:ReceiverRestrictedContext;
- 對於全域性廣播的動態註冊,回撥onReceive(context, intent)中的context返回值是:Activity Context;
- 對於應用內廣播的動態註冊(LocalBroadcastManager方式),回撥onReceive(context, intent)中的context返回值是:Application Context。