前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >LocalOnlyHotspot学习总结(二)

LocalOnlyHotspot学习总结(二)

作者头像
用户7557625
发布2020-07-15 10:39:30
1K0
发布2020-07-15 10:39:30
举报

前几天学习了LocalOnlyHotspot相关内容,做了总结LocalOnlyHotspot学习总结,还有一些问题没有搞明白,最后搞清楚了,在这里记录一下。

问题

1、LocalOnlyHotspot开启以后,应用退出前台几秒热点就会自动关闭。 2、连接LocalOnlyHotspot不能访问外网。

针对这俩个问题,在下面会分开讨论。

一、LocalOnlyHotspot开启以后,应用退出后台几秒热点就会自动关闭。

先看一下原来的代码,我们通过startLocalOnlyHotspot打开热点,需要LocalOnlyHotspotCallback参数,而在LocalOnlyHotspotCallback的onStart函数中又有一个LocalOnlyHotspotReservation参数。在关闭热点时,需要调用LocalOnlyHotspotReservation的close方法来关闭。 而当你第二次打开热点时,又会创建一个LocalOnlyHotspotReservation对象,这时第一次开启热点时创建的LocalOnlyHotspotReservation对象就会被回收,LocalOnlyHotspotReservation对象被回收时会调用它的close函数,从而关闭热点。这也就是为什么应用退出前台后热点会关闭。

代码语言:javascript
复制
if (isChecked) {
          Log.d(TAG, "startLocalOnlyHotspot: ");
           mWifiManager.startLocalOnlyHotspot(new WifiManager.LocalOnlyHotspotCallback() {
               @Override
               public void onStarted(LocalOnlyHotspotReservation reservation) {
                   super.onStarted(reservation);
                   mLocalOnlyHotspotReservation = reservation;
                   ssid = reservation.getWifiConfiguration().SSID;
                   pwd = reservation.getWifiConfiguration().preSharedKey;
                   Log.e(TAG, "ssid and pwd is" + ssid + "and" + pwd);
                   HandlerThread testHandlerThread = new HandlerThread(reservation);
               }

               @Override
               public void onStopped() {
                   super.onStopped();
                   Log.e("SetLocalOnlyHotSpotController", "stopped");
               }

               @Override
               public void onFailed(int reason) {
                   super.onFailed(reason);
               }
           }, new Handler());
       } else {
           Log.d(TAG, "stopLocalOnlyHotspot: ");
           if (mLocalOnlyHotspotReservation != null) {
               mLocalOnlyHotspotReservation.close();
               mLocalOnlyHotspotReservation = null;
           }
       }

可以看到LocalOnlyHotspotReservation被回收时会调用自己的close函数。

代码语言:javascript
复制
protected void finalize() throws Throwable {
            try {
                if (mCloseGuard != null) {
                    mCloseGuard.warnIfOpen();
                }
                close();
            } finally {
                super.finalize();
            }
        }

解决策略: 写一个线程,在开启热点的时候把创建的LocalOnlyHotspotReservation的值传给线程的成员变量,然后把这个线程设为守护线程。这样只要进程不被杀死,之前创建的LocalOnlyHotspotReservation 对象就不会被回收,就可以解决热点自动关闭这个问题了。

