美文网首页
Android HttpProxy 工作原理(上)

Android HttpProxy 工作原理(上)

作者: e3283872bb5d | 来源:发表于2022-11-17 17:21 被阅读0次

概述

Android HttpProxy 的工作原理分为代理的配置和读取,配置代理可以由系统设置中配置,也可在 App 应用中配置系统代理,其中 App 配置是在 Android 10 才提供的 API 接口。
代理读取也并不是所有的 App 都支持,看 App 具体实现;从功能完整性讲,任何 App 都应该响应系统配置的代理,okhttp 网络库默认支持了系统代理。

代理的格式

public class ProxyInfo implements Parcelable {
    // 代理地址
    private final String mHost;
    // 代理端口
    private final int mPort;
    // 不在代理里面的域名或者 ip 列表(todo 待确认具体格式)
    private final String mExclusionList;
    // pac 文件的 http 地址
    private final Uri mPacFileUrl;
    // ...
}

配置代理

通过系统设置配置

具体 UI 入口是在:设置→ WLAN→ 长按具体的 WIFI 名→ 修改网络→ 手动代理或自动代理

保存按钮会进入 onSubmit
// file: packages/apps/Settings/src/com/android/settings/wifi/WifiDialogActivity.java
public void onSubmit(WifiDialog dialog) {
    if (!hasWifiManager()) return;
    // 读取用户输入的配置,包括代理
    final WifiConfiguration config = dialog.getController().getConfig();
    final AccessPoint accessPoint = dialog.getController().getAccessPoint();
    if (getIntent().getBooleanExtra(KEY_CONNECT_FOR_CALLER, true)) {
        if (config == null) {
            //...
        } else {
            mWifiManager.save(config, null /* listener */);
            //...   
        }
    }
    Intent resultData = hasPermissionForResult() ? createResultData(config, accessPoint) : null;
    setResult(RESULT_CONNECTED, resultData);
    finish();
}

先从输入控制中心获取配置好的配置文件,根据配置执行配置保存操作。

用户在设置页面输入代理,通过 Controller 获取
// file: packages/apps/Settings/src/com/android/settings/wifi/WifiConfigController.java
public WifiConfiguration getConfig() {
    if (mMode == WifiConfigUiBase.MODE_VIEW) {
        return null;
    }
    WifiConfiguration config = new WifiConfiguration();
    //...
    final IpConfiguration ipConfig = new IpConfiguration();
    ipConfig.setIpAssignment(mIpAssignment);
    ipConfig.setProxySettings(mProxySettings);
    ipConfig.setStaticIpConfiguration(mStaticIpConfiguration);
    // 设置 ProxyInfo
    ipConfig.setHttpProxy(mHttpProxy);
    config.setIpConfiguration(ipConfig);
    //...
}

创建 WifiConfiguration 并赋值,这里设置了 httpProxy,mHttpProxy 是 ProxyInfo 对象。

ProxyInfo mHttpProxy 初始化及赋值,通过监听输入框内容变化不断检查并赋值 mHttpProxy
// file: packages/apps/Settings/src/com/android/settings/wifi/WifiConfigController.java
private boolean ipAndProxyFieldsAreValid() {
    //...
    mProxySettings = ProxySettings.NONE;
    mHttpProxy = null;
    // 手动代理
    if (selectedPosition == PROXY_STATIC && mProxyHostView != null) {
        mProxySettings = ProxySettings.STATIC;
        String host = mProxyHostView.getText().toString();
        String portStr = mProxyPortView.getText().toString();
        String exclusionList = mProxyExclusionListView.getText().toString();
        int port = 0;
        int result = 0;
        try {
            port = Integer.parseInt(portStr);
            // 校验输入 Host、IP 内容是否合法
            result = ProxySelector.validate(host, portStr, exclusionList);
        } catch (NumberFormatException e) {
            result = R.string.proxy_error_invalid_port;
        }
        if (result == 0) {
            mHttpProxy = ProxyInfo.buildDirectProxy(host, port, Arrays.asList(exclusionList.split(",")));
        } else {
            return false;
        }
    // 自动代理,用户填写 pac URL 地址,系统检测到是 PAC 地址的话,会先下载 PAC 文件,下文会介绍下载过程。
    } else if (selectedPosition == PROXY_PAC && mProxyPacView != null) {
        mProxySettings = ProxySettings.PAC;
        CharSequence uriSequence = mProxyPacView.getText();
        if (TextUtils.isEmpty(uriSequence)) {
            return false;
        }
        Uri uri = Uri.parse(uriSequence.toString());
        if (uri == null) {
            return false;
        }
        mHttpProxy = ProxyInfo.buildPacProxy(uri);
   }
   return true;
}

两种模式配置代理,自动和手动,手动是通过设置 Host 和 Port、自动是设置 PAC 的 URL,根据不同的模式,调用不同的构造方法创建 ProxyInfo。

