學習 | Android開發之詳解廣播機制

案例最后效果:

廣播的類型:

標準廣播

標準廣播是完全異步的廣播, 也就是當廣播發出之后, 所有的廣播接收器機會都會在同一時刻接收到這條廣播, 標準廣播無法被攔截。

有序廣播

有序廣播是同步執行的廣播, 也就是當廣播發出之后, 同一時刻只會有一個廣播接收器能夠收到這條廣播消息, 依次傳遞。優先級高的廣播接收器可以優先接收廣播消息, 同時前面的廣播接收器還可以截取正在傳遞的廣播, 這樣后面的廣播就無法收到廣播消息了。

接收系統廣播

Android內置了很多系統級別的廣播, 比如:手機開機完成后會發出一條廣播, 電池電量發生變化會發出一條廣播, 時間或者時區發生變化會發出一條廣播等等。如果希望接收這些廣播, 就需要使用廣播接收器, 下面來看具體用法。

動態注冊監聽網絡變化

注冊廣播一般有兩種方式:
動態注冊:在代碼中注冊
靜態注冊:在AndroidManifest.xml中注冊

首先我們來嘗試一下動態注冊:
只需要創建一個類BroadcaseDemo, 讓它繼承自Broadcast-Receiver,并重寫父類的onReceive()方法就行了. onReceive()用來監聽廣播的到來, 具體邏輯也可以在這個方法中處理.

MainActivity.java

public class MainActivity extends AppCompatActivity {

    private IntentFilter intentFilter;
    private NetWorkChangeReceiver networkChangeReceiver;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        intentFilter = new IntentFilter();
        intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
        networkChangeReceiver = new NetWorkChangeReceiver();
        registerReceiver(networkChangeReceiver, intentFilter);
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        unregisterReceiver(networkChangeReceiver);
    }
    class NetWorkChangeReceiver extends BroadcastReceiver{
        @Override
        public void onReceive(Context context, Intent intent) {
            Toast.makeText(context, "network changes", Toast.LENGTH_SHORT).show();
        }
    }
}

說明: NetWorkChangeReceiver ()繼承BroadcastReceiver, 并重寫了父類的onReceive()方法. 這樣每當網絡發生變化的時候, onReceive()方法就會得到執行.

onCreate()方法, 我們首先創建一個IntentFilter的實例, 并未它添加了一個值為android.net.conn.CONNECTIVITY_CHANGE的action, 為什么要添加這個呢?因為當網絡發生變化的時候, 系統發出的正是一條值為android.net.conn.CONNECTIVITY_CHANGE的廣播. 也就是說我們的廣播接收器想要監聽什么廣播, 就在這里添加相應的action.

接下來創建一個NetWorkChangeReceiver的實例, 然后調用registerReceiver()方法進行注冊.

切記: 動態注冊的廣播接收器一定要取消注冊才行, 這里我們是在onDestroy()方法中調用unregisterReceiver()方法來實現.

運行結果:


下面繼續進行優化, 直接判斷之后告訴用戶, 當前環境是否有網.
MainActivity.java

  class NetWorkChangeReceiver extends BroadcastReceiver{
        @Override
        public void onReceive(Context context, Intent intent) {
            // 獲取ConnectivityManager實例, 這是一個系統服務類, 專門用于管理網絡連接
            ConnectivityManager connectivityManager = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
            // 調用getActiveNetworkInfo()得到NetworkInfo的實例
            NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
            // 通過isAvailable()方法判斷當前是否有網絡
            if(networkInfo!= null && networkInfo.isAvailable()){
                Toast.makeText(context, "network is available", Toast.LENGTH_SHORT).show();
            } else {
                Toast.makeText(context, "network is unavailable", Toast.LENGTH_SHORT).show();
            }
        }
    }

注意: Android系統為了保護用戶設備的安全和隱私, 做了嚴格的規定: 如果程序需要進行一些對用戶來說比較敏感的操作, 就必須在配置文件AndroidManifest.xml中聲明權限才可以, 否者程序會直接崩潰. 比如這里訪問系統的網絡狀態就是需要聲明權限的, 所以我們需要添加下面的代碼.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.junzaivip.broadcastdemo">
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
...
</manifest>
效果演示:
靜態注冊實現開機啟動

