Android网络代理原理及实现
网络代理简介
代理典型的分为三种类型:
正向代理
缓存服务器使用的代理机制最早是放在客户端一侧的,是代理的原型,称为正向代理。其目的之一
是缓存,另一目的是用来实现防火墙(阻止互联网与公司内网之间的包,同时要让必要的包通过)。
组网图如下所示:
在使用正向代理时,一般需要在浏览器的设置窗口中的代理服务器一栏中填写正向代理的IP地址。
当设置了正向代理时。浏览器会忽略网址栏的内容,直接将所有请求发给正向代理(Http 的URI
部分从原来的文件路径改成http的完整路径,用于转发)。请求包中的URI如下所示:
反向代理
通过将请求消息中的URI中的目录名与Web服务器进行关联,使得代理能够转发一般的不包含完整网
址的请求消息。这种方式称为反向代理(客户端不需要进行代理配置,地址栏中域名通过DNS服务器
解析成对应的为代理服务器地址,代理服务器根据请求消息中的URI的目录名转发给相应的服务器处
理)。这种方式一般应用于缓存服务器,分布式服务器场景。
透明代理
通过请求消息包头部中的IP地址,进行拦截转发,这种方式成为透明代理。透明代理需要对请求消
息进行拦截,如果请求消息到目的服务器有多条路径,就需要在每条路径中设置透明代理,通常为
了方便,需要把组网设计成只有一条路径,并在该条路径中进行拦截。比如在连接互联网的接入网
入口处可设置透明代理。
客户需求Http/Https Proxy业务
客户需求的Http/Https代理业务,属于客户端一侧的代理,即我们上述介绍的正向代理。
Android原生框架已支持http/https代理功能,博主基于原生框架接口实现对所有网络
进行代理配置。
框架实现
Android中的几个Proxy概念:
名称 | 描述 | 相关接口 |
---|---|---|
GlobalProxy | 全局代理,对所有网络(以太网,WiFi,vpn等)生效。 |
ConnectivityManager.getGlobalProxy() ConnectivityManager.setGlobalProxy(ProxyInfo p) |
NetWorkProxy |
存在全局代理,则全局代理即为该网络的代理,否则为该network特定的http代理。 说明:应用可以绑定指定的网络,当未配置全局代理时,各个应用的代理可以不同。 |
ConnectivityManager.getProxyForNetwork(Network network) ConnectivityManager.getBoundNetworkForProcess() ConnectivityManager.bindProcessToNetwork(Network network) |
DefaultProxy | 默认代理为系统环境生效代理。当指定全局代理时,会将全局代理应用到默认代理。没有指定全局代理时,默认网络(首选网络)的代理会应用到默认代理。 | ConnectivityManager.getDefaultProxy() |
本文的Http/Https代理业务,针对的是GlobalProxy的配置,对所有网络生效。
相关的配置项:
名称 | 描述 | Settings provider 中使用的Key | nvram 的Pvalue | 备注 |
---|---|---|---|---|
proxy host | 代理服务器地址 | Settings.Global.GLOBAL_HTTP_PROXY_HOST | 以"host:port"形式持久存储 | |
proxy port | 代理服务器端口 | Settings.Global.GLOBAL_HTTP_PROXY_PORT | 以"host:port"形式持久存储 | |
exclusion list | 不使用代理的网址列表 | Settings.Global.GLOBAL_HTTP_PROXY_EXCLUSION_LIST | 持久存储 | |
PAC(Proxy Auto Config) | 代理配置脚本文件,可以实现对符合一定规则的url配置相应的代理服务器,实现多代理服务器的大型代理网络,具体可以参考 PAC(代理自动配置)_百度百科, PAC自动代理文件格式,教你如何写PAC文件 | Settings.Global.GLOBAL_HTTP_PROXY_PAC | 无 | 博主目前不支持 |
Global Proxy配置实现架构
http/https 客户端请求Proxy应用详解
方式一 基于Android ojluni实现
- ojluni(OpenJDK Lang,Util,Net,IO)库提供了
URL
,HttpURLConnection
等类用于建立http连接。 - 最原始的这个Java lib库,需要应用自己调用
ProxySelector
,对URL进行处理,并用处理后的URI进行
请求。 - 在Android4.4中,框架层引入了okhttp(代码目录:android/external/okhttp),并基于okhttp实现了
HttpHandler
和HttpsHandler
(代码目录:android/external/okhttp/android/main/java/com/squareup/okhttp
),
这两个类实现了URLStreamHandler
。 - 在ojluni中Android利用反射机制,当解析出协议为http或者https时,将http请求或https请求的处理实现交给okhttp的
HttpHandler
或HttpsHandler
,并且没有携带指定的proxy。
android/libcore/ojluni/src/main/java/java/net/URL.java
static URLStreamHandler getURLStreamHandler(String protocol) {/*此处略去其他代码*/// Fallback to built-in stream handler.// Makes okhttp the default http/https handlerif (handler == null) {try {// BEGIN Android-changed// Use of okhttp for http and https// Removed unnecessary use of reflection for sun classesif (protocol.equals("file")) {handler = new sun.net.www.protocol.file.Handler();} else if (protocol.equals("ftp")) {handler = new sun.net.www.protocol.ftp.Handler();} else if (protocol.equals("jar")) {handler = new sun.net.www.protocol.jar.Handler();} else if (protocol.equals("http")) {handler = (URLStreamHandler)Class.forName("com.android.okhttp.HttpHandler").newInstance();} else if (protocol.equals("https")) {handler = (URLStreamHandler)Class.forName("com.android.okhttp.HttpsHandler").newInstance();}// END Android-changed} catch (Exception e) {throw new AssertionError(e);}}}
在okhttp的
HttpHandler
或HttpsHandler
中如果没有指定proxy,则使用的是默认代理选择器(ProxySelector.getDefault()
)。ProxySelector.getDefault()
,实例为ojluni中的“sun.net.spi.DefaultProxySelector”,其根据系统环境中的http/https代理相关属性对URL进行解析匹配判断处理。系统环境中的http/https代理相关属性如下:
属性名称 http https 代理服务器地址 http.proxyHost https.proxyHost 代理服务器端口 http.proxyPort https.proxyPort 不使用代理的网址列表 http.nonProxyHosts https.nonProxyHosts 从上述实现中,可以看到,即便设置了全局代理(并应用到了系统默认代理),无法保证所有请求都走的全局代理。系统默认代理在全局代理设置结束后仍可以变更。
Demo1 此处仅示范最简单的同步请求:
private void demoHttpURLConnection() {new Thread() {@Overridepublic void run() {/*当我们设置页面修改代理参数==>nvram 变更==>SystemManager收到广播 ==>调用ConnectivityService.setGlobalProxy==>发出广播==》默认代理参数变更*///此处获取到的默认代理参数与设置的一致(http.nonProxyHosts分隔符变更为"|")String host = System.getProperty("http.proxyHost");String port = System.getProperty("http.proxyPort");String nonProxyHosts = System.getProperty("http.nonProxyHosts");// //假如我们在此处修改默认代理参数,那么接下来的http请求也就变更// System.setProperty("http.proxyHost", "");// System.setProperty("http.proxyPort", "");// System.setProperty("http.nonProxyHosts", "");Log.d(TAG, "host: " + host);Log.d(TAG, "port: " + port);Log.d(TAG, "nonProxyHosts: " + nonProxyHosts);//以下请求基于默认代理参数进行// 也就是"http.proxyHost","http.proxyPort","http.nonProxyHosts"只要确保正确,则代理请求正确try {URL url = new URL("http://192.168.xx.xx/cer/cert.pem");HttpURLConnection connection = (HttpURLConnection) url.openConnection();connection.setConnectTimeout(5000);int code = connection.getResponseCode();if (code == 200) {InputStream inputStream = connection.getInputStream();String ret = inputStreamString(inputStream);Log.d(TAG, "success.. ret:" + ret);} else {Log.e(TAG, "fail..");}} catch (Exception e) {e.printStackTrace();Log.e(TAG, "e:" + e.getMessage());}}}.start();}
方式二 基于okhttp实现
- okhttp因为设计强大,易用,问题少,因此Android框架中也将其引入,第三方应用中使用也很广泛。
- okhttp为开源http客户端,因此在Http代理的处理上设计为:
(1)直接设置代理的host port,使当前请求直接走指定的代理
(2)实现一个
ProxySelector
,对URL满足一定规则进行匹配处理,选择不同的代理(3)不设置代理或
ProxySelector
,okhttp内部直接使用默认代理服务器。
android/external/okhttp/okhttp/src/main/java/com/squareup/okhttp/OkHttpClient.java
OkHttpClient copyWithDefaults() {OkHttpClient result = new OkHttpClient(this);if (result.proxySelector == null) {result.proxySelector = ProxySelector.getDefault();}/*省略其他代码*/return result;}@android/external/okhttp/okhttp-urlconnection/src/main/java/com/squareup/okhttp/OkUrlFactory.javaHttpURLConnection open(URL url, Proxy proxy) {/*省略其他代码*/OkHttpClient copy = client.copyWithDefaults();copy.setProxy(proxy);/*省略其他代码*/}
- 上述okhttp的实现说明,使用okhttp进行请求也无法保证所有请求都走了全局代理(全局代理已应用到了默认代理),okhttp的单个请求可以特别指定它的代理服务器。
Demo2 此处仅示范最简单的同步请求:
private void demoOkhttp() {new Thread() {@Overridepublic void run() {try {//当指定了Proxy,则proxySelector会失效。不指定Proxy和proxySelector,走默认代理(同方式一,只要确保系统默认代理参数正确即可)OkHttpClient client = new OkHttpClient.Builder().proxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress("192.168.128.81", 808))).proxySelector(ProxySelector.getDefault()).build();//创建OkHttpClient对象Request request = new Request.Builder().url("http://192.168.xx.xx/cer/cert.pem")//请求接口。如果需要传参拼接到接口后面。.build();//创建Request 对象Response response = null;response = client.newCall(request).execute();//得到Response 对象if (response.isSuccessful()) {Log.d(TAG, "response.code()==" + response.code());Log.d(TAG, "response.message()==" + response.message());Log.d(TAG, "res==" + response.body().string());}} catch (Exception e) {e.printStackTrace();}}}.start();}
方式三 基于apache-http实现
- apache-http 也是属于使用比较广泛的开源http客户端。Android系统中也有引入(代码目录:
android/external/apache-http
),并在其基础上对代理相关代码做了一定的修改。 - 第三方应用使用时在moudle的gradle中增加以下,即可引入Android系统中的apache-http
android {useLibrary 'org.apache.http.legacy'}
- Android修改代理主要实现:
(1)设置指定代理时,直接使用指定代理
(2)未设置指定代理时,使用默认代理
android/external/apache-http/src/org/apache/http/impl/client/DefaultHttpClient.java
@Overrideprotected HttpRoutePlanner createHttpRoutePlanner() {// BEGIN android-changed// Use the proxy specified by system propertiesreturn new ProxySelectorRoutePlanner(getConnectionManager().getSchemeRegistry(), null);// END android-changed}@android/external/apache-http/src/org/apache/http/impl/conn/ProxySelectorRoutePlanner.javapublic HttpRoute determineRoute(HttpHost target,HttpRequest request,HttpContext context)throws HttpException {/*省略其他代码*/// BEGIN android-changed// If the client or request explicitly specifies a proxy (or no// proxy), prefer that over the ProxySelector's VM-wide default.HttpHost proxy = (HttpHost) request.getParams().getParameter(ConnRoutePNames.DEFAULT_PROXY);if (proxy == null) {proxy = determineProxy(target, request, context);} else if (ConnRouteParams.NO_HOST.equals(proxy)) {// value is explicitly unsetproxy = null;}// END android-changed/*省略其他代码*/return route;}protected HttpHost determineProxy(HttpHost target,HttpRequest request,HttpContext context)throws HttpException {// the proxy selector can be 'unset', so we better deal with null hereProxySelector psel = this.proxySelector;if (psel == null)psel = ProxySelector.getDefault();if (psel == null)return null;URI targetURI = null;try {targetURI = new URI(target.toURI());} catch (URISyntaxException usx) {throw new HttpException("Cannot convert host to URI: " + target, usx);}List<Proxy> proxies = psel.select(targetURI);Proxy p = chooseProxy(proxies, target, request, context);HttpHost result = null;if (p.type() == Proxy.Type.HTTP) {// convert the socket address to an HttpHostif (!(p.address() instanceof InetSocketAddress)) {throw new HttpException("Unable to handle non-Inet proxy address: "+p.address());}final InetSocketAddress isa = (InetSocketAddress) p.address();// assume default scheme (http)result = new HttpHost(getHost(isa), isa.getPort());}return result;}
- 上述代理实现说明,使用apache-http进行请求也无法保证所有请求都走了全局代理(全局代理已应用到了默认代理),apache-http的单个请求可以特别指定它的代理服务器。
Demo3 此处仅示范最简单的同步请求:
private void demoApacheHttp() {new Thread() {@Overridepublic void run() {try {HttpClient client = new DefaultHttpClient();HttpGet request = new HttpGet("http://192.168.xx.xx/cer/cert.pem");//当指定了Proxy,则直接使用该代理。不指定Proxy则走默认代理(同方式一,只要确保系统默认代理参数正确即可)HttpHost proxy = new HttpHost("192.168.xx.xx", 808);request.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy);HttpResponse response = client.execute(request);if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {String ret = EntityUtils.toString(response.getEntity());Log.d(TAG, "success.." + ret);}} catch (ClientProtocolException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}}}.start();}
方式四 基于Android Network实现
- 前述三种方式,如果没有指定特殊的代理,则最终使用的都是系统环境的默认代理。而Default Proxy可以直接通过System.setProperty来修改,这将导致Android无法进行维护。
- 因此Android框架层中维护了Global Proxy,当Global Proxy变更则同时更新Default Proxy。按照目前设计思路,Android应该希望每个应用均可以指定特定的网络进行请求,
且各个网络代理可以不同。因此Android的Network.java也提供了单独的接口进行网络连接。 - Android Network.java的代理实现设计也非常简单:
(1)直接使用openConnection(URL url, java.net.Proxy proxy)对某个请求指定特殊的代理。内部实现直接利用okhttp进行请求。
android/frameworks/base/core/java/android/net/Network.java
public URLConnection openConnection(URL url, java.net.Proxy proxy) throws IOException {if (proxy == null) throw new IllegalArgumentException("proxy is null");maybeInitHttpClient();String protocol = url.getProtocol();OkUrlFactory okUrlFactory;// TODO: HttpHandler creates OkUrlFactory instances that share the default ResponseCache.// Could this cause unexpected behavior?if (protocol.equals("http")) {okUrlFactory = HttpHandler.createHttpOkUrlFactory(proxy);} else if (protocol.equals("https")) {okUrlFactory = HttpsHandler.createHttpsOkUrlFactory(proxy);} else {// OkHttp only supports HTTP and HTTPS and returns a null URLStreamHandler if// passed another protocol.throw new MalformedURLException("Invalid URL or unrecognized protocol " + protocol);}OkHttpClient client = okUrlFactory.client();client.setSocketFactory(getSocketFactory()).setConnectionPool(mConnectionPool);// Let network traffic go via mDnsclient.setDns(mDns);return okUrlFactory.open(url);}
(2)使用openConnection(URL url),使用当前网络的代理(从实现来看,单个网络应该只支持设置代理域名和端口,不支持不进行代理网址的过滤,如果设置了全局代理,利用 Network.java的这个接口进行请求,同样不支持不进行代理网址的过滤)
android/frameworks/base/core/java/android/net/Network.java
public URLConnection openConnection(URL url) throws IOException {final ConnectivityManager cm = ConnectivityManager.getInstanceOrNull();if (cm == null) {throw new IOException("No ConnectivityManager yet constructed, please construct one");}/* note:此处我们可以看出,该实现没有完全确定,后续可能不支持全局代理替换单个Network代理。*/// TODO: Should this be optimized to avoid fetching the global proxy for every request?final ProxyInfo proxyInfo = cm.getProxyForNetwork(this);java.net.Proxy proxy = null;if (proxyInfo != null) {proxy = proxyInfo.makeProxy();} else {proxy = java.net.Proxy.NO_PROXY;}return openConnection(url, proxy);}
- 上述实现中,也能看到单个网络的请求也可指定特殊代理。如果不指定代理,则使用的是全局代理。此处存在差异是该全局代理却不支持不进行代理的网址的过滤。(可能是Android的bug,只要代理传null,其实就和其他的方式一样使用默认代理了)
Demo4 此处仅示范最简单的同步请求:
private void demoAndroidNetwork() {new Thread() {@Overridepublic void run() {try {URL url = new URL("http://192.168.xx.xx/cer/cert.pem");ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);Network defNet = cm.getActiveNetwork();Log.e(TAG, "defNet.." + defNet);HttpURLConnection connection = (HttpURLConnection) defNet.openConnection(url);connection.setConnectTimeout(5000);int code = connection.getResponseCode();if (code == 200) {InputStream inputStream = connection.getInputStream();String ret = inputStreamString(inputStream);Log.e(TAG, "success.." + ret);} else {Log.e(TAG, "fail..");}} catch (Exception e) {e.printStackTrace();Log.e(TAG, "e:" + e.getMessage());}}}.start();}
相关代码路径
模块 | 路径 | 说明 |
---|---|---|
Android framework |
android/frameworks/base/telecomm/java/android/telecom/ConnectionService.java android/frameworks/base/core/java/android/net/Network.java |
Global Proxy维护,以及提供单个Network的网络连接接口 |
ojluni | android/libcore/ojluni | OpenJDK Lang,Util,Net,IO库,默认代理选择器的实现在此 |
okhttp | android/external/okhttp | 框架层实际使用了okhttp进行http请求 |
apache-http | android/external/apache-http | 第三方应用可以引用Android系统中的apache-http,Android在apache-http中修改了代理的实现 |
遗留问题说明
- 上述方式四,也就是使用指定Network进行网络请求,在设置了全局代理的情况时,全局代理的不进行代理的域名列表将不生效。
该问题可能为Android的bug,Android后续也可能不会对在此接口应用全局代理。目前未做修改,如有必要,我认为修改为和其他方式一样,直接使用默认代理。 - 上述前三种方式,均存在默认代理被修改的风险(通过System.setProperty),此为Android系统整体的问题,后续可能SELINUX加强后,对这个属性进行保护,也许能进行规避。目前未做修改。
其他调试经验
- 获取global proxy信息
settings get global global_http_proxy_hostsettings get global global_http_proxy_portsettings get global global_http_proxy_exclusion_list
Android网络代理原理及实现相关推荐
- Android网络代理终极方案(适用于手机及电视盒子设备)
adb命令设置全局代理 adb shell settings put global http_proxy ip:port 如: adb shell settings put global http_p ...
- 基于Http协议的Android网络编程
之前写过一篇Android网络编程<浅谈android网络编程>,随着了解了更多Android知识,我意识到HttpClient已经不推荐使用了,更是在Android 6.0中被废弃了,原 ...
- Android网络编程(八) 之 HttpURLConnection原理分析
1 使用回顾 我们在前面博文<Android网络编程(四) 之 HttpClient与HttpURLConnection>中已经对HttpURLConnection的使用进行过介绍.今天我 ...
- Android网络编程(一)HTTP协议原理
相关文章 Android网络编程(一)HTTP协议原理 Android网络编程(二)HttpClient与HttpURLConnection Android网络编程(三)Volley用法全解析 And ...
- Android 插件化原理学习 —— Hook 机制之动态代理
前言 为了实现 App 的快速迭代更新,基于 H5 Hybrid 的解决方案有很多,由于 webview 本身的性能问题,也随之出现了很多基于 JS 引擎实现的原生渲染的方案,例如 React Nat ...
- android中多态的应用_动态代理原理及在 Android 中的应用
code小生 一个专注大前端领域的技术平台公众号回复Android加入安卓技术群 作者:trampcr 链接:https://www.jianshu.com/p/492903ab2fae 声明:本文已 ...
- 【Android 应用开发】Android 网络编程 API笔记 - java.net 包 权限 地址 套接字 相关类 简介
Android 网络编程相关的包 : 9 包, 20 接口, 103 类, 6 枚举, 14异常; -- Java包 : java.net 包 (6接口, 34类, 2枚举, 12异常); -- An ...
- Android插件化原理—ClassLoader加载机制
前面<Android 插件化原理学习 -- Hook 机制之动态代理>一文中我们探索了一下动态代理 hook 实现了 启动没有在 AndroidManifest.xml 中显式声明的 Ac ...
- 流量都去哪儿了——三板斧搞定Android网络流量测试
流量是什么? 为什么要进行流量测试? 流量测试方法有哪些? 怎么统计流量并进行结果分析? 带着这些疑问,小编带您探寻Android网络流量的测试方法. 1 流量是什么? 随着智能手机的普及,移动互联网 ...
最新文章
- Uva673 平衡的括号
- 双系统不能引导双系统
- htmlparser新建tag类(以iframe标签为例)
- [bootstrapValidator] - bootstrap的验证工具
- 【Python】SQLAlchemy:session何时commit,何时close?
- 并行编程——内存模型之缓存一致性
- .NETCore3.1中的Json互操作最全解读-收藏级
- 专业对不对口重要吗_应届生求职,专业对口到底重不重要?
- 使用阿基米德螺线进行数据可视化
- oracle12C 创建用户学习
- centos7磁盘备份和还原
- BCH码(BCH code)详细分析
- 软件测试场景 例子,软件测试基础(六)用例设计方法之场景法
- 《算法导论3rd第十二章》二叉查找树
- 单词前缀dia/dis/duo/en/epi/eu等衍生单词(辅助记忆)
- 笔记本如何解除锁定计算机,笔记本电脑数字小键盘如何解锁_笔记本电脑数字键盘被锁定了如何打开-win7之家...
- 2021年育婴员(五级)考试试卷及育婴员(五级)考试技巧
- 2020年国赛A题目思路(高教杯全国大学生数学建模竞赛)
- MIKE 21 教程 1.7 网格生成过程中的常见报错与问题
- 《Adobe Premiere Pro视频编辑指南(第2版)》——水银回放引擎
热门文章
- Large Division-----同余模定理(大数的求余)
- LINUX ROUTE命令详解-2
- [jzoj 4249] 【五校联考7day1】游戏 {贪心/斜率优化}
- android获取手机资源文件,android MediaStore 读取手机文件
- 错误解决方案:FATAL: Error inserting cmemk (/lib/modules/2.6.37/kernel/drivers/dsp/cmemk.ko): Bad address
- JS–for循环嵌套
- m277打印机 重置_惠普M277n说明书
- php天气预报小偷,帮朋友写的PHP天气预报小偷程序
- elasticSearch常见问题答疑
- 【XSY2500】都城(树形dp)