Android英雄联盟应用:AsyncTask中英雄Key转名称的异常解决
解决英雄Key转名称的两种方案
首先,你遇到的NetworkOnMainThreadException是因为Android不允许在主线程(也就是onPostExecute方法)执行网络请求,所以把英雄数据的获取逻辑移到doInBackground里是完全正确的方向,不过我们可以再优化这个方案,同时也给你提供另一种更高效的Data Dragon JSON映射方案。
方案一:优化AsyncTask中的API调用
你修改后的代码已经把英雄数据请求放到了doInBackground,但可以考虑缓存英雄数据,避免多次重复请求同一个英雄的信息(比如召唤师连续玩了同一个英雄):
class Wrapper { Summoner summoner; MatchList matchList; RiotApi api; Map<Integer, String> championKeyToNameMap; // 新增缓存映射 } @SuppressLint("StaticFieldLeak") public class FetchMatchListTask extends AsyncTask<String, Void, Wrapper> { @Override protected Wrapper doInBackground(String... params) { ApiConfig config = new ApiConfig().setKey("MY-API-KEY"); Wrapper wrapper = new Wrapper(); wrapper.api = new RiotApi(config); wrapper.championKeyToNameMap = new HashMap<>(); // 初始化缓存 try { wrapper.summoner = wrapper.api.getSummonerByName(Platform.EUNE, params[0]); wrapper.matchList = wrapper.api.getMatchListByAccountId(Platform.EUNE, wrapper.summoner.getAccountId()); if (wrapper.matchList.getMatches() != null) { int matchLimitCounter = 0; for (MatchReference match : wrapper.matchList.getMatches()) { if(matchLimitCounter == 10) break; matchLimitCounter++; int championKey = match.getChampion(); String champName; // 先查缓存,不存在再请求API if (wrapper.championKeyToNameMap.containsKey(championKey)) { champName = wrapper.championKeyToNameMap.get(championKey); } else { Champion champion = wrapper.api.getDataChampion(Platform.EUNE, championKey); champName = champion.getName(); wrapper.championKeyToNameMap.put(championKey, champName); // 存入缓存 } Match myMatch = new Match(wrapper.summoner.getProfileIconId(), champName, championKey, "lala"); matchList.add(myMatch); } } return wrapper; } catch (RiotApiException e) { Log.d("RiotMSG", "Blad: " + RiotApiException.getMessage(e.getErrorCode())); e.printStackTrace(); } return null; } @Override protected void onPostExecute(Wrapper wrapp) { super.onPostExecute(wrapp); if(wrapp != null) { numOfGamesText.setText(String.valueOf(wrapp.matchList.getTotalGames())); // 这里可以用matchList里的champName通过Picasso加载图片 // 比如:Picasso.get().load("http://ddragon.leagueoflegends.com/cdn/9.22.1/img/champion/" + champName + ".png").into(imageView); } else { throw new NullPointerException("wrapp.matchList object is null"); } } }
这样做的好处是减少不必要的API调用,避免触发Riot API的速率限制,同时保证所有网络操作都在后台线程执行。
方案二:解析Data Dragon的champion.json实现本地映射
如果想完全避免调用英雄信息API,可以直接下载Data Dragon的champion.json文件,解析后建立英雄Key到名称的本地映射,这是更高效的方案:
步骤1:下载并解析champion.json
你可以在应用启动时(或者首次运行时)下载对应的JSON文件,然后解析成一个全局的Map<Integer, String>:
// 可以放在Application类或者单例类中初始化 public class ChampionNameMapper { private static Map<Integer, String> keyToNameMap; public static void init(Context context) { // 这里可以用OkHttp或者Volley下载JSON,注意要在后台线程执行 new AsyncTask<Void, Void, Map<Integer, String>>() { @Override protected Map<Integer, String> doInBackground(Void... voids) { Map<Integer, String> map = new HashMap<>(); try { URL url = new URL("http://ddragon.leagueoflegends.com/cdn/9.22.1/data/en_US/champion.json"); BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream())); StringBuilder sb = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { sb.append(line); } reader.close(); // 用Gson解析JSON JsonObject jsonObject = new JsonParser().parse(sb.toString()).getAsJsonObject(); JsonObject data = jsonObject.getAsJsonObject("data"); for (String champKey : data.keySet()) { JsonObject champData = data.getAsJsonObject(champKey); int key = champData.get("key").getAsInt(); String name = champData.get("name").getAsString(); map.put(key, name); } } catch (IOException e) { e.printStackTrace(); } return map; } @Override protected void onPostExecute(Map<Integer, String> map) { keyToNameMap = map; } }.execute(); } public static String getChampionName(int key) { return keyToNameMap != null ? keyToNameMap.get(key) : null; } }
步骤2:在AsyncTask中直接使用映射
这样在doInBackground里就不需要调用英雄API了,直接通过映射获取名称:
@Override protected Wrapper doInBackground(String... params) { ApiConfig config = new ApiConfig().setKey("MY-API-KEY"); Wrapper wrapper = new Wrapper(); wrapper.api = new RiotApi(config); try { wrapper.summoner = wrapper.api.getSummonerByName(Platform.EUNE, params[0]); wrapper.matchList = wrapper.api.getMatchListByAccountId(Platform.EUNE, wrapper.summoner.getAccountId()); if (wrapper.matchList.getMatches() != null) { int matchLimitCounter = 0; for (MatchReference match : wrapper.matchList.getMatches()) { if(matchLimitCounter == 10) break; matchLimitCounter++; int championKey = match.getChampion(); String champName = ChampionNameMapper.getChampionName(championKey); Match myMatch = new Match(wrapper.summoner.getProfileIconId(), champName, championKey, "lala"); matchList.add(myMatch); } } return wrapper; } catch (RiotApiException e) { Log.d("RiotMSG", "Blad: " + RiotApiException.getMessage(e.getErrorCode())); e.printStackTrace(); } return null; }
这种方案的优势是完全避免英雄信息API的调用,节省API配额,而且本地查询速度更快,适合需要频繁获取英雄名称的场景。
注意事项
- 如果你使用方案二,记得要处理JSON版本的更新问题(比如LOL版本更新后,Data Dragon的CDN版本号也要对应修改),可以通过Riot的API获取最新版本号后再下载对应JSON。
- 不管用哪种方案,加载图片时都要确保用Picasso/Glide等图片加载库,它们会自动在后台线程处理图片下载,避免主线程阻塞。
内容的提问来源于stack exchange,提问作者1haker
相关产品推荐
相关产品推荐

