Volley异步请求+AsyncTask仍触发ANR及response识别问题排查
问题背景
我是首次使用JSON、Volley和AsyncTask的Android开发者,要完成一个编码挑战:从URL下载JSON数组到应用,编辑过滤后展示成列表。
问题1:ANR仍出现
最初我在Volley的JsonArrayRequest的onResponse方法里把JSON数据转成Java对象并插入Room数据库,结果触发了ANR;后来把转换和入库逻辑移到AsyncTask里,还是会出现ANR。
现象:只有应用首次启动时会ANR(后续启动因为数据库已经有数据就不会出现),ANR期间数据库还在填充数据,进度条只闪一下就消失,感觉AsyncTask的onPostExecute提前执行了。
控制器的onCreate方法
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder() .detectLeakedClosableObjects() .penaltyLog() .build()); repository = new Repository(getApplication()); super.onCreate(savedInstanceState); setContentView(R.layout.activity_item_list_screen); itemRV = findViewById(R.id.itemListRecyclerView); progressBar = findViewById(R.id.progressBar1); RequestQueue queue1 = Volley.newRequestQueue(ItemListScreen.this); queue = queue1; final ItemAdapter itemAdapter = new ItemAdapter(this); getSupportActionBar().setDisplayHomeAsUpEnabled(true); RecyclerView recyclerView = findViewById(R.id.itemListRecyclerView); recyclerView.setAdapter(itemAdapter); recyclerView.setLayoutManager(new LinearLayoutManager(this)); if(repository.getAllItems().isEmpty()) { //moves the download of the json file offof the UI thread startAsyncTask(); } //fills GUI table with database data items = repository.getAllItems(); itemAdapter.setItems(items);
数据获取及入库方法
private void getData () throws InterruptedException { //fills database with JSON objects JsonArrayRequest jsonArrayRequest = new JsonArrayRequest(Request.Method.GET, url, null, new Response.Listener<JSONArray>() { @Override public void onResponse(JSONArray response) { for (int i = 0; i < response.length(); i++) { try { JSONObject responseObj = response.getJSONObject(i); int id = responseObj.getInt("id"); int listId = responseObj.getInt("listId"); String name = responseObj.getString("name"); Item item = new Item(id, listId, name); repository.insert(item); } catch (JSONException e) { throw new RuntimeException(e); } } } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { Toast.makeText(ItemListScreen.this, "Failed to get the data", Toast.LENGTH_SHORT).show(); } }); //Thread.sleep(1000); queue.add(jsonArrayRequest); }
AsyncTask实现
public void startAsyncTask(){ ItemsAsyncTask itemsAsyncTask = new ItemsAsyncTask(this); //not sure what this integerparameter does but a youtube video said this was very important to have. itemsAsyncTask.execute(10); } private static class ItemsAsyncTask extends AsyncTask<Integer, Integer, String>{ private WeakReference<ItemListScreen> reference; ItemsAsyncTask(ItemListScreen activity){ reference = new WeakReference<ItemListScreen>(activity); } @Override protected void onPreExecute() { super.onPreExecute(); ItemListScreen activity = reference.get(); if (activity == null || activity.isFinishing()){ return; } //This should begin the progress bar on the screen, which should continue throughout the doInBackground method. activity.itemRV.setVisibility(View.INVISIBLE); activity.progressBar.setVisibility(View.VISIBLE); } @Override protected String doInBackground(Integer... integers) { //this calls the getData method that performs the data transfer away from the UI thread. try { ItemListScreen activity = reference.get(); if (activity == null || activity.isFinishing()){ return "return"; } activity.getData(); } catch (InterruptedException e) { throw new RuntimeException(e); } return "return"; } @Override protected void onPostExecute(String s) { //After the doInBackground method is complete, this should remove the progress bar // and then continue on to the rest of the OnCreate method where the GUI table will be filled witht he database objects super.onPostExecute(s); ItemListScreen activity = reference.get(); if (activity == null || activity.isFinishing()){ return; } activity.itemRV.setVisibility(View.VISIBLE); activity.progressBar.setVisibility(View.INVISIBLE); } }
问题2:response识别失败
按照建议修改代码后,又出现新问题:通过弱引用获取Activity后,代码识别不了response对象,相关代码如下:
@Override protected String doInBackground(Integer... integers) { //this calls the getData method that performs the data transfer away from the UI thread. try { ItemListScreen activity = reference.get(); if (activity == null || activity.isFinishing()){ return "return"; } for (int i = 0; i < activity.response.length(); i++) { try { JSONObject responseObj = activity.response.getJSONObject(i); int id = responseObj.getInt("id"); int listId = responseObj.getInt("listId"); String name = responseObj.getString("name"); Item item = new Item(id, listId, name); activity.repository.insert(item); } catch (JSONException e) { throw new RuntimeException(e); } } } catch (InterruptedException e) { throw new RuntimeException(e); } return "return"; }
解决方案
针对问题1:ANR仍出现的原因及解决
原因
- Volley回调运行在UI线程:你把
getData()放到AsyncTask的doInBackground里,但Volley的onResponse回调本身就是在UI线程执行的,里面的循环插入数据库操作直接阻塞了UI,导致ANR。 - AsyncTask提前结束:
getData()只是把请求加入Volley队列就返回了,doInBackground执行完activity.getData()就直接return,触发onPostExecute,所以进度条提前消失,但此时Volley请求还在后台处理,数据库插入仍在UI线程运行,继续阻塞引发ANR。 - Room插入未异步:如果你的
repository.insert(item)是同步方法,大量插入操作在UI线程执行会直接卡死界面。
解决步骤
推荐用Coroutine替代AsyncTask(AsyncTask已被官方废弃),同时确保所有耗时操作都在后台线程执行:
- 添加CoroutineScope到Activity
private CoroutineScope scope = CoroutineScope(Dispatchers.Main + Job());
- 修改
getData()方法,将数据处理移到IO线程
private void getData() { JsonArrayRequest jsonArrayRequest = new JsonArrayRequest(Request.Method.GET, url, null, response -> { // 在IO线程处理数据转换和数据库插入 scope.launch(Dispatchers.IO) { for (int i = 0; i < response.length(); i++) { try { JSONObject responseObj = response.getJSONObject(i); int id = responseObj.getInt("id"); int listId = responseObj.getInt("listId"); String name = responseObj.getString("name"); Item item = new Item(id, listId, name); repository.insert(item); } catch (JSONException e) { e.printStackTrace(); } } // 回到主线程更新UI和列表 launch(Dispatchers.Main) { progressBar.setVisibility(View.INVISIBLE); itemRV.setVisibility(View.VISIBLE); items = repository.getAllItems(); itemAdapter.setItems(items); } } }, error -> { Toast.makeText(ItemListScreen.this, "Failed to get the data", Toast.LENGTH_SHORT).show(); progressBar.setVisibility(View.INVISIBLE); itemRV.setVisibility(View.VISIBLE); }); queue.add(jsonArrayRequest); }
- 修改启动逻辑,去掉AsyncTask
if(repository.getAllItems().isEmpty()) { itemRV.setVisibility(View.INVISIBLE); progressBar.setVisibility(View.VISIBLE); getData(); } else { items = repository.getAllItems(); itemAdapter.setItems(items); }
- 确保Room插入异步
如果用Java,给Repository的insert方法套一层AsyncTask:
public void insert(Item item) { new InsertAsyncTask(itemDao).execute(item); } private static class InsertAsyncTask extends AsyncTask<Item, Void, Void> { private ItemDao itemDao; InsertAsyncTask(ItemDao itemDao) { this.itemDao = itemDao; } @Override protected Void doInBackground(Item... items) { itemDao.insert(items[0]); return null; } }
针对问题2:response识别失败的原因及解决
原因
response是Volley回调里的局部变量,你并没有把它定义成Activity的成员变量,所以activity.response自然找不到这个字段。
解决步骤
不要试图从Activity直接获取response,而是在Volley的onResponse回调里拿到response后,直接传给后台处理逻辑。如果坚持用AsyncTask,可以把JSONArray作为参数传给AsyncTask:
- 修改Volley的
onResponse回调
@Override public void onResponse(JSONArray response) { new InsertItemsAsyncTask(repository, ItemListScreen.this).execute(response); }
- 重新定义AsyncTask
private static class InsertItemsAsyncTask extends AsyncTask<JSONArray, Void, Void> { private Repository repository; private WeakReference<ItemListScreen> activityRef; InsertItemsAsyncTask(Repository repository, ItemListScreen activity) { this.repository = repository; this.activityRef = new WeakReference<>(activity); } @Override protected void onPreExecute() { super.onPreExecute(); ItemListScreen activity = activityRef.get(); if (activity != null && !activity.isFinishing()) { activity.itemRV.setVisibility(View.INVISIBLE); activity.progressBar.setVisibility(View.VISIBLE); } } @Override protected Void doInBackground(JSONArray... jsonArrays) { JSONArray response = jsonArrays[0]; for (int i = 0; i < response.length(); i++) { try { JSONObject responseObj = response.getJSONObject(i); int id = responseObj.getInt("id"); int listId = responseObj.getInt("listId"); String name = responseObj.getString("name"); Item item = new Item(id, listId, name); repository.insert(item); } catch (JSONException e) { e.printStackTrace(); } } return null; } @Override protected void onPostExecute(Void unused) { super.onPostExecute(unused); ItemListScreen activity = activityRef.get(); if (activity != null && !activity.isFinishing()) { activity.progressBar.setVisibility(View.INVISIBLE); activity.itemRV.setVisibility(View.VISIBLE); List<Item> items = activity.repository.getAllItems(); activity.itemAdapter.setItems(items); } } }
内容的提问来源于stack exchange,提问作者jwy205
相关产品推荐
相关产品推荐

