本文中含有需要您注意的重要提示信息,忽略该信息可能对您的业务造成影响,请务必仔细阅读。
本文档介绍了阿里云公共DNS Android/iOS SDK在Flutter场景下的接入方式。
概述
Flutter是谷歌的移动UI框架,可以快速在iOS和Android上构建高质量的原生用户界面。 Flutter可以与现有的代码一起工作。在全世界,Flutter正在被越来越多的开发者和组织使用,并且Flutter是完全免费、开源的。Flutter使用Dart语言开发,有单独的Dart虚拟机,Dart可以被编译(AOT)成不同平台的本地代码,让Flutter可以直接和平台通讯而不需要一个中间的桥接过程,可见Flutter是一个高效的移动端跨平台框架。在实际的项目开发中主要有以下2种实践场景:
普通场景
整个Flutter项目全部由Dart实现,写一份代码可以运行在Android和iOS平台
混合开发模式
整个Android/iOS项目是将Flutter作为module集成在项目中,通过MethodChannel实现两端的相互调用
由于Dart的底层HttpClient对外没有提供HTTPS的证书验证接口,通过Dart自带的HttpClient或使用第三方的Dio、Http网络框架,用户不能直接通过设置Host的IP直连方式进行HTTPS网络访问。因此要实现HTTPS的直连方案需要在本地搭建一个代理,在Socket建连的时候将用户访问的HOST替换成通过阿里云公共DNS SDK解析出的IP以实现HTTPS的IP直连访问。
Flutter场景下接入阿里云公共DNS Android、iOS端SDK实践完整代码请参考flutterDNSDemo源码。
实践方案
Flutter工程中Android端集成原生阿里云公共DNS SDK
在Android Studio中打开您的Flutter项目的Android部分:
启动 Android Studio,选择 File > Open…
定位到您 Flutter app目录, 然后选择里面的android文件夹,点击OK。在Java目录下打开MainActivity.java
在Application里集成阿里云公共 DNS Android SDK:
如何集成阿里云公共 DNS Android SDK请参考Android SDK开发指南文档。
在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
在Xcode中打开您的Flutter项目的iOS工程:
在Flutter项目中找到iOS目录下的Xcode工程文件,使用Xcode打开。
在Xcode工程里集成阿里云公共 DNS iOS SDK:
如何集成阿里云公共 DNS iOS SDK请参考iOS SDK开发指南文档。
在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以实现HTTPS的IP直连访问
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); } }
当前文档只针对Flutter场景下阿里云公共DNS Android/iOS SDK的使用。
关于阿里云公共DNS Android/iOS SDK的接入与使用问题,请查看Android SDK开发指南、iOS SDK开发指南。
开发者在Flutter场景下接入阿里云公共DNS Android/iOS SDK实践完整代码请参考flutterDNSDemo源码。