Kotlin Android应用JsonArrayRequest获响应后崩溃问题排查
问题排查:Android Kotlin应用JSON请求后RecyclerView崩溃
错误日志
FATAL EXCEPTION: main Process: com.example.scoremusplayer, PID: 19239 java.lang.NullPointerException: Attempt to invoke virtual method 'android.content.Context android.content.Context.getApplicationContext()' on a null object at android.content.ContextWrapper.getApplicationContext(ContextWrapper.java:122) at com.bumptech.glide.Glide.get(Glide.java:128) at com.bumptech.glide.Glide.getRetriever (Glide.java:510) at com.bumptech.glide.Glide.with(Glide.java:564) at com.example.scoremusplayer.scoreExplore.adapter.AdapterForScoreListForAllScoreFragmentForRecyclerView.onBindViewHolder at com.example.scoremusplayer.scoreExplore.adapter.AdapterForScoreListForAllScoreFragmentForRecyclerView.onBindViewHolder at androidx.recyclerview.widget.RecyclerView$Adapter.onBindViewHolder (RecyclerView.java:7065) at androidx.recyclerview.widget.RecyclerView$Adapter.bindViewHolder (RecyclerView.java:7107) at androidx.recyclerview.widget.RecyclerView$Recycler.tryBindViewHolderByDeadline (RecyclerView.java:6012) at androidx.recyclerview.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline (RecyclerView.java:6279) at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition (RecyclerView.java:6118) at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition (RecyclerView.java:6114) at androidx.recyclerview.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:2303) at androidx.recyclerview.widget.GridLayoutManager.LayoutChunk (GridLayoutManager.java:561) at androidx.recyclerview.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1587)
相关代码
Fragment代码
class AllScoresFragment : Fragment() { companion object { lateinit var tempList : ArrayList<Score> fun newInstance() = AllScoresFragment() } private lateinit var binding: FragmentAllScoresBinding private lateinit var viewModel: AllScoresViewModel private lateinit var progressDialog : ProgressDialog override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { binding = FragmentAllScoresBinding.inflate(inflater) return binding.root // return inflater.inflate(R.layout.fragment_all_scores, container, false) } @Deprecated("Deprecated in Java") override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) viewModel = ViewModelProvider(this)[AllScoresViewModel::class.java] binding.include.recyclerViewForScoreContent.setHasFixedSize(true) binding.include.recyclerViewForScoreContent.layoutManager = GridLayoutManager(context, 3) retrieveTwo() binding.swipeToRefresh.setOnRefreshListener { retrieveTwo() binding.swipeToRefresh.isRefreshing = false } } }
retrieveTwo()函数代码
fun retrieveTwo() { tempList = ArrayList() progressDialog = ProgressDialog(context) progressDialog.setMessage("Loading...") progressDialog.show() val pdfSiteUrl = "https://..." // Get a RequestQueue AllScoresFragment().context?.let { MySingleton.getInstance(it).requestQueue } val jsonArrayRequest = JsonArrayRequest( Request.Method.GET, pdfSiteUrl, null, { response -> //Toast the response to know if response is actually gotten Toast.makeText(context, response.toString(), Toast.LENGTH_LONG).show() var jo : JSONObject try { for (i in 0 until response.length()){ jo = response.getJSONObject(i) val id = jo.getInt("id") val name:String = jo.getString("name") val composer:String = jo.getString("composer") val style:String = jo.getString("style") val theme:String = jo.getString("theme") val album:String = jo.getString("album") val lang:String = jo.getString("lang") val thumbnailUrl:String = jo.getString("thumbnail_url") val pdfUrl: String = jo.getString("pdf_url") val score = Score( id = id, name = name, composer = composer, style = style, theme = theme, album = album, lang = lang, thumbnail_url = pdfSiteUrl + thumbnailUrl, pdf_url = pdfSiteUrl + pdfUrl ) tempList.add(score) val adapter = context?.let { AdapterForScoreListForAllScoreFragmentForRecyclerView(it, tempList) } binding.include.recyclerViewForScoreContent.adapter = adapter progressDialog.hide() } } catch (e : JSONException){ Toast.makeText(context, e.message, Toast.LENGTH_LONG).show() progressDialog.hide() } }, { error -> Toast.makeText(context, error.message, Toast.LENGTH_SHORT) .show() progressDialog.hide() } ) // Access the RequestQueue through my singleton class. context?.let { MySingleton.getInstance(it).addToRequestQueue(jsonArrayRequest) } }
RecyclerView Adapter代码
class AdapterForScoreListForAllScoreFragmentForRecyclerView( private val context: Context, private val scoreList: ArrayList<Score> ) : RecyclerView.Adapter<AdapterForScoreListForAllScoreFragmentForRecyclerView.ViewHolder>() { class ViewHolder(binding: ScoreListForRecycleBinding) : RecyclerView.ViewHolder(binding.root){ val pdfThumbnailImage = binding.thumbnailForScore val songTitle = binding.scoreTitleNameOfAllScoresFragment val composerName = binding.nameOfComposerForAllScoresFragment val genre = binding.styleForAllScoresFragment //val for root val root = binding.root } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) : ViewHolder { val layoutInflater = LayoutInflater.from(parent.context) val scoreList = ScoreListForRecycleBinding.inflate(layoutInflater, parent, false) return ViewHolder(scoreList) } override fun getItemCount(): Int { return scoreList.size } override fun onBindViewHolder(holder: ViewHolder, position: Int) { val scoreName = scoreList[position].name val scoreComposer = scoreList[position].composer val genre = scoreList[position].style val pdfUrl = scoreList[position].pdf_url val thumbnailImage = scoreList[position].thumbnail_url holder.songTitle.text = scoreName holder.composerName.text = scoreComposer holder.genre.text = genre if (pdfUrl.isNotEmpty()){ Glide .with(FragmentActivity()) .load(thumbnailImage) .placeholder(R.drawable.scoremus_icon_slash) .into(holder.pdfThumbnailImage) } holder.root.setOnClickListener { holder.root.isLongClickable = true Toast.makeText( context, "opening \"${scoreName.uppercase()} composed by $scoreComposer\"...", Toast.LENGTH_SHORT) .show() val intent = Intent(context, PdfActivity::class.java) intent.putExtra("index", position) intent.putExtra("pdfPath", scoreList[position].pdf_url) ContextCompat.startActivity(context, intent, null) } } }
问题分析与修复
1. 直接崩溃原因:Glide初始化错误
Adapter的onBindViewHolder中,手动创建的FragmentActivity()实例未被系统初始化,内部context为null,导致Glide调用getApplicationContext()时抛出空指针。
修复:
使用Adapter传入的context或ViewHolder的itemView上下文:
if (pdfUrl.isNotEmpty()){ Glide.with(context) // 或 holder.itemView.context .load(thumbnailImage) .placeholder(R.drawable.scoremus_icon_slash) .into(holder.pdfThumbnailImage) }
2. RecyclerView Adapter重复设置问题
在retrieveTwo()的循环里,每添加一条数据就重新创建并设置Adapter,会导致RecyclerView频繁刷新,性能低下且可能引发异常。
修复:
将Adapter初始化逻辑移到循环外部,循环仅负责添加数据,最后统一设置Adapter:
try { for (i in 0 until response.length()){ // 解析数据并添加到tempList的代码 } // 循环结束后初始化并设置Adapter val adapter = context?.let { AdapterForScoreListForAllScoreFragmentForRecyclerView(it, tempList) } binding.include.recyclerViewForScoreContent.adapter = adapter progressDialog.hide() } catch (e : JSONException){ Toast.makeText(context, e.message, Toast.LENGTH_LONG).show() progressDialog.hide() }
更优方案:提前初始化Adapter,添加数据后调用notifyDataSetChanged()更新UI,避免重复创建Adapter实例。
3. 无用代码清理
retrieveTwo()中AllScoresFragment().context?.let { MySingleton.getInstance(it).requestQueue }这行代码完全无效,新创建的Fragment实例context为null,且未执行任何实际操作,直接删除即可。
4. Context空安全优化
Fragment中使用requireContext()代替context,确保上下文非空,避免潜在的空指针:
progressDialog = ProgressDialog(requireContext()) Toast.makeText(requireContext(), response.toString(), Toast.LENGTH_LONG).show()
5. 全局tempList风险修复
伴生对象中的lateinit var tempList是全局变量,易引发内存泄漏或数据混乱,建议将其移至Fragment内部作为成员变量:
class AllScoresFragment : Fragment() { private lateinit var tempList : ArrayList<Score> // 移到这里 companion object { fun newInstance() = AllScoresFragment() } // ... 其他代码 }
内容的提问来源于stack exchange,提问作者BaffySarp
相关产品推荐
相关产品推荐

