美文网首页
Android 开启WiFi 热点的一些适配方案

Android 开启WiFi 热点的一些适配方案

作者: cuieney | 来源:发表于2018-11-14 20:51 被阅读0次

    前言

    博主又来更新文章了,有点墨迹哈,很久才来一篇文章,不讲究文章量的大小,只在乎内容的实用性,帮助每一个开发者,避过一些不必要的坑,废话不多说了,文章的内容就是说各种版本手机通过代码如何开启热点,文章也比较简洁,不会有太多的啰嗦话

    不同版本开启热点的方式

    首先呢,通过Android 对应Api的源码(哈哈哈上来就源码,不看源码怎么知道开热点啊)就可以找到如何开热点的,在我们手机的设置页面肯定会有开启热点的功能,然后呢 就去找对于版本号的Api,
    代码我就贴出来了,简洁的代码 有兴趣的同学可以去源码官网查看TetherSettings.java 源码

    • 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的方式走的

    • 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 文件然后通过别的方式来开启热点的,下面的代码就是开启热点的方式。

     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了。

    • 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方式没有废弃还是可以用的。

    不同版本我们如何通过代码进行开启热点。

    在说如何开启热点的代码时候,还是会带大家看一点点的源码。然后看看如何在自己项目中用代码进行开启。

    • 上面也介绍了通过哪个类和哪个方法进行开启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已经被隐藏了。大兄弟们,你们是没法玩耍的。只能干瞪眼

    • 然后我们在看一下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 热点了

    那么接下来说的就是手动编写的代码了

    由于都是隐藏的api 所以开启热点的方式几乎都是通过反射来进行的

    • 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

    花了个把小时写的东西,希望给老铁们带来的是知识的储备而不是时间的浪费。不早了不早了下班了,

    相关文章

      网友评论

          本文标题:Android 开启WiFi 热点的一些适配方案

          本文链接:https://www.haomeiwen.com/subject/zudsfqtx.html