You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 02:15:43