Android端WEEX + HTTPDNS 最佳实践

由于WebView并未暴露处设置DNS的接口,因而在WebView场景下使用HttpDns存在很多无法限制,但如果接入WEEX,则可以较好地植入HTTPDNS,本文主要介绍在WEEX场景下接入HTTPDNS的方案细节。

WEEX运行时环境下,所有的逻辑最终都会转换到Native Runtime中执行,网络请求也不例外。同时WEEX也提供了自定义相应实现的接口,通过重写网络请求适配器,我们可以较为简单地接入HTTPDNS。在WEEX运行环境中,主要有两种网络请求:

  • 通过Stream进行的网络请求
  • 标签指定的加载图片的网络请求

1 Stream网络请求 + HTTPDNS

Stream网络请求在Android端最终会通过DefaultWXHttpAdapter完成,同时WEEX也提供了相应的接口自定义网络请求适配器。具体的逻辑如下:

第一步:创建自定义网络请求适配器,实现IWXHttpAdapter接口

public class WXHttpdnsAdatper implements IWXHttpAdapter {
    @Override
    public void sendRequest(final WXRequest request, final OnHttpListener listener) {
      ......
    }
}

该接口需要实现sendRequest方法,该方法传入一个WXRequest对象的实例,该对象提供了以下信息:

public class WXRequest {
  // The request parameter
  public Map<String, String> paramMap;
  // The request URL
  public String url;
  // The request method
  public String method;
  // The request body
  public String body;
  // The request time out
  public int timeoutMs = WXRequest.DEFAULT_TIMEOUT_MS;
  // The default timeout
  public static final int DEFAULT_TIMEOUT_MS = 3000;
}

通过该对象我们可以获取到请求报头、URL、方法以及body。

第二步:在WEEX初始化时注册自定义网络适配器,替换默认适配器:

InitConfig config=new InitConfig.Builder()
                .setHttpAdapter(new WXHttpdnsAdatper()) // 注册自定义网络请求适配器
                ......
                .build();
        WXSDKEngine.initialize(this,config);

之后左右的网络请求都会通过WXHttpdnsAdatper实现,所以只需要在WXHttpdnsAdapter中植入HTTPDNS逻辑即可,具体逻辑可以参考如下代码:

public class WXHttpdnsAdatper implements IWXHttpAdapter {
  ......
    private void execute(Runnable runnable){
        if(mExecutorService==null){
            mExecutorService = Executors.newFixedThreadPool(3);
        }
        mExecutorService.execute(runnable);
    }

    @Override
    public void sendRequest(final WXRequest request, final OnHttpListener listener) {
        if (listener != null) {
            listener.onHttpStart();
        }

        Log.e(TAG, "URL:" + request.url);
        execute(new Runnable() {
            @Override
            public void run() {
                WXResponse response = new WXResponse();
                WXHttpdnsAdatper.IEventReporterDelegate reporter = getEventReporterDelegate();
                try {
                    // 创建连接
                    HttpURLConnection connection = openConnection(request, listener);
                    reporter.preConnect(connection, request.body);
                  ......
                } catch (IOException |IllegalArgumentException e) {
                  ......
                }
            }
        });
    }

    private HttpURLConnection openConnection(WXRequest request, OnHttpListener listener) throws IOException {
        URL url = new URL(request.url);
        // 创建一个植入HTTPDNS的连接
        HttpURLConnection connection = openHttpDnsConnection(request, request.url, listener, null);
        return connection;
    }

    private HttpURLConnection openHttpDnsConnection(WXRequest request, String path, OnHttpListener listener, String reffer) {
        HttpURLConnection conn = null;
        URL url = null;
        try {
            url = new URL(path);
            conn = (HttpURLConnection) url.openConnection();
           // 创建一个接入httpdns的连接
            HttpURLConnection tmpConn = httpDnsConnection(url, path);
          ......

            int code = conn.getResponseCode();// Network block
            Log.e(TAG, "code:" + code);
            // SNI场景下通常涉及重定向,重新建立新连接
            if (needRedirect(code)) {
                Log.e(TAG, "need redirect");
                String location = conn.getHeaderField("Location");
                if (location == null) {
                    location = conn.getHeaderField("location");
                }

                if (location != null) {
                    if (!(location.startsWith("http://") || location
                            .startsWith("https://"))) {
                        //某些时候会省略host,只返回后面的path,所以需要补全url
                        URL originalUrl = new URL(path);
                        location = originalUrl.getProtocol() + "://"
                                + originalUrl.getHost() + location;
                    }
                    Log.e(TAG, "code:" + code + "; location:" + location + "; path" + path);
                    return openHttpDnsConnection(request, location, listener, path);
                } else {
                    return conn;
                }
            } else {
                // redirect finish.
                Log.e(TAG, "redirect finish");
                return conn;
            }
        }
        ......
        return conn;
    }

