1. 程式人生 > >Wifi原始碼學習(Android5.1)之wifi開關

Wifi原始碼學習(Android5.1)之wifi開關

wifi系列部落格地址:

正文:

老方法,從介面入手:
這裡寫圖片描述

這裡寫圖片描述

現在我們看到的這兩個介面就是android5.1 的wifi 設定介面了,我們就從這兒入手。
我們可以看到這個介面大概分為三個部分
1、開關
2、option items
3、列表

一、開關:
這個開關是一個自定義控制元件,在原始碼中這種重用自定義控制元件特別多,也值得我們去借鑑。
Settings/res/layout/switch_bar.xml

<com.android.settings.widget.ToggleSwitch android:id="@+id/switch_widget"
        android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:background="@null" android:theme="@style/ThemeOverlay.SwitchBar" />

具體的細節,後續部落格再說,我們只看 wifi開關 邏輯部分。
載入這個佈局的地方是 SwitchBar

Settings\src\com\android\settings\widget\SwitchBar.java

這裡寫圖片描述

如上圖所示,自定義控制元件互動事件的書寫一般就是這樣。像button的單擊事件等都是按照這樣的方式實現的。

Settings\src\com\android\settings\wifi\WifiEnabler.java

這個方法就是接收 開關狀態回撥的方法

@Override
public void onSwitchChanged(Switch switchView, boolean isChecked) {
    //Do nothing if called as a result of a state machine event
// 如果是程式碼中 修改開關狀態(也就是人沒有點選),則不走下邊的邏輯
if (mStateMachineEvent) { return; } // Show toast message if Wi-Fi is not allowed in airplane mode // 寫的很清楚,如果在 飛航模式下,wifi 無法開啟 if (isChecked && !WirelessSettings.isRadioAllowed(mContext, Settings.Global.RADIO_WIFI)) { Toast.makeText(mContext, R.string.wifi_in_airplane_mode, Toast.LENGTH_SHORT).show(); // Reset switch to off. No infinite check/listenenr loop. mSwitchBar.setChecked(false); return; } // Disable tethering if enabling Wifi int wifiApState = mWifiManager.getWifiApState(); // 介面顯示為開啟狀態,並且 wifi 實際上是 正在開啟或開啟狀態,則將 wifi 共享關閉 if (isChecked && ((wifiApState == WifiManager.WIFI_AP_STATE_ENABLING) || (wifiApState == WifiManager.WIFI_AP_STATE_ENABLED))) { mWifiManager.setWifiApEnabled(null, false); } // 要開始改變 wifi 的狀態,因為是一個耗時操作,此時暫時將 開關置為不可點選的狀態,這種寫 //法我們在平時寫客戶端的時候也可以借鑑 mSwitchBar.setEnabled(false); //判斷中的這句為真正的修改wifi的狀態,如果失敗,則彈框提示 if (!mWifiManager.setWifiEnabled(isChecked)) { // Error mSwitchBar.setEnabled(true); Toast.makeText(mContext, R.string.wifi_error, Toast.LENGTH_SHORT).show(); } //這個地方需要注意,此時開關無法點選,而設定成功後也沒有在這兒恢復其點選狀態,其實這也是系統常用 //的一種寫法,為了防止使用者不斷點擊出現一些錯誤。我們繼續看 }

恢復點選狀態的方法其實是寫到了 wifi 狀態改變的回撥中,所謂各司其職吧。

private void handleWifiStateChanged(int state) {
    switch (state) {
        case WifiManager.WIFI_STATE_ENABLING:
            mSwitchBar.setEnabled(false);
            break;
        case WifiManager.WIFI_STATE_ENABLED:
            setSwitchBarChecked(true);
            mSwitchBar.setEnabled(true);
            updateSearchIndex(true);
            break;

那系統是怎麼樣知道wifi狀態的變化呢?答案就是廣播

private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) {
            handleWifiStateChanged(intent.getIntExtra(
                    WifiManager.EXTRA_WIFI_STATE, WifiManager.WIFI_STATE_UNKNOWN));
        } else if (WifiManager.SUPPLICANT_STATE_CHANGED_ACTION.equals(action)) {
            if (!mConnected.get()) {
                handleStateChanged(WifiInfo.getDetailedStateOf((SupplicantState)
                        intent.getParcelableExtra(WifiManager.EXTRA_NEW_STATE)));
            }
        } else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) {
            NetworkInfo info = (NetworkInfo) intent.getParcelableExtra(
                    WifiManager.EXTRA_NETWORK_INFO);
            mConnected.set(info.isConnected());
            handleStateChanged(info.getDetailedState());
        }
    }
};

都呼叫了 handleStateChanged() 方法