動態注冊的廣播接收器可以自由的控制注冊與注銷, 在靈活性方面有很大的優勢, 但是它必須在程序啟動之后才能接收到廣播, 因為注冊的邏輯寫在onCreate()方法中. 那么有沒有什么辦法可以讓程序在未啟動的情況下就能接收到廣播呢? 這就需要使用靜態注冊的方式了.

準備實現一個開機啟動的功能, 我們準備讓程序接收一條開機廣播, 當收到這條廣播時, 就可以在onReceive()方法里執行相應的邏輯.

下面我們使用AS的快捷方式來創建一個廣播接收器.

可以看到我們將廣播接收器命名為BootCompleteReceiver, Exported屬性表示是否允許這個廣播接收器接收本程序以外的廣播, Enabled屬性表示是否啟用這個廣播接收器, 默認是勾選的, 然后點擊finish完成創建.

修改完之后的代碼如下:

public class BootCompleteReceive extends BroadcastReceiver {
    public BootCompleteReceive() {
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        Toast.makeText(context, "Boot Complete", Toast.LENGTH_SHORT).show();
    }
}

代碼非常簡單, 我們只是在onReceive()方法中使用Toast彈出一段提示信息.

另外, 靜態的廣播接收器一定要在AndroidManifest.xml文件中注冊才可以使用, 不過由于我們是使用的Android Studio的快捷方式創建的廣播接收器, 因此注冊這一步已經被自動完成了. 我們一起去看一看.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.junzaivip.broadcastdemo">

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

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <receiver
            android:name=".BootCompleteReceive"
            android:enabled="true"
            android:exported="true"></receiver>
    </application>

</manifest>

說明: 可以看到在</application>的標簽內出現了一個新的標簽<receiver>, 所有的靜態廣播接收器都是在這里進行注冊的, 它的用法其實和<activity>標簽非常相似, 也是通過android:name來指定具體哪一個廣播接收器, 而android:enabled 和android:exported屬性則是根據我們剛才勾選的狀態自動生成的.

不過目前BootCompleteReceive還是不能接收到開機廣播, 我們需要對AndroidManifest.xml文件進行修改才行, 如下所示:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.junzaivip.broadcastdemo">
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <receiver
            android:name=".BootCompleteReceive"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.LOCKED_BOOT_COMPLETED"/>
            </intent-filter>
            
        </receiver>
    </application>

</manifest>

主要更新了下面的文件:
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<intent-filter>
<action android:name="android.intent.action.LOCKED_BOOT_COMPLETED"/>
</intent-filter>

說明: 由于Android系統啟動完成后, 會發出android.permission.RECEIVE_BOOT_COMPLETED的廣播, 因此我們在<intent-filter>標簽里添加了相應的action. 另外監聽系統開機廣播也是需要聲明權限的, 所以添加了一條<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>權限.

當然我們程序中只是簡單使用了Toast提示了一段文本信息, 當你真正在項目中使用的時候, 就可以添加自己的邏輯.

需要注意的是, 不要在onReceive()方法中添加過多的邏輯或者進行任何的耗時操作, 因為在廣播接收器中是不允許開啟線程的. onReceive()方法中進行了較長時間而沒有結束時, 程序就會報錯.

廣播接收器, 更多的是扮演一種打開程序或者其他組件的角色, 比如創建一條狀態欄通知, 或者啟動一個服務等.

發送自定義廣播

前面的例子已經了解了接收系統廣播, 接下來我們一起來學習在應用程序中發送自定義廣播.

發送標準廣播

首先定義一個廣播接收器用來接收廣播, 代碼如下:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.junzaivip.broadcastdemo">

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

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <receiver
            android:name=".MyBroadcastReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="com.junzaivip.broadcastdemo.MY_BROADCAST"/>
            </intent-filter>
        </receiver>
    </application>

</manifest>

可以看到, 這里讓MyBroadcastReceiver接收一條值為com.junzaivip.broadcastdemo.MY_BROADCAST的廣播, 因此待會兒在發送廣播的時候, 就需要發出一條這樣的廣播.

接下來修改activity_main.xml中的代碼, 如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   android:id="@+id/activity_main"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:orientation="vertical">

   <Button
       android:id="@+id/send_cast"
       android:layout_width="match_parent"
       android:layout_height="wrap_content" 
       android:text="@string/send_broadcast"/>
</LinearLayout>

在布局中定義了一個按鈕, 用于發送廣播. 然后修改MainActivity中的代碼.

protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button sendButton = (Button) findViewById(R.id.send_cast);
        sendButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent("com.junzaivip.broadcastdemo.MY_BROADCAST");
                sendBroadcast(intent);
            }
        });
    }

說明: 按鈕的點擊事件里, 加入了自定義廣播的邏輯. 首先構建出一個對象Intent對象, 并把要發送廣播的值傳入, 然后調用sendBroadcast()方法將廣播發送出去, 這樣所有監聽com.junzaivip.broadcastdemo.MY_BROADCAST這條廣播的廣播接收器就會收到消息. 此時發出去的廣播就是一條標準廣播. 由于廣播是通過Intent進行傳遞, 所以還可以在Intent中添加一些數據傳遞給廣播接收器.

運行效果:

擴展 通過Intent來傳遞和接收數據方式:
通過intent來傳遞數據(字符串):

Intent intent = new Intent();
intent.setClass(DetailActivity.this, HistoryActivity.class);
intent.putExtra("","");
intent.putExtra("","");
intent.putExtra("","");
startActivity(intent);

通過intent來傳遞對象, 則在javabean需要實現序列化接口

import java.io.Serializable;
public class DetailModel implements Serializable{
}
Intent intent = new Intent();
intent.setClass(LeaderScheduleClientDetailActivity.this, LeaderScheduleContactHistoryActivity.class);
Bundle bundle = new Bundle();
bundle.putSerializable("contactHistorys",item);
intent.putExtras(bundle);
startActivity(intent);

接收intent傳遞過來的字符串:

true_exhibitionID = getIntent().getStringExtra("true_exhibitionID");

接收intent傳遞過來的對象

Bundle bundle = getIntent().getExtras();
model = (GroupModel) bundle.getSerializable("groupModel");

發送有序廣播

因為廣播是一種可以跨進程的通信方式, 因此我們應用程序內發出的廣播, 其他的應用程序應該也是可以收到的.

下面我們就來一個例子, 通過第一個程序發送一個廣播, 打開另外一個程序, 并且在第二個程序中顯示自定義內容.

在另外一個程序中添加AnotherBroadcastReceiver, 代碼如下:

public class AnotherBroadcastReceiver extends BroadcastReceiver {
    public AnotherBroadcastReceiver() {
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        Intent i = new Intent();
        i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        i.setClass(context,MainActivity.class);
        context.startActivity(i);
        Toast.makeText(context, "另外一個廣播", Toast.LENGTH_SHORT).show();
    }
}

在AndroidManifest.xml中配置:

 <receiver
            android:name=".AnotherBroadcastReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="com.junzaivip.broadcastdemo.MY_BROADCAST"/>
            </intent-filter>
</receiver>

說明: Intent i = new Intent();
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
i.setClass(context,MainActivity.class);
context.startActivity(i); 這一段就是核心代碼, 用來打開另外一個程序.

這樣就可以通過廣播打開另外一個應用程序了, 也可以顯示內容. 下面是效果演示:

這樣就強有力的證明了, 我們的應用程序發出的廣播是可以被其他應用程序接收到的.

不過到目前為止, 程序里發出的廣播都還是標準廣播, 現在我們來嘗試一下發送有序廣播.
重新回到broadcastDemo項目, 然后修改MainActivity中的代碼, 如下:

將 sendBroadcast(intent); 修改為 sendOrderedBroadcast(intent,null);

可以看到發送有序廣播只需要改動一行代碼, 即將sendBroadcast()方法改成sendOrderedBroadcast()方法.

sendOrderedBroadcast()方法接收兩個參數, 第一個參數仍然是Intent, 第二個參數是一個與權限相關的字符串, 這里傳入null就行了. 現在重新運行程序, 并點擊Send Broadcast按鈕, 你會發現兩個應用程序都還是可以接收到這條廣播.

看上去貌似沒什么區別, 這個時候廣播接收器是有先后順序的. 而且前面的廣播接收器還可以將廣播截斷, 以阻止其繼續傳播.

通過android:priority屬性給廣播接收器設置優先級, 優先級比較高的廣播就可以先收到廣播. 下面給它設置了100, 以保證它一定會會在另一個之前收到廣播. 同時它也有權限來決定是否允許廣播繼續傳遞.
AndroidManifest.xml

 <receiver
            android:name=".MyBroadcastReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter android:priority="100">
                <action android:name="com.junzaivip.broadcastdemo.MY_BROADCAST"/>
            </intent-filter>
        </receiver>

