Android端WEEX + HTTPDNS 最佳实践

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 由于`WebView`并未暴露处设置DNS的接口,因而在`WebView`场景下使用`HttpDns`存在很多无法限制,但如果接入`WEEX`,则可以较好地植入`HTTPDNS`,本文主要介绍在`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

相关文章
|
2月前
|
移动开发 监控 Android开发
Android & iOS 使用 ARMS 用户体验监控(RUM)的最佳实践
本文主要介绍了 ARMS 用户体验监控的基本功能特性,并介绍了在几种常见场景下的最佳实践。
370 14
|
7月前
|
存储 移动开发 数据库
构建高效Android应用:探究LiveData和ViewModel的最佳实践
【4月更文挑战第20天】 在动态演化的移动开发领域,构建一个既响应迅速又能够在用户界面保持稳定的Android应用是至关重要的。近年来,随着Android架构组件的推出,特别是LiveData和ViewModel的引入,开发者得以更有效地管理应用状态并优化用户界面的响应性。本文将深入探讨LiveData和ViewModel的实现机制,并通过案例分析展示如何结合它们来构建一个高效且健壮的Android应用架构。我们将重点讨论如何通过这些组件简化数据绑定过程、提高代码的可维护性和测试性,同时确保用户界面的流畅性。
|
6月前
|
XML API 网络安全
【安卓】在安卓中使用HTTP协议的最佳实践
【安卓】在安卓中使用HTTP协议的最佳实践
148 4
|
6月前
|
设计模式 安全 前端开发
探索Android应用开发的最佳实践
【6月更文挑战第19天】在这篇文章中,我们将深入探讨Android应用开发的最佳实践。从设计模式的选择到性能优化的技巧,我们将一一解析如何构建高效、可维护且用户友好的Android应用。无论你是新手还是经验丰富的开发者,这篇文章都将为你提供有价值的见解和实用的建议。让我们一起探索Android应用开发的奥秘吧!
84 4
|
6月前
|
安全 开发工具 Android开发
探索Android应用开发的最佳实践
在这篇文章中,我们将深入探讨开发高效、稳定且用户友好的Android应用所需的关键实践。通过分析最新的开发工具和技术,本文旨在为开发者提供实用的建议和策略,以提升他们的应用性能和用户体验。
|
7月前
|
安全 Android开发 数据安全/隐私保护
Android中的动态权限请求与最佳实践
【4月更文挑战第14天】 在现代安卓应用开发中,用户隐私和安全被赋予了前所未有的重要性。随着Android 6.0(API级别23)引入的运行时权限模型,开发者必须更加细致地处理权限请求,以确保应用功能的完整性同时不侵犯用户的隐私。本文将深入探讨如何在Android应用中实现动态权限请求,分析常见问题,并提供一系列最佳实践,以帮助开发者优雅地处理这一挑战。
450 5
|
7月前
|
数据库 Android开发 开发者
实现高效安卓应用:探究LiveData和ViewModel的最佳实践
【4月更文挑战第19天】 在构建响应式的Android应用程序时,LiveData和ViewModel是两个核心组件。它们不仅提供了数据持有和界面更新的机制,还促进了组件间的解耦。本文将深入探讨如何通过结合LiveData和ViewModel来优化应用架构,提升用户体验,并确保数据的一致性和生存期管理。我们将透过实际案例分析,揭示这些技术如何协同工作以应对复杂的UI场景,并展示如何在实际项目中实施这些最佳实践。
|
7月前
|
移动开发 调度 Android开发
构建高效Android应用:探究Kotlin协程的最佳实践
在移动开发领域,性能优化和资源管理是至关重要的。特别是对于Android开发者来说,合理利用Kotlin协程可以大幅提升应用的响应性和用户体验。本文将深入探讨Kotlin协程的核心概念、优势以及它们在Android项目中的实际应用。我们还将通过代码示例来揭示如何有效地使用协程进行后台任务处理,避免阻塞UI线程,并确保内存的有效管理。文章的目标是为读者提供一套实用的指南,帮助他们在Android开发中充分利用Kotlin协程的强大功能。
93 1
|
缓存 Dart 监控
现代化 Android 开发:Jetpack Compose 最佳实践
如果一直关注 `Compose` 的发展的话,可以明显感受到 2022 年和 2023 年的 `Compose` 使用讨论的声音已经完全不一样了, 2022 年还多是观望,2023 年就有很多团队开始采纳 `Compose` 来进行开发了。不过也有很多同学接触了下 `Compose`,然后就放弃了。要么使用起来贼特么不顺手,要么就是感觉性能不行,卡。其实,问题只是大家的思维没有转换过来,还不会写 `Compose`。
428 1
|
消息中间件 存储 运维
钉钉 Android 端功耗优化最佳实践(中)
钉钉 Android 端功耗优化最佳实践(中)
383 1