Android 開啓WiFi 熱點的一些適配方案

前言

博主又來更新文章了,有點墨跡哈,好久纔來一篇文章,不講究文章量的大小,只在意內容的實用性,幫助每個開發者,避過一些沒必要要的坑,廢話很少說了,文章的內容就是說各類版本手機經過代碼如何開啓熱點,文章也比較簡潔,不會有太多的囉嗦話java

不一樣版本開啓熱點的方式

首先呢,經過Android 對應Api的源碼(哈哈哈上來就源碼,不看源碼怎麼知道開熱點啊)就能夠找到如何開熱點的,在咱們手機的設置頁面確定會有開啓熱點的功能,而後呢 就去找對於版本號的Api, 代碼我就貼出來了,簡潔的代碼 有興趣的同窗能夠去源碼官網查看TetherSettings.java 源碼android

  • 6.0以前的開啓wifi 熱點的方式
public void onClick(DialogInterface dialogInterface, int button) {
       if (button == DialogInterface.BUTTON_POSITIVE) {
           mWifiConfig = mDialog.getConfig();
           if (mWifiConfig != null) {
               /**
                * if soft AP is stopped, bring up
                * else restart with new config
                * TODO: update config on a running access point when framework support is added
                */
               if (mWifiManager.getWifiApState() == WifiManager.WIFI_AP_STATE_ENABLED) {
                   mWifiManager.setWifiApEnabled(null, false);
                   mWifiManager.setWifiApEnabled(mWifiConfig, true);
               } else {
                   mWifiManager.setWifiApConfiguration(mWifiConfig);
               }
               int index = WifiApDialog.getSecurityTypeIndex(mWifiConfig);
               mCreateNetwork.setSummary(String.format(getActivity().getString(CONFIG_SUBTEXT),
                       mWifiConfig.SSID,
                       mSecurityType[index]));
           }
       }
   }


複製代碼

從源碼上能夠看到 開啓熱是經過wifimanager setWifiApEnabled的方式走的shell

  • 8.0以後開啓wifi熱點的方式
public void onClick(DialogInterface dialogInterface, int button) {
       if (button == DialogInterface.BUTTON_POSITIVE) {
           mWifiConfig = mDialog.getConfig();
           if (mWifiConfig != null) {
               /**
                * if soft AP is stopped, bring up
                * else restart with new config
                * TODO: update config on a running access point when framework support is added
                */
               if (mWifiManager.getWifiApState() == WifiManager.WIFI_AP_STATE_ENABLED) {
                   Log.d("TetheringSettings",
                           "Wifi AP config changed while enabled, stop and restart");
                   mRestartWifiApAfterConfigChange = true;
                   mCm.stopTethering(TETHERING_WIFI);
               }
               mWifiManager.setWifiApConfiguration(mWifiConfig);
               int index = WifiApDialog.getSecurityTypeIndex(mWifiConfig);
               mCreateNetwork.setSummary(String.format(getActivity().getString(CONFIG_SUBTEXT),
                       mWifiConfig.SSID,
                       mSecurityType[index]));
           }
       }
   }

複製代碼

從上面的代碼能夠看到變了,變得不認識了,不是經過wifiManager的方式走的了,而是經過更新config 文件而後經過別的方式來開啓熱點的,下面的代碼就是開啓熱點的方式。api

private void startTethering(int choice) {
       if (choice == TETHERING_BLUETOOTH) {
           // Turn on Bluetooth first.
           BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
           if (adapter.getState() == BluetoothAdapter.STATE_OFF) {
               mBluetoothEnableForTether = true;
               adapter.enable();
               mBluetoothTether.setSummary(R.string.bluetooth_turning_on);
               mBluetoothTether.setEnabled(false);
               return;
           }
       }

       mCm.startTethering(choice, true, mStartTetheringCallback, mHandler);
   }

複製代碼

