Flutter接入阿里云公共DNS Android/iOS SDK实践方案

重要

本文中含有需要您注意的重要提示信息,忽略该信息可能对您的业务造成影响,请务必仔细阅读。

本文档介绍了阿里云公共DNS Android/iOS SDKFlutter场景下的接入方式。

概述

Flutter是谷歌的移动UI框架,可以快速在iOSAndroid上构建高质量的原生用户界面。 Flutter可以与现有的代码一起工作。在全世界,Flutter正在被越来越多的开发者和组织使用,并且Flutter是完全免费、开源的。Flutter使用Dart语言开发,有单独的Dart虚拟机,Dart可以被编译(AOT)成不同平台的本地代码,让Flutter可以直接和平台通讯而不需要一个中间的桥接过程,可见Flutter是一个高效的移动端跨平台框架。在实际的项目开发中主要有以下2种实践场景:

  • 普通场景

整个Flutter项目全部由Dart实现,写一份代码可以运行在AndroidiOS平台

  • 混合开发模式

整个Android/iOS项目是将Flutter作为module集成在项目中,通过MethodChannel实现两端的相互调用

警告

由于Dart的底层HttpClient对外没有提供HTTPS的证书验证接口,通过Dart自带的HttpClient或使用第三方的Dio、Http网络框架,用户不能直接通过设置HostIP直连方式进行HTTPS网络访问。因此要实现HTTPS的直连方案需要在本地搭建一个代理,在Socket建连的时候将用户访问的HOST替换成通过阿里云公共DNS SDK解析出的IP以实现HTTPSIP直连访问。

Flutter场景下接入阿里云公共DNS Android、iOSSDK实践完整代码请参考flutterDNSDemo源码

实践方案

  • Flutter工程中Android端集成原生阿里云公共DNS SDK

  1. Android Studio中打开您的Flutter项目的Android部分:

    启动 Android Studio,选择 File > Open…

    定位到您 Flutter app目录, 然后选择里面的android文件夹,点击OK。在Java目录下打开MainActivity.java

  2. Application里集成阿里云公共 DNS Android SDK:

    如何集成阿里云公共 DNS Android SDK请参考Android SDK开发指南文档。

  3. Android目录中定义和Flutter通信的MethodChannel: 需要注意的是通道的名字要跟flutter的通道名称保持一致

public class MainActivity extends FlutterActivity {

    //这个跟flutter的通道名称保持一直
    private static final String CHANNEL = "samples.flutter.io/getIP";
    //定义flutter端调用Android原生方法的channelMethod
    private static final String channelMethod = "getIP";
    //flutter对Android端的传参
    private static final String hostArgument = "host";
    //初始化阿里云公共DNS Android SDK中提供的DNSResolver对象
    private DNSResolver dnsResolver = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //初始化DNSResolver
        dnsResolver = DNSResolver.getInstance();

