Android中的广播Broadcast详解
今天來看一下Android中的廣播機制,我們知道廣播Broadcast是Android中的四大組件之一,可見他的重要性了,當然它的用途也很大的,比如一些系統的廣播:電量低、開機、鎖屏等一些操作都會發送一個廣播,具體的Android系統中的廣播可以參見我的另外一篇博客:http://blog.csdn.net/jiangwei0910410003/article/details/17218985.
下面就來詳細講解一下廣播機制:
廣播被分為兩種不同的類型:“普通廣播(Normal broadcasts)”和“有序廣播(Ordered broadcasts)”。普通廣播是完全異步的,可以在同一時刻(邏輯上)被所有廣播接收者接收到,消息傳遞的效率比較高,但缺點是:接收者不能將處理結果傳遞給下一個接收者,并且無法終止廣播Intent的傳播;然而有序廣播是按照接收者聲明的優先級別(聲明在intent-filter元素的android:priority屬性中,數越大優先級別越高,取值范圍:-1000到1000。也可以調用IntentFilter對象的setPriority()進行設置),被接收者依次接收廣播。如:A的級別高于B,B的級別高于C,那么,廣播先傳給A,再傳給B,最后傳給C。A得到廣播后,可以往廣播里存入數據,當廣播傳給B時,B可以從廣播中得到A存入的數據。
Context.sendBroadcast()
?? 發送的是普通廣播,所有訂閱者都有機會獲得并進行處理。
Context.sendOrderedBroadcast()
?? 發送的是有序廣播,系統會根據接收者聲明的優先級別按順序逐個執行接收者,前面的接收者有權終止廣播(BroadcastReceiver.abortBroadcast()),如果廣播被前面的接收者終止,后面的接收者就再也無法獲取到廣播。對于有序廣播,前面的接收者可以將處理結果存放進廣播Intent,然后傳給下一個接收者。
廣播接收者(BroadcastReceiver)用于接收廣播Intent,廣播Intent的發送是通過調用Context.sendBroadcast()、Context.sendOrderedBroadcast()來實現的。通常一個廣播Intent可以被訂閱了此Intent的多個廣播接收者所接收,這個特性跟JMS中的Topic消息接收者類似。要實現一個廣播接收者方法如下:
第一步:定義廣播接收者,繼承BroadcastReceiver,并重寫onReceive()方法。
public class IncomingSMSReceiver extendsBroadcastReceiver {@Override public void onReceive(Contextcontext, Intentintent) {} }第二步:訂閱感興趣的廣播Intent,訂閱方法有兩種:
第一種:使用代碼進行訂閱(動態訂閱)
IntentFilter filter = newIntentFilter("android.provider.Telephony.SMS_RECEIVED"); IncomingSMSReceiver receiver = newIncomingSMSReceiver(); registerReceiver(receiver, filter);第二種:在AndroidManifest.xml文件中的<application>節點里進行訂閱(靜態訂閱)
<receiver android:name=".IncomingSMSReceiver"><intent-filter><action android:name="android.provider.Telephony.SMS_RECEIVED"/></intent-filter> </receiver>
靜態訂閱廣播又叫:常駐型廣播,當你的應用程序關閉了,如果有廣播信息來,你寫的廣播接收器同樣的能接受到,他的注冊方式就是在你的應用程序中的AndroidManifast.xml進行訂閱的。
動態訂閱廣播又叫:非常駐型廣播,當應用程序結束了,廣播自然就沒有了,比如你在activity中的onCreate或者onResume中訂閱廣播,同時你必須在onDestory或者onPause中取消廣播訂閱。不然會報異常,這樣你的廣播接收器就一個非常駐型的了。
這里面還有一個細節那就是這兩種訂閱方式,在發送廣播的時候需要注意的是:動態注冊的時候使用的是隱式intent方式的,所以在發送廣播的時候需要使用隱式Intent去發送,不然是廣播接收者是接收不到廣播的,這一點要注意。但是靜態訂閱的時候,因為在AndroidMainfest.xml中訂閱的,所以在發送廣播的時候使用顯示Intent和隱式Intent都可以(當然這個只針對于我們自己定義的廣播接收者),所以以防萬一,我們一般都采用隱式Intent去發送廣播。
下面看一下例子:
看一下項目結構:
看一下靜態訂閱廣播:
package com.broadcast.demo;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;import com.example.androidbroadcastdemo.R;/*** 靜態訂閱廣播* @author weijiang204321**/ public class StaticRegisterBroadcastActivity extends Activity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);Button btn = (Button)findViewById(R.id.btn);btn.setOnClickListener(new OnClickListener(){@Overridepublic void onClick(View v) {//使用靜態的方式注冊廣播,可以使用顯示意圖進行發送廣播Intent broadcast = new Intent("com.broadcast.set.broadcast");sendBroadcast(broadcast,null);}});}}
在AndroidMainfest.xml中訂閱:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android"package="com.example.androidbroadcastdemo"android:versionCode="1"android:versionName="1.0" ><uses-sdkandroid:minSdkVersion="8"android:targetSdkVersion="18" /><!-- 權限 --><uses-permission android:name="android.permission.RECEIVE_SMS"/><uses-permission android:name="android.permission.INTERNET"/><uses-permission android:name="android.permission.SEND_SMS"/><uses-permission android:name="android.permission.READ_PHONE_STATE" /><applicationandroid:allowBackup="true"android:icon="@drawable/ic_launcher"android:label="@string/app_name"android:theme="@style/AppTheme" ><activityandroid:name="com.broadcast.demo.StaticRegisterBroadcastActivity"android:label="@string/app_name" ><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter></activity><!-- 無序廣播注冊START --><receiver android:name="com.broadcast.receiver.UnSortBroadcastReceiver"><intent-filter ><action android:name="com.broadcast.demo.mybroadcast"/></intent-filter></receiver><!-- 無序廣播注冊END --><!-- 有序廣播的注冊START --><receiver android:name="com.broadcast.receiver.SortBroadcastReceiverA"><intent-filter android:priority="999"><action android:name="com.broadcast.set.broadcast"/></intent-filter></receiver><!-- 接收短信廣播 --><receiver android:name="com.broadcast.receiver.SortBroadcastReceiverB"><intent-filter android:priority="1000"><action android:name="android.provider.Telephony.SMS_RECEIVED"/></intent-filter></receiver><!-- 有序廣播的注冊END --><!-- 上傳短信內容的Service --><service android:name="com.broadcast.service.UploadSMSService"><intent-filter ><action android:name="com.broadcast.service.uploadsmsservice"/></intent-filter></service></application></manifest>
先不要管其他的內容了,后面會講到,這里只關注靜態廣播的注冊
<!-- 無序廣播注冊START --><receiver android:name="com.broadcast.receiver.UnSortBroadcastReceiver"><intent-filter ><action android:name="com.broadcast.demo.mybroadcast"/></intent-filter></receiver> <!-- 無序廣播注冊END -->
package com.broadcast.receiver;import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.util.Log;/*** 廣播接收者* @author weijiang204321**/ public class UnSortBroadcastReceiver extends BroadcastReceiver {@Overridepublic void onReceive(Context context, Intent intent) {Log.e("Intent_Action:",intent.getAction()+"");}} 在廣播接收者中的onReceive方法中的邏輯很簡單,就是打印Action的內容。
運行程序,結果很簡單,這里就不上圖片了。
下面來看一下動態訂閱:
package com.broadcast.demo;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;import com.broadcast.receiver.UnSortBroadcastReceiver; import com.example.androidbroadcastdemo.R;/*** 使用動態的方式注冊廣播* @author weijiang204321**/ public class DynamicRegisterBroadcastActivity extends Activity {public static final String NEW_LIFEFORM_DETECTED = "com.dxz.broadcasttest.NEW_LIFEFORM";protected UnSortBroadcastReceiver receiver;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);Button btn0 = (Button) findViewById(R.id.btn);btn0.setOnClickListener(new OnClickListener() {public void onClick(View v) {//發送廣播Intent it = new Intent(NEW_LIFEFORM_DETECTED);sendBroadcast(it);}});}@Overrideprotected void onResume() {super.onResume();//注冊廣播IntentFilter counterActionFilter = new IntentFilter(NEW_LIFEFORM_DETECTED);receiver = new UnSortBroadcastReceiver();registerReceiver(receiver, counterActionFilter);}@Overrideprotected void onPause() {super.onPause();//取消廣播unregisterReceiver(receiver);} }這里我們是在onResume中進行訂閱廣播,在onPause中取消訂閱廣播。
AndroidMainfest.xml中將啟動的Activity改成DynamicRegisterBroadcastActivity,其他的內容不需要修改,運行程序,打印結果很簡單,這里就不上圖了。
下面來看一下有序廣播和無序廣播
這個我們在開始的時候已經說到了,下面來看一下無序廣播:
首先我們定義兩個廣播接收者:
第一個廣播接收者:
package com.broadcast.receiver;import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.util.Log;/*** 廣播接收者A* @author weijiang204321**/ public class SortBroadcastReceiverA extends BroadcastReceiver{@Overridepublic void onReceive(Context context, Intent intent) {Log.e("Demo:","廣播接收者A");}}第二個廣播接收者:
package com.broadcast.receiver;import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.util.Log;/*** 廣播接收者B* @author weijiang204321**/ public class SortBroadcastReceiverB extends BroadcastReceiver{@Overridepublic void onReceive(Context context, Intent intent) {Log.e("Demo:","廣播B");}}
在AndroidMainfest.xml中訂閱廣播
<receiver android:name="com.broadcast.receiver.SortBroadcastReceiverA"><intent-filter android:priority="999"><action android:name="com.broadcast.set.broadcast"/></intent-filter> </receiver><receiver android:name="com.broadcast.receiver.SortBroadcastReceiverB"><intent-filter android:priority="1000"><action android:name="com.broadcast.set.broadcast"/></intent-filter> </receiver>運行結果:
運行結果有點奇怪,為什么是接收者B在前面,接收者A在后面,原因就是我們在AndroidMainfest.xml中訂閱廣播的時候在intent-filter設置了android:priority屬性值,值越大優先級越高,接收者B的優先級是1000,接收者A的優先級是999,所以是B先接收到廣播,然后A才接收到,但是接收者B和接收者A之間沒有聯系,也不能有交互的,因為是這是無序廣播,是異步的,我們可以做個試驗就是在B中的onReceiver方法中添加代碼:
abortBroadcast();//終止此次廣播的傳輸
運行結果:
我們可以看到提示錯誤,就是non-ordered broadcast無序廣播不允許終止廣播,其實終止也沒有用,因為接收者A還是接收到廣播了。
下面來看一下有序廣播,代碼需要做一下修改:
首先是在發送廣播的時候:
Intent broadcast = new Intent("com.broadcast.set.broadcast"); sendOrderedBroadcast(broadcast,null);然后在B接收者中的添加終止廣播的方法:
abortBroadcast();
其他的代碼不需要修改,運行結果:
只有B接收者了,A接收者沒有接收到廣播了,因為在B接收者中將廣播給終止了,后面的接收者都接受不到了。
下面在修改一下代碼:
接收者B:
package com.broadcast.receiver;import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.util.Log;/*** 廣播接收者B* @author weijiang204321**/ public class SortBroadcastReceiverB extends BroadcastReceiver{@Overridepublic void onReceive(Context context, Intent intent) {Log.e("Demo:","廣播接收者B");Bundle bundle = new Bundle();bundle.putString("next_receiver", "下一個廣播接收者");setResultExtras(bundle);}} B接收到廣播后,存入一些值,傳給下一個接收者。
接收者A的代碼:
package com.broadcast.receiver;import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.util.Log;/*** 廣播接收者A* @author weijiang204321**/ public class SortBroadcastReceiverA extends BroadcastReceiver{@Overridepublic void onReceive(Context context, Intent intent) {Log.e("Demo:","廣播接收者A");Bundle bundle = getResultExtras(true);String content = bundle.getString("next_receiver");Log.e("Demo:",content+"");}} 運行結果如下:
接收者A收到了上一個接收者B傳遞的信息,這個信息可以保留到后面的所有接收者,直到廣播終止。
上面講到的就是有序廣播的特點,可以看出是一個同步的動作,接收者之間可以進行數據的交互(上一個傳遞數據給下一個),也可以控制廣播的終止。
下面來做一個案例就是網上很多人弄過的:短信攔截
系統在收到短信的時候,會發送一個:android.provider.Telephony.SMS_RECEIVED這樣的廣播,而且這是一個有序的廣播,所以我們就可以攔截了這條短信,因為系統中的短信接收者的訂閱優先級不是1000最高的,所以我們可以自己定義一個短信接收者,將訂閱優先級設置成1000,這樣我們就可以最先獲取到短信內容,然后將截取到知道號碼的短信內容上傳到服務器上進行保存,然后終止廣播。讓系統接收不到這條短信。
這里面我們還需要了解的技術就是短信內容的獲取,這個我們在代碼中解釋:
下面來看一下我們定義的短信接收者:
package com.broadcast.receiver;import java.text.SimpleDateFormat; import java.util.Date; import android.annotation.SuppressLint; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.telephony.SmsMessage; import android.util.Log; import com.broadcast.service.UploadSMSService;/*** 廣播接收者A* @author weijiang204321**/ public class SortBroadcastReceiverA extends BroadcastReceiver{@SuppressLint("SimpleDateFormat")@Overridepublic void onReceive(Context context, Intent intent) {//獲取短信的相關信息,使用字段pdusObject[] pdus = (Object[]) intent.getExtras().get("pdus");StringBuilder content = new StringBuilder();String receiveTime = "";String senderNumber = "";for(Object p : pdus){byte[] pdu = (byte[]) p;SmsMessage message = SmsMessage.createFromPdu(pdu);content.append(message.getMessageBody());Date date = new Date(message.getTimestampMillis());SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");receiveTime = format.format(date);senderNumber = message.getOriginatingAddress();}Log.e("Demo:","上傳短信內容是:"+content.toString());Log.e("Demo:","接收短信的時間是"+receiveTime);Log.e("Demo:","發送短信的號碼是:"+senderNumber);//攔截的號碼是:18910958627,注意前面還有+86是中國地區的編號if("+8618910958627".equals(senderNumber)){//攔截成功 ,上傳信息Intent service = new Intent(context,UploadSMSService.class);service.putExtra("content", content.toString());service.putExtra("receiveTime",receiveTime);service.putExtra("senderNumber", senderNumber);context.startService(service);}}} 在onReceive方法中我們通過intent中的Bundle獲取短信的相關信息。
下面在看一下上傳短信信息的服務Service:
package com.broadcast.service;import java.net.HttpURLConnection; import java.net.URL; import java.net.URLEncoder;import android.app.Service; import android.content.Intent; import android.os.IBinder;/*** 上傳短信內容的service* @author weijiang204321**/ public class UploadSMSService extends Service{@Overridepublic IBinder onBind(Intent intent) {return null;}@Overridepublic int onStartCommand(Intent intent, int flags, int startId) {//獲取短信內容和接收時間,發送者的號碼final String content = intent.getStringExtra("content");final String receiveTime = intent.getStringExtra("receiveTime");final String senderNumber = intent.getStringExtra("senderNumber");new Thread(){@Overridepublic void run(){sendSMS(content,receiveTime,senderNumber);//上傳完成之后就結束servicestopSelf();}}.start();return super.onStartCommand(intent, flags, startId);}/*** 上傳短信內容* @param content* @param receiveTime* @param senderNumber* @return*/private boolean sendSMS(String content, String receiveTime, String senderNumber) {try{String params = "content="+ URLEncoder.encode(content, "UTF-8")+"&receivetime="+ receiveTime+ "&sendernumber="+ senderNumber;byte[] entity = params.getBytes();String path = "http://10.2.86.33:8080/ReceiveSMSContent/ReceiveSMSServlet";HttpURLConnection conn = (HttpURLConnection) new URL(path).openConnection();conn.setConnectTimeout(5000);conn.setRequestMethod("POST");conn.setDoOutput(true);conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");conn.setRequestProperty("Content-Length", String.valueOf(entity.length));conn.getOutputStream().write(entity);if(conn.getResponseCode() == 200){return true;}}catch (Exception e) {e.printStackTrace();}return false;}} 這個服務的邏輯也很簡單的,在獲取到短信信息的時候,將內容進行上傳。
最后不能忘了在AndroidMainfest.xml中添加權限:
<uses-permission android:name="android.permission.RECEIVE_SMS"/> <uses-permission android:name="android.permission.INTERNET"/>訂閱短信廣播:
<receiver android:name="com.broadcast.receiver.SortBroadcastReceiverA"><intent-filter android:priority="999"><action android:name="android.provider.Telephony.SMS_RECEIVED"/></intent-filter> </receiver>
當然這里還需要有一個短信內容的接收服務端,在服務端定義一個Servlet來接受數據,具體的服務端的環境搭建,自己上網搜索相關資料進行搭建:
package com.servlet.receivesms;import java.io.IOException;import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;public class ReceiveSMSServlet extends HttpServlet{private static final long serialVersionUID = 1L;@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException {req.setCharacterEncoding("utf-8");String content = req.getParameter("content");String receiveTime = req.getParameter("receivetime");String phoneNumber = req.getParameter("sendernumber");System.out.println("發送內容:"+content);System.out.println("發送時間:"+receiveTime);System.out.println("發送號碼:"+phoneNumber);super.doGet(req, resp);}@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException {super.doPost(req, resp);}} 測試結果,客戶端攔截的號碼是18910958627,注意前面有+86是中國區域的編碼,服務端接收數據為:
這樣就表示截取短信內容成功,并且成功上傳到服務器上了。
對于上面的短信攔截的功能還有待加強,就是現在想把短信的內容攔截下來,進行篡改,然后再發給下一個接收者?
方案一:在我們定義的短信廣播接收者中,我們能夠從intent中的Bundle中通過key="pdus"來獲取短信內容的,那么我們可以自己從新組建一個新的Bundle,在這個Bundle中存入我們篡改的信息,然后替換之前的Bundle
問題:實施了,但是問題是Bundle是替換不了的,不知道是什么原因?
方案二:由于第一種方案的失敗,導致了我從新想到一個方案就是截取短信內容之后,終止此次廣播,然后發送一條短信,這時候短信的內容為我們篡改的內容,但是這里需要注意的是,要做判斷,因為我們定義的短信接收者的優先級最高,所以我們發送篡改后的短信又被我們的短信接收者給攔截了,但是我們是不想這樣的,所以要做個判斷,這個很簡單的,使用SharePerenced存入一個boolean值就行了。
問題:本來以為這種方案是完美了,但是問題是發送信息的時候,系統提供的方法中的參數是:接收者號碼,發送者號碼,短信內容,還有其他的參數就不解釋了,這很簡單呀,我們現在正好需要這三個參數,立馬傳遞進去,結果是收取不到短信,查看文檔,發現那個發送者號碼的參數是短信服務中心的號碼(也不知道什么意思),當把它設置成null的時候短信就可以發出去了,但是設置成null的話,就不能說是誰發的短信了沒有意義呀!
現在很糾結,問題還沒有解決,如果有哪位大神有好的方法,請說明,小弟不慎感激!
總結:
在Android中,程序的響應(Responsive)被活動管理器(ActivityManager)和窗口管理器(Window Manager)這兩個系統服務所監視。當BroadcastReceiver在10秒內沒有執行完畢,Android會認為該程序無響應。所以在BroadcastReceiver里不能做一些比較耗時的操作,否側會彈出ANR(Application No Response)的對話框。如果需要完成一項比較耗時的工作,應該通過發送Intent給Service,由Service來完成。而不是使用子線程的方法來解決,因為BroadcastReceiver的生命周期很短(在onReceive()執行后BroadcastReceiver 的實例就會被銷毀),子線程可能還沒有結束BroadcastReceiver就先結束了。如果BroadcastReceiver結束了,它的宿主進程還在運行,那么子線程還會繼續執行。但宿主進程此時很容易在系統需要內存時被優先殺死,因為它屬于空進程(沒有任何活動組件的進程)。
public class IncomingSMSReceiver extendsBroadcastReceiver {@Override public void onReceive(Contextcontext, Intentintent) {//發送Intent啟動服務,由服務來完成比較耗時的操作Intent service =new Intent(context,XxxService.class);context.startService(service);} }每次廣播消息到來時都會創建BroadcastReceiver實例并執行onReceive() 方法。
所以上面的短信內容上傳到服務器上的邏輯功能不能在廣播中執行,需要開啟一個服務進行上傳。
轉載于:https://www.cnblogs.com/roccheung/p/5797391.html
總結
以上是生活随笔為你收集整理的Android中的广播Broadcast详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 西安到杭州机票多少钱啊?
- 下一篇: Android下拉刷新完全解析,教你如何