本文中含有需要您注意的重要提示信息,忽略该信息可能对您的业务造成影响,请务必仔细阅读。
本文档介绍了在Flutter场景下移动解析HTTPDNS Android/iOS SDK的接入方式。
概述
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替换成通过移动解析HTTPDNS SDK解析出的IP以实现HTTPS的IP直连访问。
Flutter场景下接入Android、iOS端SDK实践的完整代码请参考flutterDNSDemo源码。
实践方案
Flutter工程中Android端集成原生移动解析HTTPDNS SDK
在Android Studio中打开您的Flutter项目的Android部分:
启动 Android Studio,选择 File > Open…
定位到您 Flutter app目录, 然后选择里面的android文件夹,点击OK。在Java目录下打开MainActivity.java
在Application里集成移动解析HTTPDNS DNS Android SDK:
如何集成移动解析HTTPDNS DNS Android SDK请参考Android SDK开发指南文档。
在Android目录中定义和Flutter通信的MethodChannel: 需要注意的是通道的名字要跟flutter的通道名称保持一致
package com.example.flutter;
import android.widget.Toast;
import com.alibaba.pdns.DNSResolver;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result;
import io.flutter.embedding.android.FlutterActivity;
import android.content.Context;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import android.util.Log; // 导入Log类
public class MainActivity extends FlutterActivity {
/**定义一个TAG用于日志标识**/
private static final String TAG = "MainActivity";
private MethodChannel channel;
private Context context;
/**这个跟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;
/**创建一个固定大小的线程池**/
private ExecutorService executor = Executors.newFixedThreadPool(5);
@Override
public void configureFlutterEngine(FlutterEngine flutterEngine) {
super.configureFlutterEngine(flutterEngine);
//初始化DNSResolver
dnsResolver = DNSResolver.getInstance();
channel = new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL);
channel.setMethodCallHandler(new MethodCallHandler() {
@Override
public void onMethodCall(MethodCall call, Result result) {
//这里是判断是不是调getIP方法,因为所有的方法都走这里
if (call.method.equals(channelMethod)) {
if (call.hasArgument(hostArgument)) {
String resolverName = call.argument(hostArgument);
Log.d(TAG, "resolverName: " + resolverName); // 使用Log.d打印日志
executor.execute(() -> {
try {
String[] IPArray = dnsResolver.getIpv4ByHostFromCache(resolverName,true);
String ip = null;
if (IPArray==null || IPArray.length==0){
ip = dnsResolver.getIPV4ByHost(resolverName);
}else {
ip = IPArray[0];
}
String finalIp = ip;
runOnUiThread(() -> {
if (finalIp != null) {
Log.d(TAG, "DNS_RESOLUTION_SUCCESS: " + resolverName + "==" + finalIp); // 使用Log.d打印日志
result.success(finalIp);
Toast.makeText(MainActivity.this, "调用Android原生域名解析成功!", Toast.LENGTH_SHORT).show();
} else {
Log.e(TAG, "Error resolving IP address"); // 使用Log.e打印错误日志
result.error("DNS_RESOLUTION_FAILED", "Failed to resolve IP address", null);
}
});
} catch (Exception e) {
e.printStackTrace();
Log.e(TAG, "Error resolving IP address", e); // 使用Log.e打印错误日志
runOnUiThread(() -> result.error("DNS_RESOLUTION_FAILED", e.getMessage(), null));
}
});
}
}
}
});
}
}Flutter工程中iOS端集成原生移动解析HTTPDNS SDK
在Xcode中打开您的Flutter项目的iOS工程:
在Flutter项目中找到iOS目录下的Xcode工程文件,使用Xcode打开。
在Xcode工程里集成移动解析HTTPDNS iOS SDK:
如何集成移动解析HTTPDNS 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 *dic = call.arguments; NSString *hostName = dic[@"host"]; NSLog(@"hostName==%@",hostName); NSArray *arr = [[DNSResolver share] getIpv4ByCacheWithDomain:hostName andExpiredIPEnabled:YES]; if (arr != nil && arr.count > 0) { result(arr[0]); }else { [[DNSResolver share] getIpv4DataWithDomain:hostName complete:^(NSArray<NSString *> *dataArray) { NSLog(@"array==%@", dataArray); if (dataArray != nil && dataArray.count > 0) { result(dataArray[0]); } else { // 处理解析失败的情况,返回原来的 hostName result(hostName); } }]; } } else { result(FlutterMethodNotImplemented); } }];
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 = 443; 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) ?? 'unknown'; port = int.tryParse(m.group(2) ?? '') ?? 443; final String? resultIP = await platform.invokeMethod('getIP', {'host': host}); final String realHost = 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场景下移动解析HTTPDNS Android/iOS SDK的使用。
开发者在Flutter场景下接入移动解析HTTPDNS Android/iOS SDK实践完整代码请参考flutterDNSDemo源码。