    private HttpURLConnection httpDnsConnection(URL url, String path) {
        HttpURLConnection conn = null;
        // 通过HTTPDNS SDK接口获取IP
        String ip = HttpDnsManager.getInstance().getHttpDnsService().getIpByHostAsync(url.getHost());
        if (ip != null) {
            // 通过HTTPDNS获取IP成功,进行URL替换和HOST头设置
            Log.d(TAG, "Get IP: " + ip + " for host: " + url.getHost() + " from HTTPDNS successfully!");
            String newUrl = path.replaceFirst(url.getHost(), ip);
            try {
                conn = (HttpURLConnection) new URL(newUrl).openConnection();
            } catch (IOException e) {
                return null;
            }

            // 设置HTTP请求头Host域
            conn.setRequestProperty("Host", url.getHost());

            // HTTPS场景
            if (conn instanceof HttpsURLConnection) {
                final HttpsURLConnection httpsURLConnection = (HttpsURLConnection)conn;
                WXTlsSniSocketFactory sslSocketFactory = new WXTlsSniSocketFactory((HttpsURLConnection) conn);

                // SNI场景,创建SSLScocket解决SNI场景下的证书问题
                conn.setInstanceFollowRedirects(false);
                httpsURLConnection.setSSLSocketFactory(sslSocketFactory);
                // HTTPS场景,证书校验
                httpsURLConnection.setHostnameVerifier(new HostnameVerifier() {
                    @Override
                    public boolean verify(String hostname, SSLSession session) {
                        String host = httpsURLConnection.getRequestProperty("Host");
                        Log.e(TAG, "verify host:" + host);
                        if (null == host) {
                            host = httpsURLConnection.getURL().getHost();
                        }
                        return HttpsURLConnection.getDefaultHostnameVerifier().verify(host, session);
                    }
                });
            }
        } else {
            Log.e(TAG, "no corresponding ip found, return null");
            return null;
        }

        return conn;
    }
}

1.2 <image>网络请求 + HTTPDNS

WEEX并没有提供默认的图片适配器实现,所以用户必须自行实现才能完成图片请求逻辑,具体步骤分为以下几步:

第一步:自定义图片请求适配器,实现IWXImgLoaderAdapter接口

public class HttpDnsImageAdapter implements IWXImgLoaderAdapter {
    @Override
    public void setImage(final String url, final ImageView view, WXImageQuality quality, final WXImageStrategy strategy) {
      ......
    }
}

第二步:在WEEX初始化时注册该图片适配器:

    private void initWeex() {
        InitConfig config=new InitConfig.Builder()
                .setImgAdapter(new HttpDnsImageAdapter())
                .build();
        WXSDKEngine.initialize(this,config);
        ......
    }

所以同WXHttpdnsAdatper一样,我们只需在HttpDnsImageAdapter植入HTTPDNS逻辑即可。具体代码可参考:

/**
 * Created by liyazhou on 2017/10/22.
 */

public class WXHttpDnsImageAdapter implements IWXImgLoaderAdapter {

