Android WiFi热点

Android WiFi热点,第1张

Android WiFi热点

项目中有需要对WiFi热点进行某些 *** 作,所以记录下一些有关WiFi热点的知识点

开启或者关闭热点

网上的大部分例子,都是通过反射,调用WifiManagersetWifiApEnabled方法,来开启或者关闭热点,如:

Android WiFi开发 (二)Wifi热点三、Android开启wifi热点
//热点的配置类
WifiConfiguration apConfig = new WifiConfiguration();
//配置热点的名称(可以在名字后面加点随机数什么的)
apConfig.SSID = "YRCCONNECTION";
//配置热点的密码
apConfig.preSharedKey="12122112";
//通过反射调用设置热点
Method method = mWifiManager.getClass().getMethod("setWifiApEnabled", WifiConfiguration.class, Boolean.TYPE);

但上面的方法,貌似只对安卓7.0或7.0以下版本有效,在我Android 11的手机上,并没有调用成功

通过Android系统源码中的WifiTetherSettings,可以知道,现在启用热点,调用的是ConnectivityManager中的 startTethering方法

    /**
     * Runs tether provisioning for the given type if needed and then starts tethering if
     * the check succeeds. If no carrier provisioning is required for tethering, tethering is
     * enabled immediately. If provisioning fails, tethering will not be enabled. It also
     * schedules tether provisioning re-checks if appropriate.
     *
     * @param type The type of tethering to start. Must be one of
     *         {@link ConnectivityManager.TETHERING_WIFI},
     *         {@link ConnectivityManager.TETHERING_USB}, or
     *         {@link ConnectivityManager.TETHERING_BLUETOOTH}.
     * @param showProvisioningUi a boolean indicating to show the provisioning app UI if there
     *         is one. This should be true the first time this function is called and also any time
     *         the user can see this UI. It gives users information from their carrier about the
     *         check failing and how they can sign up for tethering if possible.
     * @param callback an {@link OnStartTetheringCallback} which will be called to notify the caller
     *         of the result of trying to tether.
     * @param handler {@link Handler} to specify the thread upon which the callback will be invoked.
     *
     * @deprecated Use {@link TetheringManager#startTethering} instead.
     * @hide
     */
    @SystemApi
    @Deprecated
    @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED)
    public void startTethering(int type, boolean showProvisioningUi,
            final OnStartTetheringCallback callback, Handler handler) {
        Objects.requireNonNull(callback, "OnStartTetheringCallback cannot be null.");

        final Executor executor = new Executor() {
            @Override
            public void execute(Runnable command) {
                if (handler == null) {
                    command.run();
                } else {
                    handler.post(command);
                }
            }
        };

        final StartTetheringCallback tetheringCallback = new StartTetheringCallback() {
            @Override
            public void onTetheringStarted() {
                callback.onTetheringStarted();
            }

            @Override
            public void onTetheringFailed(final int error) {
                callback.onTetheringFailed();
            }
        };

        final TetheringRequest request = new TetheringRequest.Builder(type)
                .setShouldShowEntitlementUi(showProvisioningUi).build();

        getTetheringManager().startTethering(request, executor, tetheringCallback);
    }

这个方法也被标注为@Deprecated,表示已废弃
提示我们使用TetheringManager#startTethering

TetheringManager中的startTethering方法

    public void startTethering(@NonNull final TetheringRequest request,
            @NonNull final Executor executor, @NonNull final StartTetheringCallback callback) {
        final String callerPkg = mContext.getOpPackageName();
        Log.i(TAG, "startTethering caller:" + callerPkg);

        final IIntResultListener listener = new IIntResultListener.Stub() {
            @Override
            public void onResult(final int resultCode) {
                executor.execute(() -> {
                    if (resultCode == TETHER_ERROR_NO_ERROR) {
                        callback.onTetheringStarted();
                    } else {
                        callback.onTetheringFailed(resultCode);
                    }
                });
            }
        };
        getConnector(c -> c.startTethering(request.getParcel(), callerPkg,
                getAttributionTag(), listener));
    }

