HarmonyOS HTTP请求:从“能跑就行”到“优雅可靠”的进化之路

HarmonyOS HTTP请求:从"能跑就行"到"优雅可靠"的进化之路


一、当网络请求遇上"薛定谔的响应"

还记得第一次在HarmonyOS里写网络请求吗?满心欢喜地调完接口,结果页面一片空白------没有错误提示,没有崩溃日志,就像什么都没发生过一样。你盯着代码看了半小时,最后才发现:哦,忘了在module.json5里加网络权限。

这种"静默失败"的经历,相信不少开发者都遇到过。HTTP请求看似简单,但在移动端开发中,它就像一座冰山------表面上的几行代码,下面藏着连接管理、超时控制、错误处理、数据解析等一系列复杂问题。

今天,我们就来彻底拆解HarmonyOS的HTTP请求机制。不只是教你怎么用,更要告诉你为什么这么用,以及如何用得更好。


二、HTTP请求在HarmonyOS里到底经历了什么?

1. 什么方式呢
通过
失败
发起请求
权限检查
创建HttpRequest对象
静默失败
配置请求参数
建立TCP连接
发送HTTP请求
等待服务器响应
接收响应头
触发headersReceive事件
接收响应体
请求完成回调
销毁HttpRequest对象

2. 那些容易忽略的关键细节

  • 权限不是可选项 :没有ohos.permission.INTERNET,请求直接消失,连个错误都不给
  • 每个请求都是独立的http.createHttp()创建的对象不能复用,用完必须destroy()
  • 回调地狱的救赎:支持Promise和Callback两种方式,但混用容易出问题
  • 内存泄漏的隐形杀手:忘记销毁的HttpRequest对象会一直占用连接资源

三、你的第一个"真正可用"的HTTP请求

1. 基础配置(别再忘了这一步)

json 复制代码
// module.json5 - 没有这个,一切免谈
{
  "module": {
    "requestPermissions": [
      {
        "name": "ohos.permission.INTERNET",
        "usedScene": {
          "when": "always"
        }
      }
    ]
  }
}

2. 基础GET请求(Promise版本)

typescript 复制代码
import http from '@ohos.net.http';

async function fetchUserProfile(userId: string): Promise<User> {
  // 注意:每次请求都要创建新实例
  const httpRequest = http.createHttp();
  
  try {
    const response = await httpRequest.request(
      `https://api.example.com/users/${userId}`,
      {
        method: http.RequestMethod.GET,
        connectTimeout: 10000,  // 10秒连接超时
        readTimeout: 15000,     // 15秒读取超时
        header: {
          'Content-Type': 'application/json',
          'Accept': 'application/json'
        }
      }
    );
    
    if (response.responseCode === 200) {
      // response.result可能是string或ArrayBuffer
      const result = JSON.parse(response.result as string);
      return result as User;
    } else {
      throw new Error(`HTTP ${response.responseCode}: ${response.result}`);
    }
  } catch (error) {
    console.error('网络请求失败:', error);
    throw error;
  } finally {
    // 关键!必须销毁,否则内存泄漏
    httpRequest.destroy();
  }
}

3. POST请求的"坑"与"填"

typescript 复制代码
async function login(username: string, password: string): Promise<LoginResponse> {
  const httpRequest = http.createHttp();
  
  try {
    const response = await httpRequest.request(
      'https://api.example.com/auth/login',
      {
        method: http.RequestMethod.POST,
        header: {
          'Content-Type': 'application/json'
        },
        // 注意:POST数据放在extraData,不是body!
        extraData: JSON.stringify({
          username: username,
          password: password
        }),
        connectTimeout: 10000,
        readTimeout: 10000
      }
    );
    
    return JSON.parse(response.result as string);
  } finally {
    httpRequest.destroy();
  }
}

看到那个extraData了吗?这是HarmonyOS和浏览器Fetch API的一个关键区别。第一次用的时候,我对着文档看了三遍才确认------没错,POST数据确实叫extraData


四、封装一个生产级的HTTP客户端

直接使用原生API就像用螺丝刀拧螺丝------能干活,但效率不高。真正项目中,我们需要的是电动螺丝刀。下面这个封装,是我在多个项目中打磨出来的。

1. 类型定义(TypeScript的优势就在这里)

typescript 复制代码
// network/HttpTypes.ets
export interface ApiResponse<T> {
  code: number;
  message: string;
  data: T;
}

