1. 程式人生 > >第一行程式碼——第五章:全域性大喇叭——詳解廣播機制

第一行程式碼——第五章:全域性大喇叭——詳解廣播機制

目錄:

5.1 廣播機制簡介

5.2 接收系統廣播

5.2.1 動態註冊監聽網路變化

5.2.2 靜態註冊實現開機啟動

5.3 傳送自定義廣播

5.3.1 傳送標準廣播

5.3.2 傳送有序廣播

5.4 使用本地廣播

5.5 廣播最佳實踐——實現強制下線功能

5.6 Git時間——初始版本控制工具

5.6.1 安裝Git

5.6.2 建立程式碼倉庫

5.6.3 提交原生代碼

5.7 小結與點評


知識點:

5.1 廣播機制簡介

Android中的廣播主要分為兩類:標準廣播和有序廣播。

標準廣播:是一種完全非同步執行的廣播,發出後所有廣播接收器幾乎同時接收到這條廣播,該型別廣播無法被截斷。
有序廣播:是一種同步執行的廣播,在廣播發出後同一時刻只有一個廣播接收器能夠接收到這條廣播訊息。優先順序高的廣播接收器先收到廣播訊息,並能截斷正在傳遞的廣播。

5.2 接收系統廣播

Android 內建了很多系統級別的廣播,我們可以在應用程式中通過監聽這些廣播來得到各種系統的狀態資訊。若想要接收到這些廣播,就需要使用廣播接收器。

5.2.1 動態註冊監聽網路變化

新建一個內部類繼承BroadcastReceiver 重寫onReceive 方法,當這樣有廣播來時,onReceive方法就會得到執行

注意不要忘記加網路許可權:

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

同時,因為我們是動態註冊,所以也需要在onDestory 方法中

unregisterReceiver(recevier);

package com.dak.administrator.firstcode.broadcast_recevier;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.Toast;

import com.dak.administrator.firstcode.R;

public class NetWorkActivity extends AppCompatActivity {

    NetworkChangeRecevier recevier;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_net_work);

        recevier = new NetworkChangeRecevier();
        IntentFilter filter = new IntentFilter();
        filter.addAction("android.NET.conn.CONNECTIVITY_CHANGE");
        registerReceiver(recevier, filter);
    }


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

    class NetworkChangeRecevier extends BroadcastReceiver{


        @Override
        public void onReceive(Context context, Intent intent) {
            if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) {
                ConnectivityManager manager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);


                if (manager.getActiveNetworkInfo() != null && manager.getActiveNetworkInfo().isAvailable()) {
                    Toast.makeText(NetWorkActivity.this, "網不錯 兄dei", Toast.LENGTH_SHORT).show();
                }else{
                    Toast.makeText(NetWorkActivity.this, "沒網了 兄dei", Toast.LENGTH_SHORT).show();
                }
            }

        }
    }

}

5.2.2 靜態註冊實現開機啟動

動態註冊的廣播接收器可以自由地控制註冊與登出,在靈活性方面有很大的優勢,但它也存在著一個缺點,即必須要在程式啟動之後才能接收到廣播,因為註冊的邏輯是寫在 onCreate()方法中的。使用靜態註冊的方式就可以讓程式在未啟動的情況下接收到廣播。

這裡我們準備讓程式接受一條開機廣播

新建BordercastReceiver

package com.dak.administrator.firstcode.broadcast_recevier;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;

import java.util.ArrayList;
import java.util.List;

public class BootCompleteReceiver extends BroadcastReceiver {


    private static List<Callback> callBack = new ArrayList<Callback>();
    private static final Object CALLBACK_LOCK = new Object();

    public interface Callback {
        void onReceive(Intent intent);
    }

    public static void registerCallback(Callback callback) {
        synchronized (CALLBACK_LOCK) {
            callBack.add(callback);
        }
    }

    public static void unRegisterCallback(Callback callback) {
        synchronized (CALLBACK_LOCK) {
            callBack.remove(callback);
        }
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        // TODO: This method is called when the BroadcastReceiver is receiving
        // an Intent broadcast.
        Toast.makeText(context, "開機了", Toast.LENGTH_SHORT).show();


        callBack(intent);
    }