所以参考How do I enable/disable hotspot or tethering mode programmatically on Android?

如果Android Version >= 11,则使用TetheringManager#startTethering
如果Android Version < 11,则使用ConnectivityManager#startTethering

最终,对最新的Android的版本,在网络上最后找到了如下的几个解决方案:

android开发 控制wifi和热点的开启(适配到 9.1)How to turn on/off wifi hotspot programmatically in Android 8.0 (Oreo)

如果直接运行上面的代码,可能会报错,如

java.lang.IllegalArgumentException: dexcache == null (and no default could be found; consider setting the ‘dexmaker.dexcache’ system property)

可能的解决办法是:

1.修改依赖的dexmaker-mockito版本为2.2.0
2.ProxyBuilder要调用dexCache()方法,不要问我为什么

使用的时候需要注意:
1.需要WRITE_SETTINGS权限,在手机权限里面设置下,打开这个权限

2.是否真的启用了”个人热点“,需要去设置中查看,这个开关是否打开

粗略代码如下,2个方法,大同小异

    /** * android8.0以上开启手机热点 */
    private  void startTethering() {
        ConnectivityManager connectivityManager = (ConnectivityManager) getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE);
        try {
            File outputDir = getCodeCacheDir();
            Class classOnStartTetheringCallback = Class.forName("android.net.ConnectivityManager$OnStartTetheringCallback");
            Method startTethering = connectivityManager.getClass().getDeclaredMethod("startTethering", int.class, boolean.class, classOnStartTetheringCallback);
            Object proxy = ProxyBuilder.forClass(classOnStartTetheringCallback).dexCache(outputDir).handler(new InvocationHandler() {
                @Override
                public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
                    return null;
                }
            }).build();
            startTethering.invoke(connectivityManager, 0, false, proxy);
        } catch (Exception e) {
            Log.e(TAG,"打开热点失败");
            e.printStackTrace();
        }
    }
    public boolean enableTetheringNew() {
        File outputDir = getCodeCacheDir();
        Object proxy = new Object();
        try {
            proxy = ProxyBuilder.forClass(classOnStartTetheringCallback())
                    .dexCache(outputDir).handler(new InvocationHandler() {
                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                            switch (method.getName()) {
                                case "onTetheringStarted":
                                    Log.d(TAG, "onTetheringStarted");
                                    break;
                                case "onTetheringFailed":
                                    Log.d(TAG, "onTetheringFailed");
                                    break;
                                default:
                                    ProxyBuilder.callSuper(proxy, method, args);
                            }
                            return null;
                        }

                    }).build();
        } catch (Exception e) {
            e.printStackTrace();
        }
        ConnectivityManager manager = (ConnectivityManager)getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE);

        Method method = null;
        try {
            method = manager.getClass().getDeclaredMethod("startTethering", int.class, boolean.class, classOnStartTetheringCallback(), Handler.class);
            if (method == null) {
                Log.e(TAG, "startTetheringMethod is null");
            } else {
                method.invoke(manager, 0, false, proxy, null);
            }
            return true;
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        return false;
    }

    private Class classOnStartTetheringCallback() {
        try {
            return Class.forName("android.net.ConnectivityManager$OnStartTetheringCallback");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }

在本人oppo Android11的手机上,测试是有效果的

关闭WiFi热点

    /**
     * android8.0以上关闭手机热点
     */
    private void stopTethering() {
        ConnectivityManager connectivityManager = (ConnectivityManager) getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE);
        try {
            Method stopTethering = connectivityManager.getClass().getDeclaredMethod("stopTethering", int.class);
            stopTethering.invoke(connectivityManager,0);
        } catch (Exception e) {
            Log.e(TAG,"关闭热点失败");
            e.printStackTrace();
        }
    }

在本人手机上测试也是有效果的

