1. 程式人生 > >android6.0 wifi連線

android6.0 wifi連線

Android6.0Wifi連線過程基本和之前的版本一致,但是,在獲取附近熱點的時候,卻出現了一些差別,這差別主要包括獲取許可權的方式發生了改變,以及getScanResults這個函式有點怪異的行為...

1.android6.0之前的版本獲取附近的wifi熱點

1.1開啟和關閉wifi

setWifiEnabled(true/false);

1.2掃描附近熱點

startScan();之後,接受WifiManager.SCAN_RESULTS_AVAILABLE_ACTION的廣播會觸發,在這個廣播中呼叫getScanResults()方法可以獲得一個List<ScanResult>,它裡面的每一個條目就是一個可連線的熱點。

1.3程式碼

<span style="font-family:SimSun;font-size:14px;background-color: rgb(255, 255, 255);">public class MainActivity extends AppCompatActivity {
    WifiManager wifi;
    int size = 0;
    List<ScanResult> results;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        wifi = (WifiManager) getSystemService(Context.WIFI_SERVICE);
        if (wifi.isWifiEnabled() == false)
        {
            Toast.makeText(getApplicationContext(), "wifi is disabled..making it enabled", Toast.LENGTH_LONG).show();
            wifi.setWifiEnabled(true);
            Log.d("hello","wifi enabled");
        }
        wifi.startScan();
        registerReceiver(new BroadcastReceiver()
        {
            @Override
            public void onReceive(Context c, Intent intent)
            {
                results = wifi.getScanResults();
                size = results.size();
                for(int i=0;i<size;i++){
                    Log.d("hello",results.get(i).toString());
                }
            }
        }, new IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION));
    }
}</span>
這樣就可以打印出所有的可連線wifi資訊。

總結來說第一步:setWifiEnabled(true);第二步:startScan();

2.android6.0獲取wifi熱點

然而同樣的問題在android6.0中確實不可以的。原因有兩個:

第一:沒有許可權

android6.0訪問wifi新增了兩個許可權:

<span style="font-family:SimSun;font-size:14px;background-color: rgb(255, 255, 255);"><uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /></span>
android6.0之前,這兩個許可權在AndroidMenifest檔案中宣告就可以了,但是android6.0中,又增加了執行時許可權。執行是許可權這裡不多說,總之,這兩個許可權是要執行時獲取的,在Menifest檔案中宣告是行不通的。當然,為了不那麼麻煩,你可以把targetSdkVersion 改為23以下,這樣就不存在執行時許可權的問題了。如果你不想這麼做,那麼不妨試試android6.0的執行時許可權:

首先,檢查有沒有該許可權:

<span style="font-family:SimSun;font-size:14px;background-color: rgb(255, 255, 255);">    final int REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS = 111;
    private boolean checkPermission() {
        Log.d("hello","checkPermission");
        List<String> permissionsList = new ArrayList<String>();
        String[] permission = new String[2];
        if (checkSelfPermission( Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            permission[0] = Manifest.permission.ACCESS_FINE_LOCATION;
        }
        if (checkSelfPermission( Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            permission[1] = Manifest.permission.ACCESS_COARSE_LOCATION;
        }
        if(permission[0] != null || permission[1] != null){
            Log.d("hello","regist Permission");
            requestPermissions(permission,REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS);
            return false;
        }
        return true;
    }</span>
這個方法中使用checkSelfPermisson方法檢查有沒有對應許可權,最後使用requestPermissions方法請求執行時許可權。這會導致介面彈出一個詢問框,問你要不要允許什麼什麼許可權。最終請求對導致一個回撥方法被呼叫:
<span style="font-family:SimSun;font-size:14px;background-color: rgb(255, 255, 255);">   @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions,
                                           int[] grantResults) {
        Log.d("hello","onRequestPermissionsResult");
        switch (requestCode) {
            case REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS:
                if (permissions.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED ||
                        (permissions.length == 2 && grantResults[0] == PackageManager.PERMISSION_GRANTED &&
                                grantResults[1] == PackageManager.PERMISSION_GRANTED)){
                    wifi.startScan();
                    Log.d("hello","permission allow");
                    Toast.makeText(this, "permission allow", Toast.LENGTH_LONG).show();
                    //list is still empty
                }
                else {
                    // Permission Denied
                    Toast.makeText(this, "permission deny", Toast.LENGTH_LONG).show();
                    Log.d("hello","permission deny");
                }
                break;
        }
    }</span>
在這個方法中你可以知道你是不是獲得了對應的許可權。這兩個文法之間有個紐帶就是REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS,你在請求許可權的時候傳入,在回撥函式中通過它判斷自己的請求有沒有成功。

一旦請求成功,那麼你已經成功了第一步。


第二:GPS沒有開啟

(注意:如果你打開了的話就沒有這個問題,這是android6.0很奇怪的一點) 

如果通過第一步,你獲得了許可權,可以是還是無法獲取到附近的wifi熱點,那麼你不妨開啟GPS試試。是的,就是這麼神奇,開啟後你就可以獲得附近wifi熱點了。那麼,這到底是怎麼回事呢?不妨看看getScanResults方法到底做了什麼。

getScanResults是WifiManager中的一個方法,然後通過遠端系統呼叫,呼叫到了WifiServiceImpl.java中的getScanResults方法,這個方法如下:

<span style="font-family:SimSun;font-size:14px;background-color: rgb(255, 255, 255);">    public List<ScanResult> getScanResults(String callingPackage) {
        enforceAccessPermission();
        int userId = UserHandle.getCallingUserId();
        int uid = Binder.getCallingUid();
        boolean canReadPeerMacAddresses = checkPeersMacAddress();
        boolean isActiveNetworkScorer =
                NetworkScorerAppManager.isCallerActiveScorer(mContext, uid);
        boolean hasInteractUsersFull = checkInteractAcrossUsersFull();
        long ident = Binder.clearCallingIdentity();
        try {
            if (!canReadPeerMacAddresses && !isActiveNetworkScorer
                    && !isLocationEnabled() ) {
                return new ArrayList<ScanResult>();
            }
            if (!canReadPeerMacAddresses && !isActiveNetworkScorer
                    && !checkCallerCanAccessScanResults(callingPackage, uid)) {
                return new ArrayList<ScanResult>();
            }
            if (mAppOps.noteOp(AppOpsManager.OP_WIFI_SCAN, uid, callingPackage)
                    != AppOpsManager.MODE_ALLOWED) {
                return new ArrayList<ScanResult>();
            }
            if (!isCurrentProfile(userId) && !hasInteractUsersFull) {
                return new ArrayList<ScanResult>();
            }
            return mWifiStateMachine.syncGetScanResultsList();
        } finally {
            Binder.restoreCallingIdentity(ident);
        }
    }</span>

這個方法的try語句塊中,首先第一個if中,它居然會判斷:
<span style="font-family:SimSun;font-size:14px;background-color: rgb(255, 255, 255);">isLocationEnabled() </span>
如果它是false,!isLocationEnabled 就為true,再加上前面兩個對位true了,那麼if裡面的就會執行,然後就會返回一個空的List.

第二個if中則會判斷許可權:

<span style="font-family:SimSun;font-size:14px;background-color: rgb(255, 255, 255);">        if (ActivityManager.checkUidPermission(Manifest.permission.ACCESS_FINE_LOCATION, uid)
                == PackageManager.PERMISSION_GRANTED
                && isAppOppAllowed(AppOpsManager.OP_FINE_LOCATION, callingPackage, uid)) {
            return true;
        }

        if (ActivityManager.checkUidPermission(Manifest.permission.ACCESS_COARSE_LOCATION, uid)
                == PackageManager.PERMISSION_GRANTED
                && isAppOppAllowed(AppOpsManager.OP_COARSE_LOCATION, callingPackage, uid)) {
            return true;
        }
</span>
可以看到他就是判斷我們之前說的那兩個許可權,如果不想這麼麻煩,統統把它們幹掉也是可以的。這可能不是好主意,不過把對Location有沒有使能的判斷幹掉是合理的,誰會在獲取wifi熱點資訊的時候關注GPS有沒有開啟呢?google的這點設計真的很奇怪。

3.按照訊號強弱排序

<span style="font-family:SimSun;font-size:14px;background-color: rgb(255, 255, 255);">    public List<ScanResult> sortSSIDBySignalLevel(List<ScanResult> resultList){
        List<ScanResult> wifiScanResults = new ArrayList<ScanResult>();
        int length = resultList.size();
        for (int i = 0; i < length; i++) {
            for (int j = 0; j < length - i - 1; j++) {
                boolean is2Swaped = false;
                int lvl1 = resultList.get(j).level;
                lvl1 = WifiManager
                        .calculateSignalLevel(lvl1, 7);
                int lvl2 = resultList.get(j + 1).level;
                lvl2 = WifiManager
                        .calculateSignalLevel(lvl2, 7);
                if (lvl1 < lvl2) {
                    is2Swaped = true;
                } else if (lvl1 == lvl2) {

                    String str1 = resultList.get(j).SSID;
                    String str2 = resultList.get(j + 1).SSID;
                    if (str1 != null && str2 != null
                            && str1.compareToIgnoreCase(str2) > 0) {
                        is2Swaped = true;
                    } else if (str1 != null && str2 == null) {
                        is2Swaped = true;
                    }
                }
                if (is2Swaped) {
                    ScanResult temp = resultList.get(j);
                    resultList.set(j, resultList.get(j + 1));
                    resultList.set(j + 1, temp);
                }
            }
        }
        // key
        wifiScanResults = resultList;
        return wifiScanResults;
    }</span>

4.連線wifi

wifi的連線主要使用WifiManager.addNetwork(WifiConfiguration config)方法。

因此,主要工作就是配置一個WifiConfiguration。主要配置項有:

4.1配置

1.configration.SSID

2.安全型別

2.1SECURITY_NONE,沒有密碼

<span style="font-family:SimSun;font-size:14px;background-color: rgb(255, 255, 255);">config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);</span>
2.2SECURITY_WEP
配置密碼:

config.wepKeys[0] = passwd;

配置安全型別之類的資訊。

<span style="font-family:SimSun;font-size:14px;background-color: rgb(255, 255, 255);">config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
config.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN);
config.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.SHARED);</span>
2.3SECURITY_PSK

配置密碼:

<span style="font-family:SimSun;font-size:14px;background-color: rgb(255, 255, 255);">config.preSharedKey = passwd;</span>
配置安全型別:
<span style="font-family:SimSun;font-size:14px;background-color: rgb(255, 255, 255);">config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);</span>

通過以上配置,即可呼叫WifiManager.addNetwork連線wifi。

4.2完整配置的示例

<span style="font-family:SimSun;font-size:14px;background-color: rgb(255, 255, 255);">    private WifiConfiguration getNetConfig(){

        WifiConfiguration configration = new WifiConfiguration();

        String passwd = mPwdInput.getText().toString();
        
configration .SSID = "\"" + SSID + "\"";
switch(getSecurity(selScanResult.capabilities)){ case SECURITY_NONE: configration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE); break; case SECURITY_WEP: if(passwd.equals("")){ return null; } if (passwd.length() != 0) { int length = passwd.length(); if ((length == 10 || length == 26 || length == 58) && passwd.matches("[0-9A-Fa-f]*")) { configration.wepKeys[0] = passwd; } else { configration.wepKeys[0] = "\"" + passwd + "\""; } } else{ return null; }configration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE); configration.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN); configration.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.SHARED); break; case SECURITY_PSK: if(passwd.equals("")) { return null; } if (passwd.length() != 0) { if (passwd.matches("[0-9A-Fa-f]{64}")) { configration.preSharedKey = passwd; } else { configration.preSharedKey = "\"" + passwd + "\""; } }else{ return null; } configration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK); break; default: break; }</span>新增示例程式碼如下:
 int netId = wifiManager.addNetwork(wifiConfiguration);
                    if(netId>=0){
                        listViewConnectStatus("已儲存");
                    }else {
                        Toast.makeText(WifiSettingsActivity.this,"儲存失敗",Toast.LENGTH_SHORT).show();
                        listViewConnectStatus("未儲存");
                    }
      
                    if(!wifiManager.enableNetwork(netId,true)){
                        Log.d("test","enable network: "+netId+"  failed");
                    }


這個,整個wifi的連線過程就做完了。