export interface RequestConfig {
  url: string;
  method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
  params?: Record<string, string | number>;
  data?: any;
  headers?: Record<string, string>;
  timeout?: number;
}

export class HttpError extends Error {
  constructor(
    public code: number,
    message: string,
    public originalError?: any
  ) {
    super(message);
    this.name = 'HttpError';
  }
}

2. 核心HttpClient类

typescript 复制代码
// network/HttpClient.ets
import http from '@ohos.net.http';

type RequestInterceptor = (config: RequestConfig) => RequestConfig;
type ResponseInterceptor<T> = (response: ApiResponse<T>) => ApiResponse<T>;

export class HttpClient {
  private baseUrl: string;
  private defaultTimeout: number = 15000;
  private requestInterceptors: RequestInterceptor[] = [];
  private responseInterceptors: ResponseInterceptor<any>[] = [];
  
  // 单例模式,全局一个实例就够了
  private static instance: HttpClient;
  
  static getInstance(baseUrl?: string): HttpClient {
    if (!HttpClient.instance) {
      if (!baseUrl) {
        throw new Error('首次调用需要提供baseUrl');
      }
      HttpClient.instance = new HttpClient(baseUrl);
    }
    return HttpClient.instance;
  }
  
  private constructor(baseUrl: string) {
    this.baseUrl = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;
  }
  
  // 添加请求拦截器(用于添加Token等)
  addRequestInterceptor(interceptor: RequestInterceptor): void {
    this.requestInterceptors.push(interceptor);
  }
  
  // 添加响应拦截器(用于统一错误处理)
  addResponseInterceptor<T>(interceptor: ResponseInterceptor<T>): void {
    this.responseInterceptors.push(interceptor);
  }
  
  async request<T>(config: RequestConfig): Promise<T> {
    // 1. 执行请求拦截器
    let finalConfig = { ...config };
    for (const interceptor of this.requestInterceptors) {
      finalConfig = interceptor(finalConfig);
    }
    
    // 2. 构建完整URL
    let url = `${this.baseUrl}${finalConfig.url}`;
    
    // 3. 处理查询参数
    if (finalConfig.params) {
      const queryString = Object.entries(finalConfig.params)
        .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
        .join('&');
      url += `?${queryString}`;
    }
    
    // 4. 创建请求实例
    const httpRequest = http.createHttp();
    
    try {
      // 5. 发起请求
      const response = await httpRequest.request(url, {
        method: this.mapMethod(finalConfig.method || 'GET'),
        header: {
          'Content-Type': 'application/json',
          'Accept': 'application/json',
          ...finalConfig.headers
        },
        extraData: finalConfig.data ? JSON.stringify(finalConfig.data) : undefined,
        connectTimeout: finalConfig.timeout || this.defaultTimeout,
        readTimeout: finalConfig.timeout || this.defaultTimeout
      });
      
      // 6. 解析响应
      const rawData = response.result as string;
      const parsedResponse: ApiResponse<T> = JSON.parse(rawData);
      
      // 7. 执行响应拦截器
      let finalResponse = parsedResponse;
      for (const interceptor of this.responseInterceptors) {
        finalResponse = interceptor(finalResponse);
      }
      
      // 8. 统一错误处理
      if (finalResponse.code !== 0 && finalResponse.code !== 200) {
        throw new HttpError(
          finalResponse.code,
          finalResponse.message || '请求失败'
        );
      }
      
      return finalResponse.data;
      
    } catch (error) {
      // 9. 网络错误处理
      if (error instanceof HttpError) {
        throw error;
      }
      
      // 处理HTTP状态码错误
      if (error.responseCode && error.responseCode !== 200) {
        throw new HttpError(
          error.responseCode,
          `HTTP ${error.responseCode}`,
          error
        );
      }
      
      // 处理网络异常
      throw new HttpError(
        -1,
        '网络连接失败,请检查网络设置',
        error
      );
    } finally {
      // 10. 清理资源
      httpRequest.destroy();
    }
  }
  
  private mapMethod(method: string): http.RequestMethod {
    const methodMap: Record<string, http.RequestMethod> = {
      'GET': http.RequestMethod.GET,
      'POST': http.RequestMethod.POST,
      'PUT': http.RequestMethod.PUT,
      'DELETE': http.RequestMethod.DELETE,
      'PATCH': http.RequestMethod.PATCH
    };
    return methodMap[method] || http.RequestMethod.GET;
  }
  
