天天看點

Android廣播機制-Broadcast Receiver

廣播機制

為了進行系統級别的消息通知,Android引入了一套廣播消息機制,就像是我們上國小的時候,每個教室都會有個喇叭,到時間就會播放眼保健操一樣。

廣播有兩種:

  1. 标準廣播
  2. 有序廣播

标準廣播是一種異步執行的廣播,廣播發出之後,所有的廣播接收器都可以幾乎在同一時間接收到這條廣播,沒有先後接收的順序,标準廣播也不可以被截斷。

有序廣播是一種同步執行的廣播,廣播接收器接收廣播是有先後順序的,優先級高的接收器可以先接受廣播并且進行截斷,之後的廣播接收器就無法接受到此條廣播,同一時間隻有一個廣播接收器能夠接收這條廣播。

接收廣播

Android内置了許多系統級别的廣播,例如手機開機會發送一條廣播,電量變化會發送一條廣播,wifi連接配接也會發送一條廣播,同時我們也可以自定義廣播,如果我們想要接收到這些廣播就需要使用

廣播接收器

廣播接收器

可以對自己感興趣的廣播進行注冊,注冊之後就可以接收該廣播,并且在接收器内部的

onReceive()

方法進行邏輯處理。注冊的方式有兩種:

  1. 動态注冊:在代碼中通過registerReceiver()
  2. 靜态注冊:在Manifest檔案中進行注冊

動态注冊:

  • 建立一個類繼承BroadcastReceiver
  • 重寫父類的onReceive()方法實作相應邏輯處理
  • 建立一個IntentFliter并添加action,action就是我們需要監聽的廣播
  • 通過registerReceiver()方法進行注冊

靜态注冊:

  • 建立一個類繼承BroadcastReceiver
  • 重寫父類的onReceive()方法實作相應邏輯處理
  • 在AndroidManifest.xml通過标簽在中進行注冊

動态注冊接收系統廣播–wifi開關

package com.xiezhen.justwrite;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.NetworkInfo;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.util.Log;
import android.view.View;

public class MainActivity extends Activity {

    private TestBroadCastReceiver tb;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        wifiListener();
    }
    private void wifiListener() {
        IntentFilter intentFilter = new IntentFilter();
        //添加的action就是我們要監聽的廣播,wifi開關的時候,系統都會發送這條廣播。
        intentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
        tb = new TestBroadCastReceiver();
        //通過registerReceiver方法,将我們建立的Receiver以及 InterFilter傳入,完成注冊。
        registerReceiver(tb, intentFilter);
    }
    class TestBroadCastReceiver extends BroadcastReceiver {
        //重寫onReceive方法來進行一些接收到廣播後的邏輯處理,對使用者進行提醒。
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) {
                NetworkInfo networkInfo = intent.getParcelableExtra(
                WifiManager.EXTRA_NETWORK_INFO);
                //判斷目前wifi連接配接狀态
                NetworkInfo.State state = networkInfo.getState();
                if (state == NetworkInfo.State.DISCONNECTED) {
                    Log.d("xiezhen", "DISCONNECTED");
                } else if (state == NetworkInfo.State.CONNECTING) {
                    Log.d("xiezhen", "CONNECTING");
                } else if (state == NetworkInfo.State.CONNECTED) {
                    Log.d("xiezhen", "CONNECTED");
                }
            }
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unregisterReceiver(tb);
    }
}
           

需要注意的是上面這個例子,我們通路了網絡連接配接狀态,是以需要在AndroidManifest.xml中添加相應的權限

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

。這樣我們就可以在切換wifi開關的時候,在logcat檢視輸出的資訊。

靜态注冊接收系統廣播–開機

package com.xiezhen.justwrite;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;

public class StaticReceiver extends BroadcastReceiver{
    @Override
    public void onReceive(Context context, Intent intent) {
        Log.d("xiezhen","boot complete");
    }
}
           
<receiver android:name=".StaticReceiver" >
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED" />
    </intent-filter>
 </receiver>
           

建立一個類繼承BroadcastReceiver,之後在AndroidManifest.xml當中進行注冊,action就是需要監聽的廣播,也是系統開機後會發送的一條廣播。别忘了添加監聽開機廣播的權限

<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

在模拟器上測試的話,我們隻需要重新開機模拟器就可以在logcat當中看到boot complete這條資訊。

動态注冊與靜态注冊對比

  1. 動态注冊的廣播要在onDestroy方法中銷毀
  2. 可以自由注冊,銷毀廣播,但是程式未啟動時無法監聽廣播
  3. 靜态注冊的廣播在程式安裝後就自動啟動,監聽廣播

發送自定義廣播

之前我們都是接收系統發送的廣播,現在我們将發送自己定義的廣播,之前我們說了廣播有兩種:标準廣播和有序廣播。

發送标準廣播:

  1. 定義一個接收器
  2. 進行注冊(動态靜态都可)
  3. 通過sendBroadcast方法發送廣播
package com.xiezhen.justwrite;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;

public class CustomBroadCastReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        Log.d("xiezhen", "my custom receiver");
    }
}
           
<receiver android:name=".CustomBroadCastReceiver" >
   <intent-filter>
       <action android:name="my.custom.receiver" />
    </intent-filter>
  /receiver>
           
@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button = (Button) findViewById(R.id.button);
        button.setOnClickListener(new OnClickListener(){
            @Override
            public void onClick(View view){
            //建構一個Intent,然後将要發送的廣播傳入
                Intent intent=new Intent("my.custom.receiver");
                //調用sendBroadcast方法發送标準廣播
                sendBroadcast(intent);
            }
        };
    }
           

這樣所有監聽

my.custom.receiver

這條廣播的接收器都可以接收到這條廣播。

