You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android应用多GET请求并行执行性能优化方案咨询

问题分析与解决方案

你当前的核心问题是:在getAltCoinData()方法中,对prefCoinList的每个条目串行执行网络请求——每一个币种的API请求都要等上一个请求完全响应并处理完才会开始,导致条目越多,总耗时呈线性增长,最终拖慢应用启动速度。

下面给出几种高效的并行处理方案,同时附带代码优化建议:


方案一:Java 原生 ExecutorService 实现并行请求

利用线程池批量执行单个币种的API请求,所有请求同时发起,大幅缩短总耗时。

步骤1:封装单个币种的请求逻辑

把单个币种的API请求和数据处理抽成独立方法,方便复用:

private String fetchSingleCoin(String coinId, String convertCurrency) throws IOException {
    StringBuilder sb = new StringBuilder();
    BufferedReader br = new BufferedReader(
        new InputStreamReader(getDataFromURL(
            "https://api.coinmarketcap.com/v1/ticker/" + coinId + "/?convert=" + convertCurrency
        ))
    );
    String line;
    while ((line = br.readLine()) != null) {
        // 过滤掉无用的[]行
        if (!line.equals("[") && !line.equals("]")) {
            sb.append(line);
        }
        sb.append("\n");
    }
    br.close();
    return sb.toString();
}

步骤2:修改getAltCoinData()为并行处理

用线程池批量提交任务,等待所有请求完成后再拼接结果:

public String getAltCoinData() throws IOException, InterruptedException, ExecutionException {
    List<String> setting = getSetting();
    // 先获取初始的批量币种数据
    BufferedReader br = new BufferedReader(
        new InputStreamReader(getDataFromURL(
            "https://api.coinmarketcap.com/v1/ticker/?convert=" + setting.get(0) + "&limit=" + setting.get(1)
        ))
    );
    StringBuilder sb = new StringBuilder();
    String line;
    while ((line = br.readLine()) != null) {
        if (!line.equals("]")) {
            sb.append(line);
        }
        sb.append("\n");
    }
    br.close();
    System.out.println("Testing 1111 Initial 30 coins Loading Time : " + df.format(new Date()));

    List<String> prefCoinList = getPrefCoin();
    if (prefCoinList.size() > 0) {
        sb.append(",");
        // 创建固定大小线程池,并发数建议设为4-8(避免触发API限流)
        ExecutorService executor = Executors.newFixedThreadPool(Math.min(prefCoinList.size(), 6));
        List<Callable<String>> tasks = new ArrayList<>();
        final String convertCurrency = setting.get(0);
        
        // 为每个币种创建请求任务
        for (String coinId : prefCoinList) {
            tasks.add(() -> fetchSingleCoin(coinId, convertCurrency));
        }
        
        // 批量提交并等待所有任务完成
        List<Future<String>> futures = executor.invokeAll(tasks);
        // 收集所有请求结果并拼接
        for (int i = 0; i < futures.size(); i++) {
            sb.append(futures.get(i).get());
            if (i != futures.size() - 1) {
                sb.append(",");
            }
        }
        executor.shutdown(); // 关闭线程池释放资源
        sb.append("]");
    } else {
        sb.append("]");
    }
    System.out.println("Testing 1111 loading (Prefered + Tran Coins) : " + df.format(new Date()));
    return sb.toString();
}

注意事项

  • 线程池大小不要设置过大,避免触发CoinMarketCap API的限流规则;
  • 处理好invokeAll()和Future.get()抛出的异常,避免应用崩溃;
  • 记得关闭线程池,防止资源泄漏。

方案二:Kotlin Coroutines(推荐,Android官方现代异步方案)

如果你的项目可以迁移到Kotlin,用协程的async/await实现并行会更简洁,且自带线程调度优化:

核心代码示例

// 在Activity/Fragment或ViewModel中启动协程
fun initialFetchAltCoins() {
    lifecycleScope.launch(Dispatchers.IO) {
        try {
            val coinData = getAltCoinData()
            loadAltCoins(coinData)
            // 切换回主线程更新UI/状态
            withContext(Dispatchers.Main) {
                maingame.dataReady = true
                println("Testing 1111 loading Initial + (Prefered + Tran Coins) : ${df.format(Date())}")
            }
        } catch (e: IOException) {
            e.printStackTrace()
        }
    }
}

suspend fun getAltCoinData(): String {
    val setting = getSetting()
    // 初始批量请求
    val initialData = getDataFromURL(
        "https://api.coinmarketcap.com/v1/ticker/?convert=${setting[0]}&limit=${setting[1]}"
    ).use { inputStream ->
        BufferedReader(InputStreamReader(inputStream)).readLines()
            .joinToString("\n") { if (it != "]") it else "" } + "\n"
    }
    println("Testing 1111 Initial 30 coins Loading Time : ${df.format(Date())}")

    val prefCoinList = getPrefCoin()
    return if (prefCoinList.isNotEmpty()) {
        val convertCurrency = setting[0]
        // 并行请求所有偏好币种
        val coinDataList = coroutineScope {
            prefCoinList.map { coinId ->
                async { fetchSingleCoin(coinId, convertCurrency) }
            }.awaitAll()
        }
        // 拼接最终结果
        initialData + "," + coinDataList.joinToString(",") + "]"
    } else {
        initialData + "]"
    }.also {
        println("Testing 1111 loading (Prefered + Tran Coins) : ${df.format(Date())}")
    }
}

suspend fun fetchSingleCoin(coinId: String, convertCurrency: String): String {
    return getDataFromURL(
        "https://api.coinmarketcap.com/v1/ticker/$coinId/?convert=$convertCurrency"
    ).use { inputStream ->
        BufferedReader(InputStreamReader(inputStream)).readLines()
            .joinToString("\n") { if (it != "[" && it != "]") it else "" } + "\n"
    }
}

// 封装网络请求为挂起函数
suspend fun getDataFromURL(url: String): InputStream {
    return withContext(Dispatchers.IO) {
        URL(url).openStream()
    }
}

额外优化建议

  1. 替换废弃的AsyncTask:AsyncTask已在API 30被废弃,建议用上述协程或ExecutorService替代;
  2. 改用OkHttp网络库:原生HttpURLConnection效率较低,OkHttp自带连接池、异步请求支持,能进一步提升网络请求性能;
  3. 添加本地缓存:如果数据不需要实时更新,可将请求结果缓存到Room或SharedPreferences,启动时先加载缓存,后台再更新;
  4. API批量查询:查看CoinMarketCap最新API(v2/v3)是否支持批量查询多个币种,减少请求次数(这是最优的性能优化方向);
  5. 避免线程安全问题:并行处理时不要多个线程直接操作同一个StringBuilder,每个线程单独生成结果后再拼接。

内容的提问来源于stack exchange,提问作者iappmaker

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 03:43:08