IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Android WiFi热点 -> 正文阅读

[移动开发]Android WiFi热点

Android WiFi热点

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

开启或者关闭热点

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

//热点的配置类
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的版本,在网络上最后找到了如下的几个解决方案:

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

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权限,在手机权限里面设置下,打开这个权限
011
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的手机上,测试是有效果的

013 014

关闭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();
        }
    }

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

参考文档:

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_DISABLED - 值为11
  • WIFI_AP_STATE_DISABLING - 值为10
  • WIFI_AP_STATE_ENABLED- 值为13
  • WIFI_AP_STATE_ENABLING- 值为12
  • WIFI_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热点配置

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

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的权限,所以有提示如下的异常:

018

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 11尝试后,有如下的提示,看来是不再有效了:

016

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)处有给出新的方法,尝试之后也行不通

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

    /**
     * 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:
     * <ul>
     * <li> {@link SoftApCallback#onStateChanged(int, int)}</li>
     * <li> {@link SoftApCallback#onConnectedClientsChanged(List<WifiClient>)}</li>
     * <li> {@link SoftApCallback#onInfoChanged(SoftApInfo)}</li>
     * <li> {@link SoftApCallback#onCapabilityChanged(SoftApCapability)}</li>
     * </ul>
     * 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}
     * <p>
     * Applications should have the
     * {@link android.Manifest.permission#NETWORK_SETTINGS NETWORK_SETTINGS} permission. Callers
     * without the permission will trigger a {@link java.lang.SecurityException}.
     * <p>
     *
     * @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();
        }
    }

SoftApConfiguration相关的源码,可查看Here

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-08-20 15:14:29  更:2021-08-20 15:15:56 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 9:47:24-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码