代码语言:javascript
复制
mWifiManager.startLocalOnlyHotspot(new WifiManager.LocalOnlyHotspotCallback() {
                        @Override
                        public void onStarted(LocalOnlyHotspotReservation reservation) {
                            super.onStarted(reservation);
                            mLocalOnlyHotspotReservation = reservation;
                            ssid = reservation.getWifiConfiguration().SSID;
                            pwd = reservation.getWifiConfiguration().preSharedKey;
                            Log.e(TAG, "ssid and pwd is" + ssid + "and" + pwd);
                            HandlerThread testHandlerThread = new HandlerThread(reservation);
                            testHandlerThread.setDaemon(true);
                            testHandlerThread.start();
                        }

线程代码:

代码语言:javascript
复制
private class HandlerThread extends Thread {
        private LocalOnlyHotspotReservation mHandlerThreadHotspotReservation;

        public HandlerThread(LocalOnlyHotspotReservation localOnlyHotspotReservation) {
            mHandlerThreadHotspotReservation = localOnlyHotspotReservation;
        }

        @Override
        public void run() {
            super.run();
            while (true) {
                try {
                    sleep(500);
                    Log.d("HandlerThread" , "HandlerThread is running, thread id: " + Thread.currentThread().getId());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

二、连接LocalOnlyHotspot不能访问外网。

代码链接:http://androidxref.com/9.0.0_r3/xref/frameworks/opt/net/wifi/service/java/com/android/server/wifi/SoftApManager.java 热点开启我们就不多说了,直接到SoftApManager中去看。SoftApManager也是一个状态机。我们直接看StartedState 状态,因为这个是热点打开以后的配置。onUpChanged函数会调用updateApState函数,改变softAp的状态。

代码语言:javascript
复制
private class StartedState extends State {
        private void onUpChanged(boolean isUp) {
                if (isUp == mIfaceIsUp) {
                    return;  // no change
                }
                mIfaceIsUp = isUp;
                if (isUp) {
                    Log.d(TAG, "SoftAp is ready for use");
                    updateApState(WifiManager.WIFI_AP_STATE_ENABLED,
                            WifiManager.WIFI_AP_STATE_ENABLING, 0);
                    mWifiMetrics.incrementSoftApStartResult(true, 0);
                    if (mCallback != null) {
                        mCallback.onNumClientsChanged(mNumAssociatedStations);
                    }
                } else {
                    // the interface was up, but goes down
                    sendMessage(CMD_INTERFACE_DOWN);
                }
                mWifiMetrics.addSoftApUpChangedEvent(isUp, mMode);
            }

updateApState函数主要是发送广播。我们看WIFI_AP_STATE_CHANGED_ACTION这个关闭被谁接收到了。

代码语言:javascript
复制
private void updateApState(int newState, int currentState, int reason) {
        mCallback.onStateChanged(newState, reason);

        //send the AP state change broadcast
        final Intent intent = new Intent(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
        intent.putExtra(WifiManager.EXTRA_WIFI_AP_STATE, newState);
        intent.putExtra(WifiManager.EXTRA_PREVIOUS_WIFI_AP_STATE, currentState);
        if (newState == WifiManager.WIFI_AP_STATE_FAILED) {
            //only set reason number when softAP start failed
            intent.putExtra(WifiManager.EXTRA_WIFI_AP_FAILURE_REASON, reason);
        }

        intent.putExtra(WifiManager.EXTRA_WIFI_AP_INTERFACE_NAME, mApInterfaceName);
        intent.putExtra(WifiManager.EXTRA_WIFI_AP_MODE, mMode);
        mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
    }

代码链接:http://androidxref.com/9.0.0_r3/xref/frameworks/base/services/core/java/com/android/server/connectivity/Tethering.java 在Tethering.java中找到了WIFI_AP_STATE_CHANGED_ACTION这个广播的相关处理。curState这个参数在此时应该是WIFI_AP_STATE_ENABLED,因为热点成功打开了。相应的处理我们看enableWifiIpServingLocked函数。

代码语言:javascript
复制
private void handleWifiApAction(Intent intent) {
            final int curState = intent.getIntExtra(EXTRA_WIFI_AP_STATE, WIFI_AP_STATE_DISABLED);
            final String ifname = intent.getStringExtra(EXTRA_WIFI_AP_INTERFACE_NAME);
            final int ipmode = intent.getIntExtra(EXTRA_WIFI_AP_MODE, IFACE_IP_MODE_UNSPECIFIED);

            synchronized (Tethering.this.mPublicSync) {
                switch (curState) {
                    case WifiManager.WIFI_AP_STATE_ENABLING:
                        // We can see this state on the way to both enabled and failure states.
                        break;
                    case WifiManager.WIFI_AP_STATE_ENABLED:
                        enableWifiIpServingLocked(ifname, ipmode);
                        break;
                    case WifiManager.WIFI_AP_STATE_DISABLED:
                    case WifiManager.WIFI_AP_STATE_DISABLING:
                    case WifiManager.WIFI_AP_STATE_FAILED:
                    default:
                        disableWifiIpServingLocked(ifname, curState);
                        break;
                }
            }
        }
    }

在这里通过changeInterfaceState函数把ipServingMode参数传了出去,ipServingMode参数是来区分是普通的热点还是localonly热点。

代码语言:javascript
复制
private void enableWifiIpServingLocked(String ifname, int wifiIpMode) {
        // Map wifiIpMode values to IControlsTethering serving states, inferring
        // from mWifiTetherRequested as a final "best guess".
        final int ipServingMode;
        switch (wifiIpMode) {
            case IFACE_IP_MODE_TETHERED:
                ipServingMode = IControlsTethering.STATE_TETHERED;
                break;
            case IFACE_IP_MODE_LOCAL_ONLY:
                ipServingMode = IControlsTethering.STATE_LOCAL_ONLY;
                break;
            default:
                mLog.e("Cannot enable IP serving in unknown WiFi mode: " + wifiIpMode);
                return;
        }

        if (!TextUtils.isEmpty(ifname)) {
            maybeTrackNewInterfaceLocked(ifname, TETHERING_WIFI);
            changeInterfaceState(ifname, ipServingMode);
        } else {
            mLog.e(String.format(
                   "Cannot enable IP serving in mode %s on missing interface name",
                   ipServingMode));
        }
    }

可以看到,在changeInterfaceState这里,普通热点和本地热点(localonlyhotspot)没有区分,统一处理。

代码语言:javascript
复制
private void changeInterfaceState(String ifname, int requestedState) {
        final int result;
        switch (requestedState) {
            case IControlsTethering.STATE_UNAVAILABLE:
            case IControlsTethering.STATE_AVAILABLE:
                result = untether(ifname);
                break;
            case IControlsTethering.STATE_TETHERED:
            case IControlsTethering.STATE_LOCAL_ONLY:
                result = tether(ifname, requestedState);
                break;
            default:
                Log.wtf(TAG, "Unknown interface state: " + requestedState);
                return;
        }
        if (result != TETHER_ERROR_NO_ERROR) {
            Log.e(TAG, "unable start or stop tethering on iface " + ifname);
            return;
        }
    }

在这里,tetherState发送了CMD_TETHER_REQUESTED消息给TetherInterfaceStateMachine状态机,requestedState是CMD_TETHER_REQUESTED消息里的内容,用来区分是普通热点还是本地热点。

代码语言:javascript
复制
private int tether(String iface, int requestedState) {
        if (DBG) Log.d(TAG, "Tethering " + iface);
        synchronized (mPublicSync) {
            TetherState tetherState = mTetherStates.get(iface);
            if (tetherState == null) {
                Log.e(TAG, "Tried to Tether an unknown iface: " + iface + ", ignoring");
                return TETHER_ERROR_UNKNOWN_IFACE;
            }
            // Ignore the error status of the interface.  If the interface is available,
            // the errors are referring to past tethering attempts anyway.
            if (tetherState.lastState != IControlsTethering.STATE_AVAILABLE) {
                Log.e(TAG, "Tried to Tether an unavailable iface: " + iface + ", ignoring");
                return TETHER_ERROR_UNAVAIL_IFACE;
            }
            // NOTE: If a CMD_TETHER_REQUESTED message is already in the TISM's
            // queue but not yet processed, this will be a no-op and it will not
            // return an error.
            //
            // TODO: reexamine the threading and messaging model.
            tetherState.stateMachine.sendMessage(
                    TetherInterfaceStateMachine.CMD_TETHER_REQUESTED, requestedState);
            return TETHER_ERROR_NO_ERROR;
        }
    }

代码链接:http://androidxref.com/9.0.0_r3/xref/frameworks/base/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java

我们看TetherInterfaceStateMachine状态机是怎么处理的。此时状态机还是InitialState 状态,这时候CMD_TETHER_REQUESTED消息里的requestedState参数就起了作用。根据requestedState来判断热点的类型,然后跳转到不同的状态机。然后我们看LocalHotspotState和TetheredState俩种状态机的区别。

代码语言:javascript
复制
class InitialState extends State {
        @Override
        public void enter() {
            sendInterfaceState(IControlsTethering.STATE_AVAILABLE);
        }

        @Override
        public boolean processMessage(Message message) {
            logMessage(this, message.what);
            switch (message.what) {
                case CMD_TETHER_REQUESTED:
                    mLastError = ConnectivityManager.TETHER_ERROR_NO_ERROR;
                    switch (message.arg1) {
                        case IControlsTethering.STATE_LOCAL_ONLY:
                            transitionTo(mLocalHotspotState);
                            break;
                        case IControlsTethering.STATE_TETHERED:
                            transitionTo(mTetheredState);
                            break;
                        default:
                            mLog.e("Invalid tethering interface serving state specified.");
                    }
                    break;
                case CMD_INTERFACE_DOWN:
                    transitionTo(mUnavailableState);
                    break;
                case CMD_IPV6_TETHER_UPDATE:
                    updateUpstreamIPv6LinkProperties((LinkProperties) message.obj);
                    break;
                default:
                    return NOT_HANDLED;
            }
            return HANDLED;
        }
    }

我们看俩个状态机对CMD_TETHER_CONNECTION_CHANGED消息的处理。 可以看到LocalHotspotState 不对这个消息做任何处理,而TetheredState 则做了处理,那么这里就是区别所在。我们直接去看TetheredState 的处理内容。

代码语言:javascript
复制
class LocalHotspotState extends BaseServingState {
       ·········
        @Override
        public boolean processMessage(Message message) {
            if (super.processMessage(message)) return true;

            logMessage(this, message.what);
            switch (message.what) {
                case CMD_TETHER_REQUESTED:
                    mLog.e("CMD_TETHER_REQUESTED while in local-only hotspot mode.");
                    break;
                case CMD_TETHER_CONNECTION_CHANGED:
                    // Ignored in local hotspot state.
                    break;
                default:
                    return false;
            }
            return true;
        }
    }

我们看TetheredState里的处理。调用了enableNat和startInterfaceForwarding方法,再看mNMService是哪个类,mNMService是NetworkManagementService对象。那我们到NetworkManagementService里看这俩个函数的实现。

代码语言:javascript
复制
class TetheredState extends BaseServingState {
         ··············
        @Override
        public boolean processMessage(Message message) {
            switch (message.what) {
                case CMD_TETHER_REQUESTED:
                    mLog.e("CMD_TETHER_REQUESTED while already tethering.");
                    break;
                case CMD_TETHER_CONNECTION_CHANGED:
                    final InterfaceSet newUpstreamIfaceSet = (InterfaceSet) message.obj;
                    if (noChangeInUpstreamIfaceSet(newUpstreamIfaceSet)) {
                        if (VDBG) Log.d(TAG, "Connection changed noop - dropping");
                        break;
                    }

                    if (newUpstreamIfaceSet == null) {
                        cleanupUpstream();
                        break;
                    }

                    for (String removed : upstreamInterfacesRemoved(newUpstreamIfaceSet)) {
                        cleanupUpstreamInterface(removed);
                    }

                    final Set<String> added = upstreamInterfacesAdd(newUpstreamIfaceSet);
                    // This makes the call to cleanupUpstream() in the error
                    // path for any interface neatly cleanup all the interfaces.
                    mUpstreamIfaceSet = newUpstreamIfaceSet;

                    for (String ifname : added) {
                        try {
                            mNMService.enableNat(mIfaceName, ifname);
                            mNMService.startInterfaceForwarding(mIfaceName, ifname);
                        } catch (Exception e) {
                            mLog.e("Exception enabling NAT: " + e);
                            cleanupUpstream();
                            mLastError = ConnectivityManager.TETHER_ERROR_ENABLE_NAT_ERROR;
                            transitionTo(mInitialState);
                            return true;
                        }
                    }
                    break;
                default:
                    return false;
            }
            return true;
        }
        ···········
}

代码链接: http://androidxref.com/9.0.0_r3/xref/frameworks/base/services/core/java/com/android/server/NetworkManagementService.java enableNat() 的作用是启用两个接口之间的网络地址转换,

代码语言:javascript
复制
public void enableNat(String internalInterface, String externalInterface) {
        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
        try {
            modifyNat("enable", internalInterface, externalInterface);
        } catch (SocketException e) {
            throw new IllegalStateException(e);
        }
    }

startInterfaceForwarding的作用是启用从端到端的单向数据包转发。

代码语言:javascript
复制
public void startInterfaceForwarding(String fromIface, String toIface) {
        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
        modifyInterfaceForward(true, fromIface, toIface);
    }

也就是说,enableNat 和 startInterfaceForwarding是打开Nat,启用转发,也就是服务端把客户端的请求转发到网络,客户端才能够连接到网络。而LocalHotspotState没有做这一步,当然访问不了外网喽。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019-11-01 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 问题
  • 一、LocalOnlyHotspot开启以后,应用退出后台几秒热点就会自动关闭。
  • 二、连接LocalOnlyHotspot不能访问外网。
相关产品与服务
命令行工具
腾讯云命令行工具 TCCLI 是管理腾讯云资源的统一工具。使用腾讯云命令行工具,您可以快速调用腾讯云 API 来管理您的腾讯云资源。此外,您还可以基于腾讯云的命令行工具来做自动化和脚本处理,以更多样的方式进行组合和重用。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档