  // 快捷方法
  get<T>(url: string, params?: Record<string, any>, config?: Partial<RequestConfig>): Promise<T> {
    return this.request<T>({
      url,
      method: 'GET',
      params,
      ...config
    });
  }
  
  post<T>(url: string, data?: any, config?: Partial<RequestConfig>): Promise<T> {
    return this.request<T>({
      url,
      method: 'POST',
      data,
      ...config
    });
  }
}

3. 使用示例

typescript 复制代码
// 初始化
const apiClient = HttpClient.getInstance('https://api.example.com');

// 添加Token拦截器
apiClient.addRequestInterceptor((config) => {
  const token = AppStorage.get<string>('userToken');
  if (token) {
    config.headers = {
      ...config.headers,
      'Authorization': `Bearer ${token}`
    };
  }
  return config;
});

// 添加错误拦截器
apiClient.addResponseInterceptor((response) => {
  if (response.code === 401) {
    // Token过期,跳转到登录页
    router.replaceUrl({ url: 'pages/Login' });
    throw new HttpError(401, '请重新登录');
  }
  return response;
});

// 在组件中使用
@Component
struct UserProfile {
  @State user: User | null = null;
  @State loading: boolean = false;
  
  async loadUserData() {
    this.loading = true;
    try {
      this.user = await apiClient.get<User>('/api/user/profile');
    } catch (error) {
      promptAction.showToast({
        message: error.message,
        duration: 3000
      });
    } finally {
      this.loading = false;
    }
  }
  
  build() {
    Column() {
      if (this.loading) {
        LoadingIndicator()
      } else if (this.user) {
        UserCard({ user: this.user })
      }
    }
  }
}

五、电商应用 vs 即时通讯

1. 电商应用:请求可缓存,失败可重试

typescript 复制代码
class ECommerceService {
  private cache = new Map<string, { data: any; timestamp: number }>();
  
  async getProductList(category: string, forceRefresh = false): Promise<Product[]> {
    const cacheKey = `products_${category}`;
    
    // 检查缓存(5分钟内有效)
    if (!forceRefresh) {
      const cached = this.cache.get(cacheKey);
      if (cached && Date.now() - cached.timestamp < 5 * 60 * 1000) {
        return cached.data;
      }
    }
    
    // 带重试机制的请求
    let lastError: Error;
    for (let i = 0; i < 3; i++) {
      try {
        const products = await apiClient.get<Product[]>(`/api/products`, { category });
        
        // 更新缓存
        this.cache.set(cacheKey, {
          data: products,
          timestamp: Date.now()
        });
        
        return products;
      } catch (error) {
        lastError = error;
        if (i < 2) {
          // 等待指数退避时间
          await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, i)));
        }
      }
    }
    
    throw lastError!;
  }
}

2. 即时通讯:长连接 + 短轮询混合

typescript 复制代码
class ChatService {
  private ws: WebSocket | null = null;
  private messageQueue: Message[] = [];
  private isOnline = false;
  
  // 初始化WebSocket连接
  connect(userId: string): void {
    this.ws = new WebSocket(`wss://chat.example.com/ws?userId=${userId}`);
    
    this.ws.onopen = () => {
      this.isOnline = true;
      this.flushMessageQueue();
    };
    
    this.ws.onmessage = (event) => {
      this.handleIncomingMessage(JSON.parse(event.data));
    };
    
    this.ws.onclose = () => {
      this.isOnline = false;
      // 退回到HTTP轮询
      this.startPolling();
    };
  }
  
  // 发送消息(优先WebSocket,失败则用HTTP)
  async sendMessage(message: Message): Promise<void> {
    if (this.isOnline && this.ws) {
      this.ws.send(JSON.stringify(message));
    } else {
      // 加入队列,等待连接恢复
      this.messageQueue.push(message);
      
      // 同时尝试HTTP发送
      try {
        await apiClient.post('/api/messages/send', message);
        this.messageQueue = this.messageQueue.filter(m => m.id !== message.id);
      } catch (error) {
        console.error('消息发送失败,已加入重试队列:', error);
      }
    }
  }
  
  // 轮询备选方案
  private startPolling(): void {
    setInterval(async () => {
      try {
        const messages = await apiClient.get<Message[]>('/api/messages/poll');
        messages.forEach(msg => this.handleIncomingMessage(msg));
      } catch (error) {
        console.error('轮询失败:', error);
      }
    }, 5000); // 每5秒轮询一次
  }
}