        new MethodChannel(getFlutterView(), CHANNEL).setMethodCallHandler(
                new MethodChannel.MethodCallHandler() {
                    @Override
                    public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) {
                        //这里是判断是不是调getIP方法,因为所有的方法都走这里
                        if (methodCall.method.equals(channelMethod)) {
                            if (methodCall.hasArgument(hostArgument)) {
                                try{
                                    //取出从flutter端传递的host参数
                                    String resolverName = methodCall.argument(hostArgument);
                                    String ip = dnsResolver.getIPV4ByHost(resolverName);
                                    if (ip != null) {
                                        result.success(ip);
                                        Toast.makeText(MainActivity.this, "调用Android原生域名解析成功!", Toast.LENGTH_SHORT).show();
                                    } else {
                                        result.success(resolverName);
                                    }
                                }catch (Exception e){
                                    e.printStackTrace();
                                }
                            }
                        }
                    }
                }
        );
    }
}
  • Flutter工程中iOS端集成原生阿里云公共DNS SDK

  1. Xcode中打开您的Flutter项目的iOS工程:

    Flutter项目中找到iOS目录下的Xcode工程文件,使用Xcode打开。

  2. Xcode工程里集成阿里云公共 DNS iOS SDK:

    如何集成阿里云公共 DNS iOS SDK请参考iOS SDK开发指南文档。

  3. iOS中定义和Flutter通信的MethodChannel:

    FlutterViewController* controller = (FlutterViewController*)self.window.rootViewController;
    FlutterMethodChannel* getIPChannel = [FlutterMethodChannel
                                                methodChannelWithName:@"samples.flutter.io/getIP"
                                                binaryMessenger:controller];//跟flutter的通道名称保持一直
    [getIPChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
            if ([@"getIP" isEqualToString:call.method]) {
                NSDictionary *argumentDic = call.arguments;
                result([self getIPWithHostName:argumentDic[@"host"]]); // 解析结果
            } else {
                result(FlutterMethodNotImplemented);
            }
    }];

    用户自定义获取域名解析结果IP的方法

    - (NSString *)getIPWithHostName:(NSString *)hostName {//建议用户自行增加相应的容错处理,如果没有IP那么返回域名保证程序正常运行
        NSArray *array = [[DNSResolver share] getIpv4ByCacheWithDomain:hostName andExpiredIPEnabled:YES];
        if (array != NULL && array.count > 0) {
            return array[0];
        }else{
            return hostName;
        }
    }
  • Flutter工程中通过methodChannel和自定义方法获取域名解析结果

    static const platform = const MethodChannel('samples.flutter.io/getIP');
    final String result = await platform.invokeMethod('getIP', {'host': '您要解析的域名'});
  • Flutter工程中HTTPS网络请求使用IP直连

    void runPoxyRequest() async {
        var proxy = CustomHttpsProxy();
        final result = await proxy.init();
        print('proxy established: ${result != null}');
        await doHttpGet();
        proxy.close();
    }
    Future doHttpGet() async{
      var httpClient= HttpClient();
      httpClient.findProxy= (uri) => 'PROXY localhost:4041';
      var request= await httpClient.getUrl(Uri.parse('https://www.taobao.com'));
      var response= await request.close();
      //读取响应内容
      var responseBody= await response.transform(Utf8Decoder()).join();
      print(responseBody);
    }

    Socket建连的时候将用户访问的HOST替换成IP以实现HTTPSIP直连访问

    import 'dart:io';
    import 'dart:convert';
    
    import 'package:flutter/services.dart';
    
    class CustomHttpsProxy {
      final int port = 4041;
      ServerSocket serverSocket;
    
      CustomHttpsProxy();
    
      Future init() async {
        await ServerSocket.bind(InternetAddress.anyIPv4, port).then((serverSocket) {
          this.serverSocket = serverSocket;
          serverSocket.listen((client) {
            try {
              ClientConnectionHandler(client).handle();
            } catch (e) {
              print('ClientConnectionHandler exception $e');
            }
          });
        }).catchError((e) {
          print('serverSocket 处理异常$e');
        });
        return serverSocket;
      }
    
      void close() {
        if (serverSocket != null) {
          serverSocket.close();
        }
      }
    }
    
    class ClientConnectionHandler {
      final RegExp regx = RegExp(r'CONNECT ([^ :]+)(?::([0-9]+))? HTTP/1.1\r\n');
      static const platform = const MethodChannel('samples.flutter.io/getIP');
      Socket server;
      Socket client;
      String content = '';
      String host;
      int port;
    
      ClientConnectionHandler(this.client);
    
      void closeSockets() {
    //    print('socket is going to destroy');
        if (server != null) {
          server.destroy();
        }
        client.destroy();
      }
    
      Future<void> dataHandler(data) async {
        if (server == null) {
          content += utf8.decode(data);
          final m = regx.firstMatch(content);
          if (m != null) {
            host = m.group(1);
            port = m.group(2) == null ? 443 : int.parse(m.group(2));
            final String resultIP = await platform.invokeMethod(
                'getIP', {'host': host});
            final realHost = resultIP != null? resultIP : host;
            print('~~~~~'+resultIP);
            try {
              ServerConnectionHandler(realHost, port, this)
                  .handle()
                  .catchError((e) {
                print('Server error $e');
                closeSockets();
              });
            } catch (e) {
              print('Server exception $e');
              closeSockets();
            }
          }
        } else {
          try {
            server.add(data);
          } catch (e) {
            print('sever has been shut down');
            closeSockets();
          }
        }
      }
    
      void errorHandler(error, StackTrace trace) {
        print('client socket error: $error');
      }
    
      void doneHandler() {
        closeSockets();
      }
    
      void handle() {
        client.listen(dataHandler,
            onError: errorHandler, onDone: doneHandler, cancelOnError: true);
      }
    }
    
    class ServerConnectionHandler {
      final String RESPONSE = 'HTTP/1.1 200 Connection Established\r\n\r\n';
      final String host;
      final int port;
      final ClientConnectionHandler handler;
      Socket server;
      Socket client;
      String content = '';
    
      ServerConnectionHandler(this.host, this.port, this.handler) {
        client = handler.client;
      }
    
      //接收报文
      void dataHandler(data) {
        try {
          client.add(data);
        } on Exception catch (e) {
          print('client has been shut down $e');
          handler.closeSockets();
        }
      }
    
      void errorHandler(error, StackTrace trace) {
        print('server socket error: $error');
      }
    
      void doneHandler() {
        handler.closeSockets();
      }
    
      Future handle() async {
        print('尝试建立连接: $host:$port');
        server = await Socket.connect(host, port, timeout: Duration(seconds: 60));
        server.listen(dataHandler,
            onError: errorHandler, onDone: doneHandler, cancelOnError: true);
        handler.server = server;
        client.write(RESPONSE);
      }
    }
说明
  1. 当前文档只针对Flutter场景下阿里云公共DNS Android/iOS SDK的使用。

  2. 关于阿里云公共DNS Android/iOS SDK的接入与使用问题,请查看Android SDK开发指南iOS SDK开发指南

  3. 开发者在Flutter场景下接入阿里云公共DNS Android/iOS SDK实践完整代码请参考flutterDNSDemo源码