示例图片
在这里插入图片描述

引言

在混合开发场景中,Flutter 应用常需调用 OpenHarmony 原生能力以满足国产化合规要求,其中网络通信尤为关键。OpenHarmony 对网络权限、HTTPS 校验、代理配置等有严格管控,而 Flutter 默认使用 Dart 的 httpdio 库,其底层基于 POSIX socket,在 OpenHarmony 上可能面临:

  • 权限拒绝:未声明 ohos.permission.INTERNET
  • 证书校验失败:系统强制启用 TLS 安全策略;
  • 无法复用系统网络栈:如企业代理、国密算法支持;
  • 离线场景处理缺失:未与 OpenHarmony 的网络状态监听联动。

若直接在 Flutter 中发起网络请求,轻则功能异常,重则违反安全规范。本文提出一种 “原生代理 + 统一拦截” 的网络协同架构,并通过代码案例展示如何让 Flutter 安全、高效、合规地访问网络。


一、OpenHarmony 网络安全机制要点

1. 权限声明(必须)

module.json5 中显式申请:

{
  "requestPermissions": [
    { "name": "ohos.permission.INTERNET" },
    { "name": "ohos.permission.GET_NETWORK_INFO" }
  ]
}

2. HTTPS 强制校验

  • 默认禁止 HTTP 明文请求(可配置 network_security_config.xml 临时允许);
  • 证书需由受信任 CA 签发,或通过 自定义 TrustManager 注入。

3. 网络状态监听

可通过 @ohos.net.connection 监听网络类型(Wi-Fi/蜂窝)、是否在线等。


二、为什么不应让 Flutter 直连网络?

风险点 说明
绕过系统安全策略 Dart 的 HttpClient 不走 OpenHarmony 的网络安全框架,可能被拦截
无法适配国产加密标准 如 SM2/SM4 国密算法,需依赖系统级支持
企业网络环境不兼容 代理、证书透明度(CT)等需系统级配置
电量与流量监控失效 系统无法统计 Flutter 进程的网络消耗

最佳实践
所有网络请求应由 OpenHarmony 原生层代理执行,Flutter 仅负责业务逻辑与 UI。


三、协同架构设计

+------------------+       MethodChannel        +-----------------------+
|   Flutter (Dart) | -------------------------> | OpenHarmony (ArkTS)   |
| - 发起 API 请求  | <------------------------- | - 使用 @ohos.net.http |
| - 处理 JSON 响应 |                            | - 统一拦截/重试/缓存  |
+------------------+                            +-----------------------+
                              ↑
                      网络状态监听 & 安全校验

核心优势:

  • 复用 OpenHarmony 安全网络栈;
  • 统一处理 Token 刷新、错误码拦截;
  • 支持离线缓存、请求队列;
  • 符合等保与国产化审计要求。

四、代码实践

步骤 1:Flutter 端定义网络服务接口

// lib/services/oh_http_client.dart
import 'package:flutter/services.dart';

class OHHttpClient {
  static const _channel = MethodChannel('com.example.network');

  static Future<Map<String, dynamic>> get(String url,
      {Map<String, String>? headers}) async {
    final args = <String, dynamic>{
      'method': 'GET',
      'url': url,
      'headers': headers ?? {},
    };
    final result = await _channel.invokeMethod('request', args);
    return result as Map<String, dynamic>;
  }

  static Future<Map<String, dynamic>> post(String url,
      {Map<String, dynamic>? body, Map<String, String>? headers}) async {
    final args = <String, dynamic>{
      'method': 'POST',
      'url': url,
      'body': body,
      'headers': headers ?? {},
    };
    final result = await _channel.invokeMethod('request', args);
    return result as Map<String, dynamic>;
  }
}

返回格式约定:{ "code": 200, "data": {...}, "message": "" }


步骤 2:OpenHarmony 端实现网络代理

// model/NetworkBridge.ts
import http from '@ohos.net.http';
import connection from '@ohos.net.connection';

export class NetworkBridge {
  private netCap: connection.NetCap | null = null;

  constructor() {
    this.initNetworkListener();
  }

  private initNetworkListener() {
    // 监听网络状态变化(用于离线提示)
    connection.getDefaultNet().then(net => {
      this.netCap = net.getNetCap();
      net.on('netAvailable', () => console.log('Network available'));
      net.on('netUnavailable', () => console.log('Network lost'));
    });
  }

  async request(options: {
    method: string;
    url: string;
    headers?: Record<string, string>;
    body?: Record<string, any>;
  }): Promise<Record<string, any>> {
    // 检查网络是否可用
    if (!this.netCap?.isAvailable()) {
      return { code: -1, message: '网络不可用', data: null };
    }

    const httpRequest = http.createHttp();
    let extraData: http.HttpRequestOptions = {
      method: options.method as http.RequestMethod,
      header: options.headers || {},
      expectDataType: http.HttpDataType.STRING
    };

    if (options.method === 'POST' && options.body) {
      extraData = {
        ...extraData,
        extraData: JSON.stringify(options.body)
      };
    }

    try {
      const response = await httpRequest.request(options.url, extraData);
      
      // 统一处理响应
      if (response.responseCode >= 200 && response.responseCode < 300) {
        const data = JSON.parse(response.result as string);
        return { code: 0, data, message: 'success' };
      } else {
        return { code: response.responseCode, message: '请求失败', data: null };
      }
    } catch (err) {
      console.error('Network error:', err);
      return { code: -2, message: '网络异常', data: null };
    } finally {
      httpRequest.destroy();
    }
  }
}

