Volley解析JSON字符串数组至全局变量后为空,多选对话框失效
问题分析与解决方案
嘿,这是异步网络请求的经典坑!咱们先搞清楚为什么category数组在onCreateDialog里是null:
核心原因:Volley请求的异步特性
你在onAttach里调用了getJobArray(),但Volley的网络请求是异步执行的——也就是说,onCreateDialog会在网络请求返回数据、填充category数组之前就被系统调用了。这时候category还没被初始化,自然就是null,连长度都打印不出来。
解决方案一:数据加载完成后再构建对话框
这个方案比较直接,先给用户显示一个加载中的对话框,等网络请求完成拿到数据后,再替换成多选对话框:
修改后的代码:
首先调整onCreateDialog返回加载对话框:
@NonNull @Override public Dialog onCreateDialog(Bundle savedInstanceState) { ProgressDialog progressDialog = new ProgressDialog(getActivity()); progressDialog.setMessage("加载分类中..."); progressDialog.setCancelable(false); return progressDialog; }
然后在getJobArray的onResponse方法里,完成数组填充后调用更新对话框的方法:
private void getJobArray(){ String url = "http://api_staging.jab.poweredby.cnddts.at/mobile/metadata/categories?portal=1"; JsonObjectRequest request = new JsonObjectRequest(com.android.volley.Request.Method.GET, url, null, new com.android.volley.Response.Listener<JSONObject>() { @Override public void onResponse(JSONObject response) { try { JSONArray jsonArray = response.getJSONArray("categories"); category = new String[jsonArray.length()]; Log.d(TAG, "onResponse: 开始填充数组"); Log.d(TAG, "onResponse: 数组长度" + jsonArray.length()); for(int i = 0; i < jsonArray.length(); i++){ JSONObject categories = jsonArray.getJSONObject(i); category[i] = categories.getString("name"); Log.d(TAG, "onResponse: 填充内容 " + category[i]); } // 数据加载完成,更新对话框 updateDialogWithCategories(); } catch (JSONException e) { e.printStackTrace(); Log.d(TAG, "onResponse: JSON解析出错"); // 加载失败,提示用户并关闭对话框 dismiss(); Toast.makeText(mContext, "分类加载失败", Toast.LENGTH_SHORT).show(); } } }, new com.android.volley.Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { error.printStackTrace(); dismiss(); Toast.makeText(mContext, "网络请求失败", Toast.LENGTH_SHORT).show(); } }); mRequestQ.add(request); } // 构建多选对话框并替换当前加载框 private void updateDialogWithCategories() { final ArrayList<String> selectedItemList = new ArrayList<>(); AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); builder.setTitle("选择分类") .setMultiChoiceItems(category, null, new DialogInterface.OnMultiChoiceClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i, boolean b) { if(b){ selectedItemList.add(category[i]); }else{ selectedItemList.remove(category[i]); } } }) .setPositiveButton("确定", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { mListener.onPositiveClicked(category,selectedItemList); } }) .setNegativeButton("取消", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { mListener.onNegativeButtonClicked(); } }); // 替换当前对话框 Dialog newDialog = builder.create(); setShowsDialog(false); dismiss(); setShowsDialog(true); newDialog.show(); }
解决方案二:用ViewModel+LiveData管理异步数据(更推荐)
如果想让代码更符合Android Jetpack架构,避免内存泄漏,用ViewModel+LiveData是更好的选择:
第一步:创建CategoryViewModel
public class CategoryViewModel extends ViewModel { private MutableLiveData<String[]> categoryLiveData = new MutableLiveData<>(); private RequestQueue requestQueue; public void loadCategories(Context context) { requestQueue = Volley.newRequestQueue(context); String url = "http://api_staging.jab.poweredby.cnddts.at/mobile/metadata/categories?portal=1"; JsonObjectRequest request = new JsonObjectRequest(Request.Method.GET, url, null, response -> { try { JSONArray jsonArray = response.getJSONArray("categories"); String[] category = new String[jsonArray.length()]; for(int i = 0; i < jsonArray.length(); i++){ JSONObject categories = jsonArray.getJSONObject(i); category[i] = categories.getString("name"); } categoryLiveData.setValue(category); } catch (JSONException e) { e.printStackTrace(); } }, error -> error.printStackTrace()); requestQueue.add(request); } public LiveData<String[]> getCategoryLiveData() { return categoryLiveData; } @Override protected void onCleared() { super.onCleared(); // 页面销毁时取消所有请求,避免内存泄漏 if(requestQueue != null) { requestQueue.cancelAll(request -> true); } } }
第二步:修改DialogFragment使用ViewModel
public class MultipleChoiceDialogFragment extends DialogFragment { private static String TAG = "xd"; private onMultiChoiceListener mListener; private CategoryViewModel viewModel; public interface onMultiChoiceListener{ void onPositiveClicked(String[] list,ArrayList<String> selectedItemList); void onNegativeButtonClicked(); } @Override public void onAttach(Context context) { super.onAttach(context); try { mListener = (onMultiChoiceListener) context; }catch (Exception e){ throw new ClassCastException(getActivity() + " 必须实现onMultiChoiceListener接口"); } } @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); viewModel = new ViewModelProvider(this).get(CategoryViewModel.class); viewModel.loadCategories(getContext()); } @NonNull @Override public Dialog onCreateDialog(Bundle savedInstanceState) { ProgressDialog progressDialog = new ProgressDialog(getActivity()); progressDialog.setMessage("加载分类中..."); progressDialog.setCancelable(false); // 观察数据变化,数据就绪后更新对话框 viewModel.getCategoryLiveData().observe(this, categories -> { if(categories != null) { updateDialogWithCategories(categories); } }); return progressDialog; } private void updateDialogWithCategories(String[] category) { final ArrayList<String> selectedItemList = new ArrayList<>(); AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); builder.setTitle("选择分类") .setMultiChoiceItems(category, null, (dialogInterface, i, b) -> { if(b){ selectedItemList.add(category[i]); }else{ selectedItemList.remove(category[i]); } }) .setPositiveButton("确定", (dialogInterface, i) -> { mListener.onPositiveClicked(category,selectedItemList); }) .setNegativeButton("取消", (dialogInterface, i) -> { mListener.onNegativeButtonClicked(); }); Dialog newDialog = builder.create(); setShowsDialog(false); dismiss(); setShowsDialog(true); newDialog.show(); } }
关键提醒
- 永远记住:网络请求是异步的,不要假设UI回调(比如
onCreateDialog)执行时,异步任务已经完成 - 方案二更适合长期维护,ViewModel会自动管理生命周期,避免内存泄漏
- 一定要处理网络请求失败的情况,给用户友好的提示,不要让用户一直等加载框
内容的提问来源于stack exchange,提问作者David Pazouki
相关产品推荐
相关产品推荐