    private static void callBack(Intent intent) {
        synchronized (CALLBACK_LOCK) {
            for (Callback callback : callBack) {
                if (callback != null) {
                    callback.onReceive(intent);
                }
            }
        }
    }
}

如果你是手動建立的需要在AndroidManifest.xml 中新增receiver

當然必不可少的是:

<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
  <receiver
            android:name=".broadcast_recevier.BootCompleteReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter>

                <!-- 開機啟動 -->
                <action android:name="android.intent.action.BOOT_COMPLETED" />
            </intent-filter>
        </receiver>

5.3 傳送自定義廣播

接下來學習如何在應用程式中傳送自定義廣播,通過實踐的方式來看一下標準廣播和有序廣播的區別。

5.3.1 傳送標準廣播

新建一個廣播接收器

package com.dak.administrator.firstcode.broadcast_recevier;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;

/**
 * Created by Administrator on 2018/9/30.
 */

public class MyBroadcastReceiver extends BroadcastReceiver {
    public static final String MY_ACTION = "my_action";

    @Override
    public void onReceive(Context context, Intent intent) {

        Toast.makeText(context, "received in MyBroadcastReceiver", Toast.LENGTH_SHORT).show();
//    abortBroadcast();   將廣播截斷
    }

    public static void sendBroadcast(Context context,Intent intent){
        intent.setAction(MY_ACTION);
        context.sendBroadcast(intent);
    }
}

而後在AndroidManifest.xml中新增receiver

 <receiver
            android:name=".broadcast_recevier.MyBroadcastReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter>

                <!-- 自定義廣播 -->
                <action android:name="com.example,broadcasttest.MY_BROADCAST" />
            </intent-filter>
        </receiver>

在Activity中去傳送這條廣播

package com.dak.administrator.firstcode.broadcast_recevier;

import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;

import com.dak.administrator.firstcode.R;

public class Main2Activity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);


    }

    public void send(View view) {

        MyBroadcastReceiver.sendBroadcast(this,new Intent());

    }
}

這樣就完成了傳送自定義廣播的功能。另外,由於廣播是使用 Intent 進行傳遞的,因此你還可以在 Intent 中攜帶一些資料傳遞給廣播接收器。

5.3.2 傳送有序廣播

只需要 傳送的時候呼叫 context.sendOrderedBroadcast(intent, ""); 就可以了。

另外我們可以在intent-filter 中新增 android:priority="100" 設定優先順序,數值越大,優先順序就高。

範圍是[-1000, 1000]  

在onReceive()中呼叫abortBroadcast ()方法,表示將這條廣播截斷

5.4 使用本地廣播

本地廣播只能夠在應用程式的內部進行傳遞,並且廣播接收器也只能接收來自本應用程式發出的廣播。本地廣播的用法並不複雜,主要使用了一個 LocalBroadcastManager 來對廣播進行管理,並提供了傳送廣播和註冊廣播接收器的方法。

package com.dak.administrator.firstcode.broadcast_recevier;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

import com.dak.administrator.firstcode.R;

public class LoaclActivity extends AppCompatActivity {


    private IntentFilter intentFilter;
    private LocalBroadcastManager receiverManager;
    private LocalReceiver receiver;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_loacl);

        intentFilter = new IntentFilter();
        intentFilter.addAction("com.Local_Borad");

        receiver = new LocalReceiver();
        receiverManager = LocalBroadcastManager.getInstance(this);
        receiverManager.registerReceiver(receiver, intentFilter);

    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        receiverManager.unregisterReceiver(receiver);

    }

    class LocalReceiver extends BroadcastReceiver{

        @Override
        public void onReceive(Context context, Intent intent) {

        }
    }

}

5.5 廣播最佳實踐——實現強制下線功能

又到了實戰環節了。此次的需求是:強制下線後在介面上彈出一個對話方塊,讓使用者無法進行任何其他操作,必須要點選對話方塊中的確定按鈕, 然後回到登入介面。

藉助本次所學的廣播知識,可以輕鬆實現這一功能。

首先,建立一個 ActivityCollector 類用於管理所有的活動:

public class ActivityCollector {
    // 通過一個List來快取活動
    public static List<Activity> activities = new ArrayList<Activity>();