    @Override
    public void setImage(final String url, final ImageView view, WXImageQuality quality, final WXImageStrategy strategy) {
        Log.e(TAG, "img url:" + url);
        execute(new Runnable() {
            @Override
            public void run() {
              ......
                HttpURLConnection conn = null;
                try {
                     conn = createConnection(url);
                     ....
                     // 将得到的数据转化成InputStream
                     InputStream is = conn.getInputStream();
                     // 将InputStream转换成Bitmap
                     final Bitmap bitmap = BitmapFactory.decodeStream(is);
                     WXSDKManager.getInstance().postOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            view.setImageBitmap(bitmap);
                        }
                     }, 0);
                      ......
            }
        });
    }

    protected HttpURLConnection createConnection(String originalUrl) throws IOException {
        mHttpDnsService = HttpDnsManager.getInstance().getHttpDnsService();
        if (mHttpDnsService == null) {
            URL url = new URL(originalUrl);
            return (HttpURLConnection) url.openConnection();
        } else {
            return httpDnsRequest(originalUrl, null);
        }
    }

    private HttpURLConnection httpDnsRequest(String path, String reffer) {
        HttpURLConnection httpDnsConn = null;
        HttpURLConnection originalConn = null;
        URL url = null;
        try {
            url = new URL(path);
            originalConn = (HttpURLConnection) url.openConnection();
            // 异步接口获取IP
            String ip = HttpDnsManager.getInstance().getHttpDnsService().getIpByHostAsync(url.getHost());
            if (ip != null) {
                // 通过HTTPDNS获取IP成功,进行URL替换和HOST头设置
                Log.d(TAG, "Get IP: " + ip + " for host: " + url.getHost() + " from HTTPDNS successfully!");
                String newUrl = path.replaceFirst(url.getHost(), ip);
                httpDnsConn = (HttpURLConnection) new URL(newUrl).openConnection();

                // 设置HTTP请求头Host域
                httpDnsConn.setRequestProperty("Host", url.getHost());

                // HTTPS场景
                if (httpDnsConn instanceof HttpsURLConnection) {
                    final HttpsURLConnection httpsURLConnection = (HttpsURLConnection)httpDnsConn;
                    WXTlsSniSocketFactory sslSocketFactory = new WXTlsSniSocketFactory((HttpsURLConnection) httpDnsConn);

                    // sni场景,创建SSLScocket解决SNI场景下的证书问题
                    httpsURLConnection.setSSLSocketFactory(sslSocketFactory);
                    // https场景,证书校验
                    httpsURLConnection.setHostnameVerifier(new HostnameVerifier() {
                        @Override
                        public boolean verify(String hostname, SSLSession session) {
                            String host = httpsURLConnection.getRequestProperty("Host");
                            if (null == host) {
                                host = httpsURLConnection.getURL().getHost();
                            }
                            return HttpsURLConnection.getDefaultHostnameVerifier().verify(host, session);
                        }
                    });
                }
            } else {
                return originalConn;
            }

          ......

            int code = httpDnsConn.getResponseCode();// Network block
            if (needRedirect(code)) {
                String location = httpDnsConn.getHeaderField("Location");
                if (location == null) {
                    location = httpDnsConn.getHeaderField("location");
                }

                if (location != null) {
                    if (!(location.startsWith("http://") || location
                            .startsWith("https://"))) {
                        //某些时候会省略host,只返回后面的path,所以需要补全url
                        URL originalUrl = new URL(path);
                        location = originalUrl.getProtocol() + "://"
                                + originalUrl.getHost() + location;
                    }
                    Log.e(TAG, "code:" + code + "; location:" + location + "; path" + path);
                    return httpDnsRequest(location, path);
                } else {
                    return originalConn;
                }
            }
        return originalConn;
    }
}

上述方案详细代码建:WeexAndroid

时间: 2024-09-07 20:01:03

Android端WEEX + HTTPDNS 最佳实践的相关文章

Android应用性能优化最佳实践.

