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