参考文档:

Android WiFi开发 (三)Wifi热点8.0适配 WiFi热点状态

获取WiFi热点的状态需要使用WifiManager
如,获取WiFi热点的状态:

    /**
     * Gets the tethered Wi-Fi hotspot enabled state.
     * @return One of {@link #WIFI_AP_STATE_DISABLED},
     *         {@link #WIFI_AP_STATE_DISABLING}, {@link #WIFI_AP_STATE_ENABLED},
     *         {@link #WIFI_AP_STATE_ENABLING}, {@link #WIFI_AP_STATE_FAILED}
     * @see #isWifiApEnabled()
     *
     * @hide
     */
    @SystemApi
    @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE)
    public int getWifiApState() {
        try {
            return mService.getWifiApEnabledState();
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

getWifiApState()也是系统的API,返回值有5种状态:

WIFI_AP_STATE_DISABLING - 值为10WIFI_AP_STATE_DISABLED - 值为11WIFI_AP_STATE_ENABLING- 值为12WIFI_AP_STATE_ENABLED- 值为13WIFI_AP_STATE_FAILED- 值为14

通过isWifiApEnabled()方法,获取热点是否开启

    /**
     * Return whether tethered Wi-Fi AP is enabled or disabled.
     * @return {@code true} if tethered  Wi-Fi AP is enabled
     * @see #getWifiApState()
     *
     * @hide
     */
    @SystemApi
    @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE)
    public boolean isWifiApEnabled() {
        return getWifiApState() == WIFI_AP_STATE_ENABLED;
    }

获取WiFi热点配置

上面参考的例子中有获取热点名称的方法,我测试后发现会发生异常:

java.lang.SecurityException: App not allowed to read or update stored WiFi Ap config (uid = 10488)

网上说法是需要权限

<permission android:name="android.permission.OVERRIDE_WIFI_CONFIG"
       android:protectionLevel="signature|privileged" />

另外在MyOreoWifiManager中有配置热点的代码,说需要android.permission.TETHER_PRIVILEGED权限:

    /**
     * This sets the Wifi SSID and password
     * Call this before {@code startTethering} if app is a system/privileged app
     * Requires: android.permission.TETHER_PRIVILEGED which is only granted to system apps
     */
    public void configureHotspot(String name, String password) {
        WifiConfiguration apConfig = new WifiConfiguration();
        apConfig.SSID = name;
        apConfig.preSharedKey = password;
        apConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
        try {
            Method setConfigMethod = mWifiManager.getClass().getMethod("setWifiApConfiguration", WifiConfiguration.class);
            boolean status = (boolean) setConfigMethod.invoke(mWifiManager, apConfig);
            Log.d(TAG, "setWifiApConfiguration - success? " + status);
        } catch (Exception e) {
            Log.e(TAG, "Error in configureHotspot");
            e.printStackTrace();
        }
    }

本人也没有开发系统app的权限,所以有提示如下的异常:

WifiManager中有关热点配置的方法

public WifiConfiguration getWifiApConfiguration()

获取Wi-Fi AP的配置,注意此API已废弃,建议使用getSoftApConfiguration()

public boolean setWifiApConfiguration(WifiConfiguration wifiConfig)

设置Wi-Fi AP的配置,注意此API已废弃,建议使用setSoftApConfiguration(SoftApConfiguration)

public SoftApConfiguration getSoftApConfiguration()

Gets the Wi-Fi tethered AP Configuration.

    /**
     * Sets the tethered Wi-Fi AP Configuration.
     *
     * If the API is called while the tethered soft AP is enabled, the configuration will apply to
     * the current soft AP if the new configuration only includes
     * {@link SoftApConfiguration.Builder#setMaxNumberOfClients(int)}
     * or {@link SoftApConfiguration.Builder#setShutdownTimeoutMillis(long)}
     * or {@link SoftApConfiguration.Builder#setClientControlByUserEnabled(boolean)}
     * or {@link SoftApConfiguration.Builder#setBlockedClientList(List)}
     * or {@link SoftApConfiguration.Builder#setAllowedClientList(List)}
     *
     * Otherwise, the configuration changes will be applied when the Soft AP is next started
     * (the framework will not stop/start the AP).
     *
     * @param softApConfig  A valid SoftApConfiguration specifying the configuration of the SAP.
     * @return {@code true} if the operation succeeded, {@code false} otherwise
     *
     * @hide
     */
    @SystemApi
    @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
    public boolean setSoftApConfiguration(@NonNull SoftApConfiguration softApConfig) {
        try {
            return mService.setSoftApConfiguration(
                    softApConfig, mContext.getOpPackageName());
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

Sets the tethered Wi-Fi AP Configuration

连接到热点的设备信息

网上有通过访问系统文件/proc/net/arp的方式,来获取连接到热点的设备的ip信息,
可参考:

Android 开启个人热点时 获取连接人数以及连接上的设备信息Android 10版本获取已连接本机热点的ip

在Android 11尝试后,有如下的提示,看来是不再有效了:

java.io.FileNotFoundException: /proc/net/arp: open failed: EACCES (Permission denied)

在java android Q read ip from hotspot /proc/net/arp: open failed: EACCES (Permission denied)处有给出新的方法,尝试之后也行不通

这些命令可以在已root的设备上,或者系统app,可以正常获取
例如,在root的设备上,使用adb shell ip neigh show,效果如下:

使用adb shell cat /proc/net/arp,输出结果如下:

不过,在新版本中,Android又提供了一个SoftApCallback接口,通过WifiManager#registerSoftApCallback(Executor, SoftApCallback)设置,参考:

Android实时获取热点已连接数(谷歌现成API可用,适用于第三方APP和系统APP)
    /**
     * Base class for soft AP callback. Should be extended by applications and set when calling
     * {@link WifiManager#registerSoftApCallback(Executor, SoftApCallback)}.
     *
     * @hide
     */
    @SystemApi
    public interface SoftApCallback {
        /**
         * Called when soft AP state changes.
         *
         * @param state         the new AP state. One of {@link #WIFI_AP_STATE_DISABLED},
         *                      {@link #WIFI_AP_STATE_DISABLING}, {@link #WIFI_AP_STATE_ENABLED},
         *                      {@link #WIFI_AP_STATE_ENABLING}, {@link #WIFI_AP_STATE_FAILED}
         * @param failureReason reason when in failed state. One of
         *                      {@link #SAP_START_FAILURE_GENERAL},
         *                      {@link #SAP_START_FAILURE_NO_CHANNEL},
         *                      {@link #SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION}
         */
        default void onStateChanged(@WifiApState int state, @SapStartFailure int failureReason) {}

        /**
         * Called when the connected clients to soft AP changes.
         *
         * @param clients the currently connected clients
         */
        default void onConnectedClientsChanged(@NonNull List<WifiClient> clients) {}

	

registerSoftApCallback方法说明:

   /**
     * Registers a callback for Soft AP. See {@link SoftApCallback}. Caller will receive the
     * following callbacks on registration:
     * 
     *  {@link SoftApCallback#onStateChanged(int, int)}
     *  {@link SoftApCallback#onConnectedClientsChanged(List)}
     *  {@link SoftApCallback#onInfoChanged(SoftApInfo)}
     *  {@link SoftApCallback#onCapabilityChanged(SoftApCapability)}
     * 
     * These will be dispatched on registration to provide the caller with the current state
     * (and are not an indication of any current change). Note that receiving an immediate
     * WIFI_AP_STATE_FAILED value for soft AP state indicates that the latest attempt to start
     * soft AP has failed. Caller can unregister a previously registered callback using
     * {@link #unregisterSoftApCallback}
     * 

* Applications should have the * {@link android.Manifest.permission#NETWORK_SETTINGS NETWORK_SETTINGS} permission. Callers * without the permission will trigger a {@link java.lang.SecurityException}. *

* * @param executor The Executor on whose thread to execute the callbacks of the {@code callback} * object. * @param callback Callback for soft AP events * @hide */ @SystemApi @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void registerSoftApCallback(@NonNull @CallbackExecutor Executor executor, @NonNull SoftApCallback callback) { if (executor == null) throw new IllegalArgumentException("executor cannot be null"); if (callback == null) throw new IllegalArgumentException("callback cannot be null"); Log.v(TAG, "registerSoftApCallback: callback=" + callback + ", executor=" + executor); Binder binder = new Binder(); try { mService.registerSoftApCallback( binder, new SoftApCallbackProxy(executor, callback), callback.hashCode()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } }

该方法需要android.Manifest.permission#NETWORK_SETTINGS权限

SoftApConfiguration相关的源码,可查看Here

Android 10 热点

我们当前开发是基于Android 10,所以记录下相关的内容

WifiManager在Android10源码中的位置为frameworks/base/wifi/java/android/net/wifi/WifiManager.java

获取热点的设备数量可参考WifiTetherSoftApManager.java,位于packages/apps/Settings/src/com/android/settings/wifi/tether/WifiTetherSoftApManager.java

/**
 * Wrapper for {@link android.net.wifi.WifiManager.SoftApCallback} to pass the robo test
 */
public class WifiTetherSoftApManager {

    private WifiManager mWifiManager;
    private WifiTetherSoftApCallback mWifiTetherSoftApCallback;

    private WifiManager.SoftApCallback mSoftApCallback = new WifiManager.SoftApCallback() {
        @Override
        public void onStateChanged(int state, int failureReason) {
            mWifiTetherSoftApCallback.onStateChanged(state, failureReason);
        }

        @Override
        public void onNumClientsChanged(int numClients) {
            mWifiTetherSoftApCallback.onNumClientsChanged(numClients);
        }
    };
    private Handler mHandler;

    WifiTetherSoftApManager(WifiManager wifiManager,
            WifiTetherSoftApCallback wifiTetherSoftApCallback) {
        mWifiManager = wifiManager;
        mWifiTetherSoftApCallback = wifiTetherSoftApCallback;
        mHandler = new Handler();
    }

    public void registerSoftApCallback() {
        mWifiManager.registerSoftApCallback(mSoftApCallback, mHandler);
    }

    public void unRegisterSoftApCallback() {
        mWifiManager.unregisterSoftApCallback(mSoftApCallback);
    }

    public interface WifiTetherSoftApCallback {
        void onStateChanged(int state, int failureReason);

        void onNumClientsChanged(int numClients);
    }
}

WifiTetherSettings.java中有Wifi热点设置的相关内容,位于packages/apps/Settings/src/com/android/settings/wifi/tether/WifiTetherSettings.java

如配置Wifi热点:

    @Override
    public void onTetherConfigUpdated() {
        final WifiConfiguration config = buildNewConfig();
        mPasswordPreferenceController.updateVisibility(config.getAuthType());

        /**
         * 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;
            mSwitchBarController.stopTether();
        }
        mWifiManager.setWifiApConfiguration(config);
    }

    private WifiConfiguration buildNewConfig() {
        final WifiConfiguration config = new WifiConfiguration();
        final int securityType = mSecurityPreferenceController.getSecurityType();

        config.SSID = mSSIDPreferenceController.getSSID();
        config.allowedKeyManagement.set(securityType);
        config.preSharedKey = mPasswordPreferenceController.getPasswordValidated(securityType);
        config.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN);
        config.apBand = mApBandPreferenceController.getBandIndex();
        return config;
    }

其它不错的库:

Android-Wifi-Hotspot-Manager-ClassAndroidNetworkToolsDeviceScanManager29.java

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/992969.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-21
下一篇 2022-05-21

发表评论

登录后才能评论

评论列表(0条)

保存