看到区别了吗?电商应用追求的是稳定性性能 ,所以用缓存+重试。即时通讯追求的是实时性可靠性,所以用WebSocket为主,HTTP轮询为备。


六、鸿蒙6新特性:HTTP请求的"智能进化"

从HarmonyOS 6开始,HTTP模块有了几个让人眼前一亮的新特性:

1. 智能重试与熔断

typescript 复制代码
// HarmonyOS 6的新配置项
const response = await httpRequest.request(url, {
  method: http.RequestMethod.GET,
  // 新增:自动重试配置
  retryConfig: {
    maxRetries: 3,           // 最大重试次数
    retryDelay: 1000,        // 重试延迟(毫秒)
    retryOnStatus: [502, 503, 504] // 对这些状态码重试
  },
  // 新增:熔断器配置
  circuitBreaker: {
    failureThreshold: 5,     // 5次失败后熔断
    resetTimeout: 30000,     // 30秒后尝试恢复
    halfOpenMaxRequests: 3   // 半开状态最多3个请求
  }
});

2. 请求优先级调度

typescript 复制代码
// 关键请求优先处理
const highPriorityRequest = http.createHttp({
  priority: http.RequestPriority.HIGH  // 新增:请求优先级
});

// 图片加载可以降低优先级
const imageRequest = http.createHttp({
  priority: http.RequestPriority.LOW
});

3. 智能数据压缩

typescript 复制代码
// 自动协商压缩算法
const response = await httpRequest.request(url, {
  method: http.RequestMethod.GET,
  // 新增:压缩配置
  compression: {
    enabled: true,
    minSize: 1024,  // 超过1KB才压缩
    algorithms: ['gzip', 'br', 'deflate']
  }
});

4. 原生支持拦截器

typescript 复制代码
// 不再需要手动封装,系统原生支持
const httpRequest = http.createHttp();

// 添加请求拦截器
httpRequest.addRequestInterceptor((config) => {
  config.header['X-Request-ID'] = generateUUID();
  return config;
});

// 添加响应拦截器
httpRequest.addResponseInterceptor((response) => {
  if (response.responseCode === 429) {
    // 处理限流
    showRateLimitToast();
  }
  return response;
});

这些新特性让HTTP请求从"能用"变成了"好用"。特别是那个熔断器------再也不用担心某个接口挂掉拖垮整个应用了。


七、那些年我们踩过的HTTP坑

1. 内存泄漏的幽灵

typescript 复制代码
// 错误做法:在组件中直接创建请求
@Component
struct LeakyComponent {
  aboutToAppear() {
    const httpRequest = http.createHttp();
    httpRequest.request(url, (err, data) => {
      // 处理响应
      // 问题:忘记调用httpRequest.destroy()
    });
  }
}

// 正确做法:使用可销毁的管理器
class RequestManager {
  private requests: http.HttpRequest[] = [];
  
  createRequest(): http.HttpRequest {
    const request = http.createHttp();
    this.requests.push(request);
    return request;
  }
  
  destroyAll() {
    this.requests.forEach(req => req.destroy());
    this.requests = [];
  }
}

2. 并发的陷阱

typescript 复制代码
// 错误做法:并发请求共享同一个实例
async function fetchMultipleData() {
  const httpRequest = http.createHttp();
  
  // 并发请求,但共享同一个实例
  const [user, orders] = await Promise.all([
    httpRequest.request(userUrl),
    httpRequest.request(ordersUrl) // 可能失败!
  ]);
  
  httpRequest.destroy();
}

// 正确做法:每个请求独立实例
async function fetchMultipleDataCorrectly() {
  const [user, orders] = await Promise.all([
    this.createRequest(userUrl),
    this.createRequest(ordersUrl)
  ]);
  
  return { user, orders };
}

private async createRequest(url: string): Promise<any> {
  const httpRequest = http.createHttp();
  try {
    const response = await httpRequest.request(url);
    return response.result;
  } finally {
    httpRequest.destroy();
  }
}

3. 超时设置的学问

typescript 复制代码
// 不同场景的超时策略
const timeoutStrategies = {
  // 关键操作:短超时 + 快速失败
  critical: {
    connectTimeout: 3000,   // 3秒连接超时
    readTimeout: 5000       // 5秒读取超时
  },
  
  // 文件上传:长超时
  upload: {
    connectTimeout: 10000,  // 10秒连接超时
    readTimeout: 300000     // 5分钟读取超时(大文件)
  },
  
  // 实时聊天:中等超时 + 重试
  chat: {
    connectTimeout: 5000,
    readTimeout: 10000,
    retryCount: 2
  }
};