private void handleWifiStateChanged(int state) {
    switch (state) {
        case WifiManager.WIFI_STATE_ENABLING://正在開啟
            mSwitchBar.setEnabled(false);
            break;
        case WifiManager.WIFI_STATE_ENABLED://已經開啟
            setSwitchBarChecked(true);
            mSwitchBar.setEnabled(true);
            updateSearchIndex(true);
            break;
        case WifiManager.WIFI_STATE_DISABLING://正在關閉
            mSwitchBar.setEnabled(false);
            break;
        case WifiManager.WIFI_STATE_DISABLED://已經關閉
            setSwitchBarChecked(false);
            mSwitchBar.setEnabled(true);
            updateSearchIndex(false);
            break;
        default:
            setSwitchBarChecked(false);
            mSwitchBar.setEnabled(true);
            updateSearchIndex(false);
    }
}

private Handler mHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case EVENT_UPDATE_INDEX:
                final boolean isWiFiOn = msg.getData().getBoolean(EVENT_DATA_IS_WIFI_ON);
                Index.getInstance(mContext).updateFromClassNameResource(
                        WifiSettings.class.getName(), true, isWiFiOn);
                break;
        }
    }
};

發現這兒並沒有進行wifi的搜尋操作什麼的(因為在介面中wifi開關開啟會自動進行搜尋),但是在這兒沒有做搜尋處理也可以理解,各司其職嘛。
再找找看哪兒還有接收wifi狀態廣播的地方,果然,功夫不負有心人

Settings\src\com\android\settings\wifi\WifiSettings.java

public WifiSettings() {
    super(DISALLOW_CONFIG_WIFI);
    mFilter = new IntentFilter();
    mFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
    mFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
    mFilter.addAction(WifiManager.NETWORK_IDS_CHANGED_ACTION);
    mFilter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
    mFilter.addAction(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION);
    mFilter.addAction(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION);
    mFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
    mFilter.addAction(WifiManager.RSSI_CHANGED_ACTION);
// 在這兒我們跳過註冊廣播的程式碼
    mReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            handleEvent(intent);
        }
    };
    mScanner = new Scanner(this);
}

private void handleEvent(Intent intent) {
    String action = intent.getAction();
    //我們暫時只看和wifi開關狀態相關的這個判斷語句(也就是第一個)
    if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) {
        updateWifiState(intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
                WifiManager.WIFI_STATE_UNKNOWN));
    } else if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(action) ||
            WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION.equals(action) ||
            WifiManager.LINK_CONFIGURATION_CHANGED_ACTION.equals(action)) {
            updateAccessPoints();
    } else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) {
        NetworkInfo info = (NetworkInfo) intent.getParcelableExtra(
                WifiManager.EXTRA_NETWORK_INFO);
        mConnected.set(info.isConnected());
        changeNextButtonState(info.isConnected());
        updateAccessPoints();
        updateNetworkInfo(info);
    } else if (WifiManager.RSSI_CHANGED_ACTION.equals(action)) {
        updateNetworkInfo(null);
    }
}

private void updateWifiState(int state) {
    Activity activity = getActivity();
    if (activity != null) {
        activity.invalidateOptionsMenu();
    }
    switch (state) {
        case WifiManager.WIFI_STATE_ENABLED:
            // wifi 被打開了
            mScanner.resume();
            return; // not break, to avoid the call to pause() below

        case WifiManager.WIFI_STATE_ENABLING:
            addMessagePreference(R.string.wifi_starting);
            break;

        case WifiManager.WIFI_STATE_DISABLED:
            setOffMessage();
            break;
    }
    mLastInfo = null;
    mLastNetworkInfo = null;
    mScanner.pause();
}

private static class Scanner extends Handler {
    private int mRetry = 0;
    private WifiSettings mWifiSettings = null;
    Scanner(WifiSettings wifiSettings) {
        mWifiSettings = wifiSettings;
    }
    //我們一路呼叫到了這兒
    //原來是自己呼叫自己,停止搜尋和強制搜尋方法及邏輯都在這兒
    void resume() {
        if (!hasMessages(0)) {
            sendEmptyMessage(0);
        }
    }
    void forceScan() {
        removeMessages(0);
        sendEmptyMessage(0);
    }
    void pause() {
        mRetry = 0;
        removeMessages(0);
    }
    @Override
    public void handleMessage(Message message) {
        // 如果掃描啟動成功則走到最後一句,十秒後繼續掃描
        // 如果開啟掃描失敗,則走 else ,連續三次失敗,就不再嘗試掃描,彈出Toast
        // 這段程式碼應該是開關這一塊相對較難理解的一部分了,但是寫法值得我們去借鑑
        if (mWifiSettings.mWifiManager.startScan()) {
            mRetry = 0;
        } else if (++mRetry >= 3) {
            mRetry = 0;
            Activity activity = mWifiSettings.getActivity();
            if (activity != null) {
                Toast.makeText(activity, R.string.wifi_fail_to_scan, Toast.LENGTH_LONG).show();
            }
            return;
        }
        // WIFI_RESCAN_INTERVAL_MS = 10 * 1000 也就是十秒
        sendEmptyMessageDelayed(0, WIFI_RESCAN_INTERVAL_MS);
    }
}

最後附圖一張,以便於大家理解。

這裡寫圖片描述