    /**
     * 用於向List中新增一個活動
     * @param activity
     */
    public static void addActivity(Activity activity) {
        activities.add(activity);
    }

    /**
     * 用於從List中移除活動
     * @param activity
     */
    public static void removeActivity(Activity activity) {
        activities.remove(activity);
    }

    /**
     * 將List中儲存的活動全部銷燬掉
     */
    public static void finishAll() {
        for (Activity activity : activities) {
            if (!activity.isFinishing()) {
                activity.finish();
            }
        }
    }
}

然後建立 BaseActivity 類作為所有活動的父類(忽略一些不必要的內容,主要看ActivityCollector 相關的):

public abstract class BaseActivity extends AppCompatActivity {

    protected Context mContext;
    protected Unbinder mBinder;

    /**
     * 初始化佈局id
     * @return 佈局id
     */
    protected abstract int initLayoutId();

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(initLayoutId());

        Log.d("BaseActivity", getClass().getSimpleName());// 知曉當前是在哪一個活動
        ActivityCollector.addActivity(this);// 將當前正在建立的活動新增到活動管理期裡

        mContext = this;
        mBinder = ButterKnife.bind(this);

    }


    @Override
    protected void onDestroy() {
        // 取消繫結
        mBinder.unbind();
        super.onDestroy();
        // 將一個馬上要銷燬的活動從管理器裡移除
        ActivityCollector.removeActivity(this);
    }
}

接下來建立一個登入介面,新建 LoginActivity,編輯 activity_login.xml 如下:

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

    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="60dp">
        <TextView
            android:layout_width="90dp"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:gravity="center"
            android:textSize="18sp"
            android:text="賬號:"/>

        <EditText
            android:id="@+id/et_account"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:layout_gravity="center_vertical"/>
    </LinearLayout>

    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="60dp">
        <TextView
            android:layout_width="90dp"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:gravity="center"
            android:textSize="18sp"
            android:text="密碼:"/>

        <EditText
            android:id="@+id/et_password"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:layout_gravity="center_vertical"/>
    </LinearLayout>

    <Button
        android:id="@+id/btn_login"
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:layout_margin="10dp"
        android:text="登入"/>

</LinearLayout>

接著修改 LoginActivity 中的程式碼:

public class LoginActivity extends BaseActivity {

    private EditText et_account, et_password;
    private Button btn_login;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        et_account = (EditText) findViewById(R.id.et_account);
        et_password = (EditText) findViewById(R.id.et_password);
        btn_login = (Button) findViewById(R.id.btn_login);

        btn_login.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                String account = et_account.getText().toString();
                String password = et_password.getText().toString();
                // 若賬號是 wonderful 且密碼是 123456,就認為登入成功
                if (account.equals("wonderful") && password.equals("123456")){
                    // 登入成功跳轉到主介面
                    IntentUtils.myIntent(LoginActivity.this,ForceOfflineActivity.class);
                    finish();
                }else {
                    ToastUtils.showShort("賬號或密碼無效!");
                }
            }
        });

    }

    @Override
    protected int initLayoutId() {
        return R.layout.activity_login;
    }
}

登入成功後跳轉到 ForceOfflineActivity 主介面,在主介面加入一個強制下線的功能即可,其佈局 activity_force_offline.xml 程式碼為:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="10dp">

    <Button
        android:id="@+id/btn_force_offline"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="傳送強制下線廣播"/>
    
</RelativeLayout>

只有一個按鈕,用於觸發強制下線功能,然後修改 ForceOfflineActivity 中的程式碼:

public class ForceOfflineActivity extends BaseActivity {
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        Button btn_force_offline = (Button) findViewById(R.id.btn_force_offline);
        btn_force_offline.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent("com.wonderful.myfirstcode.FORCE_OFFLINE");
                // 傳送強制下線廣播
                sendBroadcast(intent);
            }
        });
    }

    @Override
    protected int initLayoutId() {
        return R.layout.activity_force_offline;
    }
}

上述程式碼,在按鈕的點選事件裡面傳送了一條廣播,廣播的值為 com.wonderful.myfirstcode.FORCE_OFFLINE,這條廣播就是用於通知程式強制使用者下線的。也就是說強制使用者下線的邏輯並不是寫在 ForceOfflineActivity 裡的,而是寫在接收這條廣播的廣播接收器裡面,這樣強制下線的功能就不會依附於任何的介面,不管是在程式的任何地方,只需要發出這樣一條廣播,就可以完成強制下線的操作了。