八、性能优化:让HTTP请求"飞"起来

1. 连接池复用

typescript 复制代码
class ConnectionPool {
  private pool: Map<string, http.HttpRequest[]> = new Map();
  private maxPoolSize = 5;
  
  getRequest(baseUrl: string): http.HttpRequest {
    const pool = this.pool.get(baseUrl) || [];
    
    // 复用空闲连接
    if (pool.length > 0) {
      return pool.pop()!;
    }
    
    // 创建新连接
    return http.createHttp();
  }
  
  releaseRequest(baseUrl: string, request: http.HttpRequest): void {
    const pool = this.pool.get(baseUrl) || [];
    
    if (pool.length < this.maxPoolSize) {
      pool.push(request);
      this.pool.set(baseUrl, pool);
    } else {
      request.destroy();
    }
  }
}

2. 请求去重

typescript 复制代码
class RequestDeduplicator {
  private pendingRequests: Map<string, Promise<any>> = new Map();
  
  async deduplicatedRequest<T>(
    key: string,
    requestFn: () => Promise<T>
  ): Promise<T> {
    // 如果已有相同请求在进行中,直接复用Promise
    if (this.pendingRequests.has(key)) {
      return this.pendingRequests.get(key) as Promise<T>;
    }
    
    const promise = requestFn().finally(() => {
      this.pendingRequests.delete(key);
    });
    
    this.pendingRequests.set(key, promise);
    return promise;
  }
}

// 使用示例
const deduplicator = new RequestDeduplicator();

// 多个组件同时请求用户信息,只会发一次请求
const user1 = await deduplicator.deduplicatedRequest(
  'user_123',
  () => apiClient.get('/api/user/123')
);

const user2 = await deduplicator.deduplicatedRequest(
  'user_123', 
  () => apiClient.get('/api/user/123') // 不会真正执行
);

3. 智能缓存策略

typescript 复制代码
interface CacheEntry {
  data: any;
  timestamp: number;
  expiresIn: number;
  staleWhileRevalidate?: boolean;
}

class SmartCache {
  private cache: Map<string, CacheEntry> = new Map();
  
  async getOrFetch<T>(
    key: string,
    fetchFn: () => Promise<T>,
    options: {
      expiresIn: number; // 缓存过期时间(毫秒)
      staleWhileRevalidate?: boolean; // 是否允许过期后继续使用旧数据
    }
  ): Promise<T> {
    const entry = this.cache.get(key);
    const now = Date.now();
    
    // 缓存命中
    if (entry) {
      const isFresh = now - entry.timestamp < entry.expiresIn;
      const isStaleAcceptable = entry.staleWhileRevalidate && 
                               now - entry.timestamp < entry.expiresIn * 2;
      
      if (isFresh || isStaleAcceptable) {
        // 如果允许过期后使用旧数据,在后台更新缓存
        if (!isFresh && entry.staleWhileRevalidate) {
          this.refreshInBackground(key, fetchFn);
        }
        return entry.data;
      }
    }
    
    // 缓存未命中或已过期,重新获取
    const data = await fetchFn();
    this.cache.set(key, {
      data,
      timestamp: now,
      expiresIn: options.expiresIn,
      staleWhileRevalidate: options.staleWhileRevalidate
    });
    
    return data;
  }
  
  private async refreshInBackground(key: string, fetchFn: () => Promise<any>): void {
    // 在后台更新缓存,不影响当前请求
    setTimeout(async () => {
      try {
        const data = await fetchFn();
        this.cache.set(key, {
          data,
          timestamp: Date.now(),
          expiresIn: 5 * 60 * 1000 // 5分钟
        });
      } catch (error) {
        console.warn(`后台缓存更新失败: ${key}`, error);
      }
    }, 0);
  }
}

九、监控和调试:给HTTP请求装上"眼睛"

1. 请求日志记录

typescript 复制代码
class RequestLogger {
  logRequest(config: RequestConfig, startTime: number): void {
    const duration = Date.now() - startTime;
    
    console.info(`[HTTP] ${config.method} ${config.url} - ${duration}ms`);
    
    // 发送到监控平台
    this.sendToAnalytics({
      type: 'http_request',
      method: config.method,
      url: config.url,
      duration: duration,
      timestamp: new Date().toISOString()
    });
  }
  