回到保存配置操作
// file: packages/modules/Wifi/framework/java/android/net/wifi/WifiManager.java
public void save(@NonNull WifiConfiguration config, @Nullable ActionListener listener) {
    //...
    // 调用 service 的 save,service 是 WifiServiceImpl 的实例
    mService.save(config, listenerProxy, mContext.getOpPackageName());
    //...
}
Framework service 保存代理配置
// file: packages/modules/Wifi/service/java/com/android/server/wifi/WifiServiceImpl.java
public void save(WifiConfiguration config, @Nullable IActionListener callback, @NonNull String packageName) {
    //...
    mWifiThreadRunner.post(() -> {
        ActionListenerWrapper wrapper = new ActionListenerWrapper(callback);
        // 更新内存配置
        NetworkUpdateResult result = mWifiConfigManager.updateBeforeSaveNetwork(config, uid, packageName);
        if (result.isSuccess()) {
            broadcastWifiCredentialChanged(WifiManager.WIFI_CREDENTIAL_SAVED, config);
            mMakeBeforeBreakManager.stopAllSecondaryTransientClientModeManagers(() ->
                // 调用保存操作
                mActiveModeWarden.getPrimaryClientModeManager().saveNetwork(result, wrapper, uid, packageName));
                //...
        }
        //...
    }
}

先更新内存中 Wifi 配置,更新完之后,调用真正的保存操作。

saveNetwork
// file: packages/modules/Wifi/service/java/com/android/server/wifi/ClientModeImpl.java
public void saveNetwork(NetworkUpdateResult result, ActionListenerWrapper wrapper, int callingUid, @NonNull String packageName) {
    Message message = obtainMessage(CMD_SAVE_NETWORK, new ConnectNetworkMessage(result, wrapper, packageName));
    message.sendingUid = callingUid;
    sendMessage(message);
}
processMessage
// file: packages/modules/Wifi/service/java/com/android/server/wifi/ClientModeImpl.java
public boolean processMessage(Message message) {
    switch (message.what) {
        //...
        case CMD_SAVE_NETWORK: {
            //...
            if (result.hasProxyChanged()) {
                if (mIpClient != null) {
                    log("Reconfiguring proxy on connection");
                    WifiConfiguration currentConfig =getConnectedWifiConfigurationInternal();
                    if (currentConfig != null) {
                        mIpClient.setHttpProxy(currentConfig.getHttpProxy());
                    }
                }
            }
            //...
        }
    }
}

存在代理改变的话,调用 IpClient 的 setHttpProxy 方法

IpClient 中 setHttpProxy
// file: packages/modules/NetworkStack/src/android/net/ip/IpClient.java
public void setHttpProxy(ProxyInfo proxyInfo) {
    sendMessage(CMD_UPDATE_HTTP_PROXY, proxyInfo);
}
public boolean processMessage(Message message) {
    switch (message.what) {
        //...
        case CMD_UPDATE_HTTP_PROXY: {
            mHttpProxy = (ProxyInfo) msg.obj;
            // SEND_CALLBACKS 为 true
            handleLinkPropertiesUpdate(SEND_CALLBACKS);
        }
    }
}
private boolean handleLinkPropertiesUpdate(boolean sendCallbacks) {
    // 创建一个新的链接属性,httpProxy 将在里面初始化
    final LinkProperties newLp = assembleLinkProperties();
    //...
    if (sendCallbacks) {
        // 通知链接属性有更新了
        dispatchCallback(delta, newLp);
    }
    return (delta != PROV_CHANGE_LOST_PROVISIONING);
}

通过配置更新命令,创建新的链接 LinkProperties,LinkProperties 包含了 ProxyInfo 属性,准备就绪回调 newLp。

// file: packages/modules/Wifi/service/java/com/android/server/wifi/ClientModeImpl.java
void onLinkPropertiesChange(LinkProperties newLp)
void updateLinkProperties(LinkProperties newLp)
// file: packages/modules/Connectivity/framework/src/android/net/NetworkAgent.java
void sendLinkProperties(@NonNull LinkProperties linkProperties)
mHandler.obtainMessage(NetworkAgent.EVENT_NETWORK_PROPERTIES_CHANGED,
                    new Pair<>(NetworkAgentInfo.this, lp)).sendToTarget();
// file: packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java
void updateNetworkInfo(NetworkAgentInfo networkAgent, NetworkInfo info) 
void updateLinkProperties(NetworkAgentInfo networkAgent, @NonNull LinkProperties newLp,
            @NonNull LinkProperties oldLp)

以上是一些基本的类跳转,直接把源码和对应的文件贴出来,最终会进入 ConnectivityService.java 的 updateLinkProperties

updateLinkProperties
// file: packages/modules/Connectivity/service/src/com/android/server/ConnectivityService.java
public void updateLinkProperties(NetworkAgentInfo networkAgent, @NonNull LinkProperties newLp,
            @NonNull LinkProperties oldLp) {
    //...
    if (isDefaultNetwork(networkAgent)) {
        handleApplyDefaultProxy(newLp.getHttpProxy());
    } else {
        updateProxy(newLp, oldLp);
    }
}

最终会进入执行更新代理的操作,这里判断了当前更新的网络是否是默认(正在使用的网络),如果是则需要将此代理设置为默认代理,否则只通知更新即可。

通知代理更新
// file: packages/modules/Connectivity/service/src/com/android/server/connectivity/ProxyTracker.java
public void sendProxyBroadcast() {
    final ProxyInfo defaultProxy = getDefaultProxy();
    final ProxyInfo proxyInfo = null != defaultProxy ?
                defaultProxy : ProxyInfo.buildDirectProxy("", 0, Collections.emptyList());
    mPacProxyManager.setCurrentProxyScriptUrl(proxyInfo);
    Intent intent = new Intent(Proxy.PROXY_CHANGE_ACTION);
    intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING |
                Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
    intent.putExtra(EXTRA_PROXY_INFO, proxyInfo);
    final long ident = Binder.clearCallingIdentity();
    // 发送粘性广播,在广播注册时会收到一次
    mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
}

这里做了两件事,一是 PAC 管理器处理代理中的 pac URL,二是发送广播通知接受者,广播接收者可以是系统定义的,也可以是用户自己定义的。这里值得注意一点,ProxyInfo 已经在 ProxyTracker 内存中存在一份了,业务可以通过 getDefaultHttpProxy 获取到,系统也确实提供了 Api 接口,是在 ConnectivityManager 中,如何读取代理将会介绍。

系统接收到代理变更通知
// file: frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
case Proxy.PROXY_CHANGE_ACTION:
    mHandler.sendMessage(mHandler.obtainMessage(UPDATE_HTTP_PROXY_MSG));
    break;
mProcessList.setAllHttpProxy();

系统接收代理变更的广播是在 ActivityManagerService 中注册的,接收到广播之后调用了进程管理器(ProcessList)的 setAllHttpProxy。

setAllHttpProxy
// file: frameworks/base/services/core/java/com/android/server/am/ProcessList.java
void setAllHttpProxy() {
    // Update the HTTP proxy for each application thread.
    synchronized (mProcLock) {
        for (int i = mLruProcesses.size() - 1 ; i >= 0 ; i--) {
            ProcessRecord r = mLruProcesses.get(i);
            IApplicationThread thread = r.getThread();
            if (r.getPid() != ActivityManagerService.MY_PID && thread != null && !r.isolated) {
                // 更新每个进程的 httpProxy
                thread.updateHttpProxy();
            }
        }
    }
    // 更新 ams 的 httpProxy
    ActivityThread.updateHttpProxy(mService.mContext);
}
updateHttpProxy
// file: frameworks/base/core/java/android/app/ActivityThread.java
public static void updateHttpProxy(@NonNull Context context) {
    final ConnectivityManager cm = context.getSystemService(ConnectivityManager.class);
    Proxy.setHttpProxyConfiguration(cm.getDefaultProxy());
}

进入 Proxy 设置 httpProxy

setHttpProxyConfiguration
// file: frameworks/base/core/java/android/net/Proxy.java
public static void setHttpProxyConfiguration(String host, String port, String exclList,
            Uri pacFileUrl) {
    if (exclList != null) exclList = exclList.replace(",", "|");
    if (host != null) {
        System.setProperty("http.proxyHost", host);
        System.setProperty("https.proxyHost", host);
    } else {
        System.clearProperty("http.proxyHost");
        System.clearProperty("https.proxyHost");
    }
    if (port != null) {
        System.setProperty("http.proxyPort", port);
        System.setProperty("https.proxyPort", port);
    } else {
        System.clearProperty("http.proxyPort");
        System.clearProperty("https.proxyPort");
    }
    if (exclList != null) {
        System.setProperty("http.nonProxyHosts", exclList);
        System.setProperty("https.nonProxyHosts", exclList);
    } else {
        System.clearProperty("http.nonProxyHosts");
        System.clearProperty("https.nonProxyHosts");
    }
    if (!Uri.EMPTY.equals(pacFileUrl)) {
        ProxySelector.setDefault(new PacProxySelector());
    } else {
        ProxySelector.setDefault(sDefaultProxySelector);
    }
}

这里会将代理设置到系统配置中,这个方法隐藏两个含义,设置和清空,当数据为空时相当于清空代理。最后根据是否含有 PAC 文件,设置了不同的 ProxySelector,PacProxySelector 和 DefaultProxySelector。

相关文章

网友评论

      本文标题:Android HttpProxy 工作原理(上)

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