下面我們來配置讓第一個應用程序阻止繼續傳遞.


public class MyBroadcastReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        Toast.makeText(context, "歡迎關注公眾號:史慧君, 測試接收一個自定義廣播", Toast.LENGTH_SHORT).show();
        abortBroadcast();
    }
}

說明:
abortBroadcast();表示將這條廣播階段, 后面的廣播接收器將無法再繼續接收這條廣播.

使用本地廣播

前面我們發送和接收的廣播全部屬于系統全局廣播, 即發出的廣播可以被其他任何應用程序接收到, 并且我們也可以接收來自其他任何應用程序的廣播. 這樣就很容易引起安全感的問題. 比如我們發送一條帶有關鍵性數據的廣播就可能被其他的應用程序截獲, 或者其他的應用程序不停的向我們的廣播接收器里發送各種垃圾廣播.

為了能簡單解決廣播這個安全問題, Android引入了一套本地廣播機制, 使用這個機制發出的廣播只能夠在應用程序的內部進行傳遞, 并且廣播接收器也只能接收來自本應用程序發出的廣播, 這樣所有的安全問題, 就不存在了.

本地廣播的用法并不復雜, 主要就是使用了一個LocalBroadcastManager, 來對廣播進行管理, 并提供發送廣播和注冊廣播接收器的方法. 下面我們通過具體的實例來嘗試他的用法.

修改MainActivity.java中的代碼:

public class MainActivity extends AppCompatActivity {

    private IntentFilter intentFilter;
    private NetWorkChangeReceiver networkChangeReceiver;
    private LocalReveiver localReceiver;
    private LocalBroadcastManager localBroadcastManager;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        localBroadcastManager = LocalBroadcastManager.getInstance(this); //獲取實例
        Button sendButton = (Button) findViewById(R.id.send_cast);
        sendButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent("com.junzaivip.broadcasttest.LOCAL_BORADCAST");
                localBroadcastManager.sendBroadcast(intent);//發送本地廣播
            }
        });


        intentFilter = new IntentFilter();
        intentFilter.addAction("com.junzaivip.broadcasttest.LOCAL_BORADCAST");
        localReceiver = new LocalReveiver();
        localBroadcastManager.registerReceiver(localReceiver, intentFilter); //注冊廣播監聽器


    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        unregisterReceiver(networkChangeReceiver);
    }
    class NetWorkChangeReceiver extends BroadcastReceiver{
        @Override
        public void onReceive(Context context, Intent intent) {
            // 獲取ConnectivityManager實例, 這是一個系統服務類, 專門用于管理網絡連接
            ConnectivityManager connectivityManager = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
            // 調用getActiveNetworkInfo()得到NetworkInfo的實例
            NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
            // 通過isAvailable()方法判斷當前是否有網絡
            if(networkInfo!= null && networkInfo.isAvailable()){
                Toast.makeText(context, "網絡可用", Toast.LENGTH_SHORT).show();
            } else {
                Toast.makeText(context, "網絡不可用", Toast.LENGTH_SHORT).show();
            }
        }
    }


    class  LocalReveiver extends BroadcastReceiver{
        @Override
        public void onReceive(Context context, Intent intent) {
            Toast.makeText(context, "reveived local broadcast", Toast.LENGTH_SHORT).show();
        }
    }
}

說明: LocalBroadcastManager.getInstance()方法得到了它的一個實例, 注冊廣播接收器的時候調用的是LocalBroadcastManager的registerReceiver()方法.
發送廣播的時候調用的是localBroadcastManager.sendBroadcast()方法.

運行效果

需要說明: 本地廣播無法通過靜態注冊的方式來接收. 其實這也完全可以理解. 因為靜態注冊主要就是為了讓程序在未啟動的情況下也能進行廣播, 而發送本地廣播時, 我們的程序肯定是已經啟動了的. 因此也完全不需要使用靜態注冊的方法.

使用本地廣播的幾點優勢:
可以明確的知道正在發送的廣播不會離開我們的程序, 因此不必擔心機密數據泄露.

其他程序無法將廣播發送到我們程序內部, 因此不需要擔心會有安全漏洞的隱患.

發送本地廣播比發送系統全局廣播會更加有效.

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容