  logError(config: RequestConfig, error: Error, duration: number): void {
    console.error(`[HTTP ERROR] ${config.method} ${config.url}`, error);
    
    // 错误上报
    this.sendToErrorTracking({
      type: 'http_error',
      method: config.method,
      url: config.url,
      error: error.message,
      duration: duration,
      stack: error.stack
    });
  }
}

2. 性能监控面板

typescript 复制代码
@Component
struct RequestMonitor {
  @State requests: RequestMetric[] = [];
  
  build() {
    Column() {
      // 请求统计
      Text(`总请求数: ${this.requests.length}`)
      Text(`成功: ${this.requests.filter(r => r.success).length}`)
      Text(`失败: ${this.requests.filter(r => !r.success).length}`)
      Text(`平均耗时: ${this.getAverageDuration()}ms`)
      
      // 请求列表
      List() {
        ForEach(this.requests, (metric) => {
          ListItem() {
            RequestMetricItem({ metric: metric })
          }
        })
      }
    }
  }
}

十、总结一下下:HTTP请求的"道"与"术"

写了这么多代码,看了这么多配置,其实HTTP请求的核心就三件事:

第一,可靠性。你的请求能不能在各种网络环境下都正常工作?弱网怎么办?服务器出错怎么办?Token过期怎么办?这些问题的答案,决定了应用的稳定性。

第二,性能。用户不会等你。研究显示,页面加载时间每增加1秒,转化率下降7%。缓存、压缩、连接复用------这些优化手段,都是为了把那1秒抢回来。

第三,可维护性。三个月后,你还能看懂自己的代码吗?新同事接手时,需要花多少时间理解你的网络层?清晰的架构、完善的注释、统一的错误处理,这些"软实力"往往比技术本身更重要。

HarmonyOS的HTTP模块,从最初的"能用就行",到现在的功能丰富,再到鸿蒙6的智能化,一直在进化。但工具再先进,也替代不了开发者的思考。

下次写HTTP请求时,不妨问自己三个问题:

  1. 这个请求失败了对用户有什么影响?
  2. 有没有更高效的方式获取这些数据?
  3. 如果明天要换网络库,改动成本有多大?

想清楚这些问题,你的HTTP代码就不会差。记住:好的网络层代码,是让业务代码感受不到网络的存在。用户看到的是流畅的界面、及时的数据更新,而不是"加载中"的转圈圈。


最后的实用小小建议

  1. 一定要加权限ohos.permission.INTERNET,说三遍都不够
  2. 一定要销毁 :每个createHttp()都要配一个destroy()
  3. 一定要处理错误:用户讨厌白屏,更讨厌莫名其妙的失败
  4. 一定要监控:没有监控的代码就像闭着眼睛开车
  5. 一定要测试弱网:在你的WiFi环境下跑得飞快,不代表在电梯里也行

从今天开始,不要再写"能跑就行"的HTTP代码了。让你的每一个请求,都经得起推敲,扛得住压力,对得起用户。

相关推荐
芙莉莲教你写代码2 小时前
Flutter 框架跨平台鸿蒙开发 - 水果消消乐游戏
flutter·游戏·华为·harmonyos
芙莉莲教你写代码2 小时前
Flutter 框架跨平台鸿蒙开发 - 坦克大战游戏
flutter·游戏·华为·harmonyos
AnalogElectronic3 小时前
对https一系列问题的疑问与解答
网络协议·http·https
冉佳驹3 小时前
Linux ——— 网络开发核心知识与协议实现详解
linux·http·https·udp·json·tcp·端口号
前端不太难3 小时前
鸿蒙游戏:从单设备到全场景
游戏·harmonyos
末日汐3 小时前
应用层协议HTTP
网络·网络协议·http
想你依然心痛3 小时前
HarmonyOS 5.0金融安全APP开发实战:基于可信执行环境与分布式风控的移动支付系统
安全·金融·harmonyos
Swift社区3 小时前
未来游戏形态:鸿蒙 + AI + 多端协同
人工智能·游戏·harmonyos
UnicornDev3 小时前
【HarmonyOS 6】使用说明功能:浮动按钮、弹窗与偏好设置
华为·harmonyos·arkts·鸿蒙·鸿蒙系统