步骤 3:在 EntryAbility 中注册通道

// EntryAbility.ts
import UIAbility from '@ohos/app.ability.UIAbility';
import { MethodChannel } from '@ohos/flutter';
import { NetworkBridge } from './model/NetworkBridge';

export default class EntryAbility extends UIAbility {
  private networkBridge: NetworkBridge;

  onCreate() {
    this.networkBridge = new NetworkBridge();
    
    const channel = new MethodChannel('com.example.network');
    channel.setMethodCallHandler(async (call) => {
      if (call.method === 'request') {
        return await this.networkBridge.request(call.arguments as any);
      }
      throw new Error('Unknown method');
    });
  }
}

步骤 4:Flutter 页面调用示例

// lib/pages/user_profile.dart
import 'package:flutter/material.dart';
import '../services/oh_http_client.dart';

class UserProfilePage extends StatefulWidget {
  
  _UserProfilePageState createState() => _UserProfilePageState();
}

class _UserProfilePageState extends State<UserProfilePage> {
  String _status = '点击加载';
  Map<String, dynamic>? _user;

  Future<void> _loadUser() async {
    setState(() => _status = '加载中...');
    try {
      final response = await OHHttpClient.get(
        'https://api.example.com/user/123',
        headers: {'Authorization': 'Bearer xxx'}
      );

      if (response['code'] == 0) {
        setState(() {
          _user = response['data'];
          _status = '加载成功';
        });
      } else {
        _status = '错误: ${response['message']}';
      }
    } catch (e) {
      _status = '异常: $e';
    }
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('用户信息')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(_status),
            if (_user != null) Text('姓名: ${_user!['name']}'),
            ElevatedButton(onPressed: _loadUser, child: Text('获取用户信息')),
          ],
        ),
      ),
    );
  }
}

五、高级能力扩展

1. 统一 Token 刷新

NetworkBridge.request() 中拦截 401,自动调用登录接口刷新 Token。

2. 离线缓存

  • 网络不可用时,从 OpenHarmony 的 PreferencesKV Store 读取缓存;
  • 请求成功后自动更新缓存。

3. 国密支持

通过 @ohos.security.cryptoFramework 注入 SM2 公钥,对请求体签名。

4. 请求日志审计

记录所有请求 URL、时间、耗时,满足等保日志留存要求。


六、注意事项

1. 避免阻塞主线程

OpenHarmony 的 http.request 采用异步设计,开发者必须确保网络请求不会阻塞 UI 线程。建议采用以下方式:

  • 使用 Promiseasync/await 语法处理异步请求
  • 在回调函数中更新 UI 时,务必通过 TaskDispatcher 切换到主线程
  • 示例代码:
http.request({
  // 请求配置
}, (err, data) => {
  // 回调处理
  context.resourceManager.getTaskDispatcher('main').asyncDispatch(() => {
    // 更新UI操作
  });
});

2. 超时设置

合理的超时设置对应用稳定性至关重要:

  • readTimeout 默认值为 60 秒
  • 可根据网络环境动态调整:
const options = {
  readTimeout: 10000, // 10秒超时
  // 其他配置...
}
http.request(url, options, callback);
  • 建议值:
    • WiFi环境:5-10秒
    • 移动网络:10-15秒
    • 大文件下载:适当延长

3. 敏感信息保护

安全开发规范要求:

  • 所有认证信息(Token、API Key等)必须加密存储
  • 禁止在日志中输出完整敏感信息,如需调试可显示部分字符(如"token: abc…xyz")
  • 推荐使用系统提供的安全存储接口:
import security from '@ohos.security';
// 存储敏感数据
security.cryptoFramework.createCipher().then(...);

4. HTTPS 证书管理

安全连接配置要点:

  • 生产环境必须使用正规CA颁发的证书
  • 开发阶段如需使用自签名证书:
    1. 准备证书文件(.crt/.pem)
    2. resources/base/profile/network_security_config.xml 中添加配置:
<domain-config cleartextTrafficPermitted="false">
  <domain includeSubdomains="true">yourdomain.com</domain>
  <trust-anchors>
    <certificates src="@raw/your_certificate"/>
  </trust-anchors>
</domain-config>
  • 证书文件需放在 resources/rawfile 目录
  • 正式发布前务必移除测试证书配置

七、总结

通过将网络请求委托给 OpenHarmony 原生层,我们不仅规避了安全合规风险,还获得了系统级网络能力的支持。这种“Flutter 负责 UI 与逻辑,OpenHarmony 负责系统交互”的分工模式,是构建高质量国产混合应用的最佳实践。

未来,随着 OpenHarmony 对 WebAssembly 和更高效 IPC 的支持,网络桥接性能将进一步提升。但现阶段,MethodChannel + 原生代理仍是稳定可靠的选择。


!**

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