毫無疑問,要在 BaseActivity 中動態註冊一個廣播接收器,因為所有的活動都繼承 BaseActivity 的,修改 BaseActivity 中的程式碼,如下:

public abstract class BaseActivity extends AppCompatActivity {

    protected Context mContext;
    protected Unbinder mBinder;

    private ForceOfflineReceiver receiver;

    /**
     * 初始化佈局id
     * @return 佈局id
     */
    protected abstract int initLayoutId();

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(initLayoutId());

        Log.d("BaseActivity", getClass().getSimpleName());// 知曉當前是在哪一個活動
        ActivityCollector.addActivity(this);// 將當前正在建立的活動新增到活動管理期裡

        mContext = this;
        mBinder = ButterKnife.bind(this);

    }

    @Override
    protected void onResume() {
        super.onResume();
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction("com.wonderful.myfirstcode.FORCE_OFFLINE");
        receiver = new ForceOfflineReceiver();
        registerReceiver(receiver,intentFilter);
    }

    @Override
    protected void onPause() {
        super.onPause();
        if (receiver != null){
            unregisterReceiver(receiver);
            receiver = null;
        }
    }

    @Override
    protected void onDestroy() {
        // 取消繫結
        mBinder.unbind();
        super.onDestroy();
        // 將一個馬上要銷燬的活動從管理器裡移除
        ActivityCollector.removeActivity(this);
    }

    class ForceOfflineReceiver extends BroadcastReceiver{

        @Override
        public void onReceive(final Context context, Intent intent) {
            AlertDialog.Builder builder = new AlertDialog.Builder(context);
            builder.setTitle("警告");
            builder.setMessage("你已被下線,請重新登入");
            // 設定不可取消
            builder.setCancelable(false);
            // 設定點選事件
            builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialogInterface, int i) {
                    // 銷燬所有活動
                    ActivityCollector.finishAll();
                    // 重新啟動 LoginActivity
                    IntentUtils.myIntent(context, LoginActivity.class);
                }
            });
            builder.show();
        }
    }
}

上述程式碼中,重寫了 onResume() 和 onPause() 這兩個生命週期的函式,然後分別在這兩個方法裡註冊和取消註冊了 ForceOfflineReceiver,之所以這樣寫而不是在 onCreate() 和 onDestroy() 方法裡註冊和取消註冊是因為我們始終需要保證只有處於棧頂的活動才能接收到下線廣播,非棧頂活動不必要接收這條廣播,寫在 onResume() 和 onPause() 方法裡很好的解決了這個問題,當活動失去棧頂位置時就會自動取消廣播接收器的註冊。

好了,現在去嘗試一下程式碼吧。

5.6 Git時間——初始版本控制工具

5.6.1 安裝Git

5.6.2 建立程式碼倉庫

5.6.3 提交原生代碼

關於Git 相關的內容請移駕:

https://blog.csdn.net/lhk147852369/article/details/84307580

5.7 小結與點評

郭霖總結:

本章中我們主要是對Android的廣播機制進行了深人的研究,不僅瞭解了廣播的理論知識,還掌握了接收廣播、傳送自定義廣播以及本地廣播的使用方法。廣播接收器屬於Android 四大元件之一,在不知不覺中你已經掌握了四大元件中的兩個了。

在最佳實踐環節中你一定也收穫了不少,不僅運用到了本章所學的廣播知識,還將前面章節所學到的技巧綜合運用到了一起。經過這個例子之後,相信你對所涉及的每個知識點都有了更深一層的認識。 另外,本章還添加了一個最最特殊的環節,即Git 時間。在這個環節中,我們對Git這個版本控制工具進行了初步的學習,後面還會學習關於它的更多內容。

下一章我們本應該繼續學習Android四大元件中的內容提供器,不過由於學習內容提供器之前需要先掌握Android中的持久化技術,因此下一章我們就先對這一主題展開討論。

我的總結:

不是我說,你若是四大元件 不好好學的話,趁早轉行吧。。。