經過上面代碼能夠看到 開啓的方式是走ConnectivityManager方式來開啓熱點的。而不是經過wifimanager了。bash

  • 6.0-8.0(7.0,7.1)的開啓方式 查看setting源碼能夠看到一樣也是經過8.0方式走的,經過ConnectivityManager 進行開啓wifi 熱點的。可是雖然是經過8.0方式走的,咱們卻沒有權限進行開啓。很奇怪下面我會相信講解爲何。 7.0也是能夠經過wifimanager開啓熱點的,可是到了7.1以後有些手機是能夠的,有些手機是能夠開啓熱點可是沒法鏈接。而後看了7.1源碼能夠看到7.1以後開啓源碼的方式也是經過ConnectivityManager方式來進行開啓的。不是經過wifimanager 進行開啓熱點。可是爲何還能夠用wifimanager呢,是由於在7.1這個wifimanager的setWifiApEnabled方式沒有廢棄仍是能夠用的。

不一樣版本咱們如何經過代碼進行開啓熱點。

在說如何開啓熱點的代碼時候,仍是會帶你們看一點點的源碼。而後看看如何在本身項目中用代碼進行開啓。app

  • 上面也介紹了經過哪一個類和哪一個方法進行開啓wifi,可是知道了仍是不行的。
  • 首選咱們來看一下6.0 Wifimanager的方式經過setWifiApEnabled進行開啓熱點方式的源碼
/**
    * Start AccessPoint mode with the specified
    * configuration. If the radio is already running in
    * AP mode, update the new configuration
    * Note that starting in access point mode disables station
    * mode operation
    * @param wifiConfig SSID, security and channel details as
    *        part of WifiConfiguration
    * @return {@code true} if the operation succeeds, {@code false} otherwise
    *
    * @hide Dont open up yet
    */
   public boolean setWifiApEnabled(WifiConfiguration wifiConfig, boolean enabled) {
       try {
           mService.setWifiApEnabled(wifiConfig, enabled);
           return true;
       } catch (RemoteException e) {
           return false;
       }
   }

複製代碼

能夠看到這個Api已經被隱藏了。大兄弟們,大家是無法玩耍的。只能乾瞪眼ide

  • 而後咱們在看一下7.0,7.1 Wifimanager的方式經過setWifiApEnabled開熱點的方式。發現和6.0沒有什麼區別
/**
    * Start AccessPoint mode with the specified
    * configuration. If the radio is already running in
    * AP mode, update the new configuration
    * Note that starting in access point mode disables station
    * mode operation
    * @param wifiConfig SSID, security and channel details as
    *        part of WifiConfiguration
    * @return {@code true} if the operation succeeds, {@code false} otherwise
    *
    * @hide
    */
   @SystemApi
   public boolean setWifiApEnabled(WifiConfiguration wifiConfig, boolean enabled) {
       try {
           mService.setWifiApEnabled(wifiConfig, enabled);
           return true;
       } catch (RemoteException e) {
           throw e.rethrowFromSystemServer();
       }
   }


複製代碼

看到沒有變成了系統級別的Api 不緊隱藏了,還變成了系統級別的Api,並且發現出現exec的時候直接返回exception而不是返回false 直接返回false 這個可有可無工具

  • 而後咱們在看一下8.1的Wifimanager setWifiApEnabled 源碼,發現這個api已經被廢棄了。
/**
    * This call is deprecated and removed.  It is no longer used to
    * start WiFi Tethering.  Please use {@link ConnectivityManager#startTethering(int, boolean,
    * ConnectivityManager#OnStartTetheringCallback)} if
    * the caller has proper permissions.  Callers can also use the LocalOnlyHotspot feature for a
    * hotspot capable of communicating with co-located devices {@link
    * WifiManager#startLocalOnlyHotspot(LocalOnlyHotspotCallback)}.
    *
    * @param wifiConfig SSID, security and channel details as
    *        part of WifiConfiguration
    * @return {@code false}
    *
    * @hide
    * @deprecated This API is nolonger supported.
    * @removed
    */
   @SystemApi
   @Deprecated
   @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED)
   public boolean setWifiApEnabled(WifiConfiguration wifiConfig, boolean enabled) {
       String packageName = mContext.getOpPackageName();

       Log.w(TAG, packageName + " attempted call to setWifiApEnabled: enabled = " + enabled);
       return false;
   }