移动开发 Android应用性能优化最佳实践 罗彧成 著 图书在版编目(CIP)数据 Android应用性能优化最佳实践 / 罗彧成著. -北京:机械工业出版社,2017.1 (移动开发) ISBN 978-7-111-55616-9 I. A- II. 罗- III. 移动终端-应用程序-程序设计 IV. TN929.53 中国版本图书馆CIP数据核字(2016)第315986号 Android应用性能优化最佳实践 出版发行:机械工业出版社(北京市西城区百万庄大街22号 邮政编码:100037

HttpClient/HttpURLConnection + HttpDns最佳实践

在Android端如果OkHttp作为网络请求框架,由于其提供了自定义DNS服务接口,可以很优雅地结合HttpDns,相关实现可参考:HttpDns+OkHttp最佳实践. 如果您使用HttpClient或HttpURLConnection发起网络请求,尽管无法直接自定义Dns服务,但是由于HttpClient和HttpURLConnection也通过InetAddress进行域名解析,通过修改InetAddress的DNS缓存,同样可以比通用方案更为优雅地使用HttpDns. InetAddr

《Android和PHP开发最佳实践》一2.7 Android应用界面

2.7 Android应用界面 Android应用界面系统,即Android UI(User Interface)系统是Android应用框架最核心的内容之一,也是开发者们需要重点掌握的内容.如果我们把Android应用也分为前后端两部分的话,那么之前介绍的核心要点和四大组件等都属于后端,而Android UI系统则属于前端.后端保证应用的稳定运行,而前端则决定应用的外观和体验.对于一个优秀的Android应用来说,漂亮的外观和流畅的体验是必不可少的.接下来,我们便来学习Android外观系统的

《Android和PHP开发最佳实践》一1.3 如何学习Android和PHP

1.3 如何学习Android和PHP 前面我们已经讨论过"为何学"的问题,大家应该对Android加PHP这套应用开发解决方案有了大致的了解.接下来介绍"如何学"的问题,由于本书的内容比较广泛,既涉及客户端开发的技术也包含很多服务端开发的内容,所以在正式开始学习本书之前,先搞清楚应该使用什么样的学习方法比较有效是非常有必要的.接下来,笔者会把这个问题分解为以下几个部分来探讨. 1.3.1 如何学习Android 由于Android学习是本书最核心的内容,因此我们先

Android应用性能优化最佳实践.2.1 Android系统显示原理

绘?制?优?化 Android应用启动慢,使用时经常卡顿,是非常影响用户体验的,应该尽量避免出现.卡顿的场景有很多,按场景可以分成4类:UI绘制.应用启动.页面跳转.事件响应,如 图2-1所示.在这四种场景下又有多个小分类,基本上覆盖了卡顿的各个场景.   图2-1 卡顿主要场景 这4种卡顿场景的根本原因又可以分成两大类. 界面绘制:主要原因是绘制的层级深.页面复杂.刷新不合理,由于这些原因导致卡顿的场景更多出现在UI和启动后的初始界面以及跳转到页面的绘制上. 数据处理:导致这种卡顿场景的原因是

《Android和PHP开发最佳实践 》一3.6 开发框架简介

3.6 开发框架简介 前面大家已经学习了PHP模板引擎Smarty的用法,也简单了解了PHP的官方框架Zend Framework,接下来本书将给大家介绍一个基于Zend Framework和Smarty之上的强大的PHP开发框架,即Hush Framework.本书后面微博实例的服务端程序也将采用该框架进行开发.在实际项目中,我们通常要先选择一个比较适合项目特点的框架,然后,在这个框架的基础上进行开发,这个过程我们通常称为"框架选型".其实,在之前的3.4节中我们已经介绍和分析了四种

《Android和PHP开发最佳实践》一2.6 Android数据存储

2.6 Android数据存储 前面刚介绍过上下文对象的使用,其最重要的功能之一,就是用于存储应用运行期间产生的中间数据.接下来,我们来讨论Android应用中持久化类型数据的存储方案.对于移动互联网应用来说,我们经常把核心数据存储在服务端,也就是我们常说的"云端",但是在实际项目中也会经常使用到Android系统内部的数据存储方案,接下来让我们认识一下几种最常用的数据存储方案. 2.6.1 应用配置(Shared Preferences) 在Android系统中,系统配置(Share

《Android和PHP开发最佳实践》一3.1 PHP开发基础

3.1 PHP开发基础 编写本章之前,笔者在考虑一个问题,那就是"如何把一本书的内容压缩到短短的一章中".这确实是一个难题!但是,虽然篇幅有限,本书还是会尽量使用最简洁明了的语言和最易于理解的实例,来帮助大家以最快的速度认识和了解PHP的开发. 3.1.1 PHP语言简介 PHP(Hypertext Preprocessor)是目前最流行的服务端脚本语言之一.近年来,随着互联网的飞速发展,使用PHP语言进行互联网应用开发也变得逐渐火热起来,其特点是简单.快速.灵活,主要应用于各大门户网

《Android和PHP开发最佳实践》一2.10 Android开发环境

2.10 Android开发环境 前面我们已经学习了Android系统中最重要的基础概念的内容,那么接下来就要开始正式进入Android应用的实战开发阶段."工欲善其事,必先利其器",因此,我们先来熟悉Android应用的开发环境吧. Android应用的开发环境是基于Eclipse平台的,Eclipse的强大无需多说,它当然也适应于Windows XP.Mac OS.Linux等多种操作系统.另外,我们还需要安装一些必备的开发工具包,所需要的软件见表2-8. 2.10.1 开发环境的