概述
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。
网友评论