複製代碼

也就是說這個api啊 在8.1的時候已經被徹底廢棄了。直接給你返回false。只能經過8.0的方式進行開啓wifi 熱點了ui

那麼接下來講的就是手動編寫的代碼了

因爲都是隱藏的api 因此開啓熱點的方式幾乎都是經過反射來進行的google

  • 7.0以前的方式(代碼直接copy)就不一一解釋了。網上代碼不少
public boolean createAp(boolean isOpen) {
        StringBuffer sb = new StringBuffer();
        try {
            mWifiManager = (WifiManager) getApplicationContext().getSystemService(WIFI_SERVICE);
            if (mWifiManager.isWifiEnabled()) {
                mWifiManager.setWifiEnabled(false);
            }
            sb.append(1);
            WifiConfiguration netConfig = new WifiConfiguration();
            netConfig.SSID = "xiaomeng";
            netConfig.preSharedKey = "11111111";
            Log.d("oye", "WifiPresenter:createAp----->netConfig.SSID:"
                    + netConfig.SSID + ",netConfig.preSharedKey:" + netConfig.preSharedKey + ",isOpen=" + isOpen);
            netConfig.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN);
            netConfig.allowedProtocols.set(WifiConfiguration.Protocol.RSN);
            netConfig.allowedProtocols.set(WifiConfiguration.Protocol.WPA);
            sb.append(2);
            if (isOpen) {
                netConfig.allowedKeyManagement.set(4);
                sb.append(3);
            } else {
                netConfig.allowedKeyManagement.set(4);
                sb.append(4);
            }
            netConfig.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP);
            netConfig.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP);
            netConfig.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);
            netConfig.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP);
            sb.append(5);

            Method method = mWifiManager.getClass().getMethod("setWifiApEnabled", WifiConfiguration.class, Boolean.TYPE);
            sb.append(9);
            return (boolean) method.invoke(mWifiManager, netConfig, true);
            

        } catch (NoSuchMethodException e) {
            sb.append(10 + (e.getMessage()));
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            sb.append(11 + (e.getMessage()));
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            sb.append(12 + (e.getMessage()));
            e.printStackTrace();
        } 
        log.setText(sb.toString());

        return false;
    }

複製代碼
  • 8.0的方式有兩種方式開啓熱點

第一種直接調用系統提供的Api 可是不穩定 不靠譜(並且熱點的名稱和密碼都是隨機的)

WifiManager wifiManager = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
            wifiManager.startLocalOnlyHotspot(new WifiManager.LocalOnlyHotspotCallback() {

                @TargetApi(Build.VERSION_CODES.O)
                @Override
                public void onStarted(WifiManager.LocalOnlyHotspotReservation reservation) {
                    super.onStarted(reservation);
                    WifiConfiguration wifiConfiguration = reservation.getWifiConfiguration();
                    ssid = wifiConfiguration.SSID;
                    //能夠經過config拿到開啓熱點的帳號和密碼
                    mHandler.obtainMessage(2018, wifiConfiguration).sendToTarget();
                }

                @Override
                public void onStopped() {
                    super.onStopped();
                }

                @Override
                public void onFailed(int reason) {
                    super.onFailed(reason);
                }

            }, mHandler);

複製代碼

第二種開啓的方式 有些機型會報NoSuchMethodException的異常 是由於有些機型他會多出來一個參數