發送有序廣播:

我們通過sendBroadcast發送标準廣播。

隻需要将sendBroadcast方法換成sendOrderBroadcast方法即可發送有序廣播,此方法接收兩個參數,第一個參數就是建構廣播的Intent,第二個參數是一個與權限有關的字元串,傳入null即可。

package com.xiezhen.justwrite;


import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;

public class FirstOrderReceiver extends BroadcastReceiver{
    @Override
    public void onReceive(Context context, Intent intent) {
        Log.d("xiezhen","first");
        //abortBroadcast();
    }
}
           
<receiver android:name=".FirstOrderReceiver" >
    <intent-filter>
        <action android:name="order.broadcast" />
    </intent-filter>
</receiver>
           
@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button = (Button) findViewById(R.id.button);
        button.setOnClickListener(new OnClickListener(){
            @Override
            public void onClick(View view){
            //建構一個Intent,然後将要發送的廣播傳入
                Intent intent=new Intent("order.broadcast");
                //調用sendBroadcast方法發送有序廣播
                sendOrderBroadcast(intent,null);
            }
        };
    }
           

我們重新建立一個項目,然後定義一個廣播接收器,同樣接收

order.broadcast

這條廣播

package com.xiezhen.justwrite;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;


public class SecondOrderReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {

        Log.d("xiezhen","second");
    }
}
           
<receiver android:name=".SecondOrderReceiver" >
    <intent-filter>
        <action android:name="order.broadcast" />
    </intent-filter>
</receiver>
           

FirstOrderReceiver和SecondOrderReceiver兩個廣播接收器都可以接收到我們發送的自定義有序廣播,既然是有序廣播那麼就存在接收的先後順序,我們可以在注冊FirstOrderReceiver的時候,通過android:priority屬性設定優先級:

<receiver android:name=".FirstOrderReceiver" >
    <intent-filter android:priority="100">
        <action android:name="order.broadcast" />
    </intent-filter>
</receiver>
           

同時在FirstOrderReceiver的

onReceive

方法中,調用

abortBroadcast()

方法對這條廣播進行截斷,這樣就隻有FirstOrderReceiver可以接受到這條廣播,之後的SecondOrderReceiver無法接受到這條廣播。

使用本地廣播

廣播是跨程序的,我們可以發送的廣播可以被其他應用程式接收,我們也可以接受其他應用程式發送的廣播,這些廣播有可能是垃圾廣播,這種跨程序的全局性質的廣播會帶來一系列的安全問題,Android系統提供了一套本地廣播機制用來解決可能的安全性問題:用LocalBroadcastManager對廣播進行管理,提供注冊以及發送廣播的方法,使用LocalBroadcastManager發送的廣播隻能在本應用程式内傳遞,同樣的廣播接收器也隻能接收本應用程式内的廣播。

package com.xiezhen.justwrite;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.NetworkInfo;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;
import android.view.View;

public class MainActivity extends Activity {

    private LocalBroadCastReceiver lb;
    private LocalBroadcastManager localBroadcastManager;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        localBroadcastManager = LocalBroadcastManager.getInstance(this);
        lb = new LocalBroadCastReceiver();
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction("local.broadcast");
        localBroadcastManager.registerReceiver(lb, intentFilter);
    }

    public void btnSend(View view) {

        Intent intent = new Intent("local.broadcast");
        localBroadcastManager.sendBroadcast(intent);
    }

    class LocalBroadCastReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            Log.d("xiezhen","local broadcast");
        }
    }


    @Override
    protected void onDestroy() {
        super.onDestroy();
        localBroadcastManager.unregisterReceiver(lb);
    }
}
           

布局檔案:

<?xml version="1.0" encoding="utf-8"?>
<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"
    tools:context=".MainActivity">
    <Button
        android:onClick="btnSend"
        android:layout_centerInParent="true"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="send local broadcast" />
</RelativeLayout>
           

基本上和我們發送全局廣播的方式一樣,不同的是,通過

LocalBroadcastManager.getInstance(this)

擷取一個執行個體,然後調用

LocalBroadcastManager

registerReceiver()

方法濟甯注冊,調用

LocalBroadcastManager

sendBroadcast()

方法來發送廣播而已。同樣别忘了在

onDestroy()

方法中通過

localBroadcastManager.unregisterReceiver()

方法登出廣播,

附加

  1. 本地廣播隻能通過動态注冊的方式進行注冊
  2. 使用本地廣播不用擔心洩露資料
  3. 不用擔心接收到垃圾資訊,不用擔心安全隐患
  4. 比全局廣播更加高效

幾點注意事項

  1. onReceive()方法中不能進行耗時操作,也不能開線程進行異步操作,因為廣播接收器僅當onReceive這個回調方法執行的時候被認為是活躍的,onReceive方法執行完畢後,接收器是不活躍的,這個時候廣播接收器所在的程序很有可能會被殺死,如果進行異步操作的話,有可能程序已經被殺死,異步操作還沒有結束。可以選擇在onReceive方法中開啟一個服務去進行耗時的操作。
  2. 在onReceive中彈出一個Dialog可能會報錯

    Unable to add window [email protected] -- permission denied for this window type

    ,你可以将

    AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(context)

    中的context,改成

    xxx.this

    ;或者将Dialog設定為系統對話框

    dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);

    并且在AndroidManifest.xml中設定權限

    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

  3. 在BroadcastReceiver的onReceive(Context context, Intent intent)方法當中通過context.startActivity來啟動一個Activity的時候,需要設定啟動模式為

    FLAG_ACTIVITY_NEW_TASK

    ,否則會報錯,因為不設定這個NEW_TASK啟動模式的話,就沒有棧來存放啟動的Activity。