private void startTethering() {
        mConnectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
        if (mWifiManager != null) {
            int wifiState = mWifiManager.getWifiState();
            boolean isWifiEnabled = ((wifiState == WifiManager.WIFI_STATE_ENABLED) || (wifiState == WifiManager.WIFI_STATE_ENABLING));
            if (isWifiEnabled)
                mWifiManager.setWifiEnabled(false);
        }
        if (mConnectivityManager != null) {
            try {
                Field internalConnectivityManagerField = ConnectivityManager.class.getDeclaredField("mService");
                internalConnectivityManagerField.setAccessible(true);
                WifiConfiguration apConfig = new WifiConfiguration();
                apConfig.SSID = "cuieney";
                apConfig.preSharedKey = "12121212";

                StringBuffer sb = new StringBuffer();
                Class internalConnectivityManagerClass = Class.forName("android.net.IConnectivityManager");
                ResultReceiver dummyResultReceiver = new ResultReceiver(null);
                try {
                    
                    WifiManager wifiManager = (WifiManager) getApplicationContext().getSystemService(WIFI_SERVICE);
                    Method mMethod = wifiManager.getClass().getMethod("setWifiApConfiguration", WifiConfiguration.class);
                    mMethod.invoke(wifiManager, apConfig);
                    Method startTetheringMethod = internalConnectivityManagerClass.getDeclaredMethod("startTethering",
                            int.class,
                            ResultReceiver.class,
                            boolean.class);

                    startTetheringMethod.invoke(internalConnectivityManagerClass,
                            0,
                            dummyResultReceiver,
                            true);
                } catch (NoSuchMethodException e) {
                    Method startTetheringMethod = internalConnectivityManagerClass.getDeclaredMethod("startTethering",
                            int.class,
                            ResultReceiver.class,
                            boolean.class,
                            String.class);

                    startTetheringMethod.invoke(internalConnectivityManagerClass,
                            0,
                            dummyResultReceiver,
                            false,
                            context.getPackageName());
                } catch (InvocationTargetException e) {
                    sb.append(11 + (e.getMessage()));
                    e.printStackTrace();
                } finally {
                    log.setText(sb.toString());
                }


            } catch (Exception e) {
                Log.e("WifiApManager.startTethering", Log.getStackTraceString(e));
            }
        }
    }

複製代碼

經過ConnectivityManager的startTethering方式進行開啓wifi熱點。能夠配置名稱和密碼。是以系統級別的熱點開啓方式。

  • 7.1方式開啓wifi熱點

    首先呢,7.1的方式比較奇葩 有的機型能夠經過Wifimanager的方式進行開啓,也能正常鏈接,可是有的能開啓可是沒法正常鏈接。

    下面有幾種方式能夠成功的開啓7.1熱點同時也能鏈接上網

  1. 第一種方式,經過編譯修改源碼的方式進行,把ConnectivityManager這個包下面的源碼單獨編譯一個jar 而後導入jar包到項目中使用。
  2. 第二種方式,經過8.0的方式走ConnectivityManager 的startTethering方式進行開啓熱點,可是要注意的是7.1的startTethering 參數和8.0的startTethering參數有所不一樣注意修改。可是即便這樣仍是會報一個錯誤(need MANAGE_USERS or CREATE_USERS permission to: query user)沒有權限調用這個Api同時還會出現一個Exec異常InvocationTargetException 就是說你這個app不是系統app須要權限,那咱們就把他變成系統app,如何才能變成呢,下載簽名工具 簽名包能夠在這裏下載 而後經過命令java -jar signapk.jar platform.x509.pem platform.pk8 src.apk dst.apk進行簽名 adb push 到你的手機裏的/system/app/ 目錄下 。別忘了chmod更改 apk權限 同時reboot 最後能夠經過adb shell dumpsys package 包名 查看你的app全部權限。自此 7.1版本的熱點就能夠啓動了。 具體操做能夠給老鐵們一份鏈接
  3. 第三種方式,和你們說一下,之因此開啓了wifi熱點成功,可是卻沒有成功鏈接是由於。DHCP沒有給這個熱點分配IP地址,熱點開啓了,卻沒有激活這個熱點是由於沒有分配IP,這個我在嘗試,有結果了立馬給老鐵們上代碼。

ending

花了個把小時寫的東西,但願給老鐵們帶來的是知識的儲備而不是時間的浪費。不早了不早了下班了,

相關文章
相關標籤/搜索