阅读 3518

如何在Adapter中优雅的使用Context

1. 前言

最近我在项目中,发现一个同事写 RecyclerView.Adapter 的时候都习惯定义一个全局的私有 mContext,然后在 onCreateViewHolder(parent: ViewGroup, viewType: Int) 方法中进行赋值操作 mContext = parent.context

如下:

class MyAdapter(private val dataList: List<MyData>) : RecyclerView.Adapter<MyAdapter.MyViewHolder>() {
    private lateinit var mContext: Context

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
    	//在这里进行context的赋值
        mContext = parent.context
        val view = LayoutInflater.from(parent.context).inflate(R.layout.rcy_item_view, parent, false)
        return MyViewHolder(view)
    }

    override fun getItemCount(): Int {}

    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {}
}
复制代码

WT,还可以这么操作!!第一次见到这样的写法,有点意思,由此有了这一篇文章。

2. 获取到 Context 的四种方式

很多时候我们需要在 RecyclerView.Adapter 中使用到 context,比如:利用 Glide 来加载网络图片的时候。

这时,我们该如何拿到 context 给 Glide 呢?

2.1 通过 Adapter 构造函数传入 Context

这是我之前最常用的一种方式,通过 Adapter 构造函数将当前Activity Context传进来,如下:

class MyAdapter(
    private val context: Context,
    private val dataList: List<MyData>
) : RecyclerView.Adapter<MyAdapter.MyViewHolder>() {

    class MyViewHolder(val binding: RcyItemViewBinding) :
        RecyclerView.ViewHolder(binding.root) {
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
        val binding = RcyItemViewBinding.inflate(
            LayoutInflater.from(parent.context),
            parent,
            false
        )
        return MyViewHolder(binding)
    }

    override fun getItemCount(): Int = dataList.size

    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
        val data = dataList[position]
        holder.binding.apply {
            Glide.with(context).load(data.imageUrl).into(holder.binding.imageIv)
            holder.binding.contentTv.text = data.content
        }
    }
}
复制代码

2.2 通过 Parent.context 获取

这个也是文章开头中提到的我的同事的一种写法,如下:

class MyAdapter(private val dataList: List<MyData>) :
    RecyclerView.Adapter<MyAdapter.MyViewHolder>() {

    private lateinit var mContext: Context

    class MyViewHolder(val binding: RcyItemViewBinding) :
        RecyclerView.ViewHolder(binding.root) {

    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
        mContext = parent.context
        val binding = RcyItemViewBinding.inflate(
            LayoutInflater.from(parent.context),
            parent,
            false
        )
        return MyViewHolder(binding)
    }

    override fun getItemCount(): Int = dataList.size

    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
        val data = dataList[position]
        holder.binding.apply {
            Glide.with(mContext).load(data.imageUrl).into(holder.binding.imageIv)
            holder.binding.contentTv.text = data.content
        }
    }
}
复制代码

2.3 通过 onAttachedToRecyclerView() 方法获取

对于2.2 的方法,通过 parent.context 对 mContext 进行赋值,有人说,不可以这么操作!这样会导致内存泄露!!(留个疑问?你觉得2.2方法这样操作会导致内存泄漏吗?) 所以你需要覆写 onAttachedToRecyclerView(recyclerView: RecyclerView) 方法,在这里对 mContext 进行赋值。如下:

class MyAdapter(private val dataList: List<MyData>) :
    RecyclerView.Adapter<MyAdapter.MyViewHolder>() {

    private lateinit var mContext: Context

    override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
        super.onAttachedToRecyclerView(recyclerView)
        mContext = recyclerView.context
    }

    class MyViewHolder(val binding: RcyItemViewBinding) :
        RecyclerView.ViewHolder(binding.root) {

    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
        val binding = RcyItemViewBinding.inflate(
            LayoutInflater.from(parent.context),
            parent,
            false
        )
        return MyViewHolder(binding)
    }

    override fun getItemCount(): Int = dataList.size

    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
        val data = dataList[position]
        holder.binding.apply {
            Glide.with(mContext).load(data.imageUrl).into(holder.binding.imageIv)
            holder.binding.contentTv.text = data.content
        }
    }
}
复制代码

2.4 通过 ImageView 获取 context

当当就我们举的这个例子,因为我们需要用到 Glide 来展示网络图片,所以我们需要传递 Context 给 Glide,其实我们可以直接通过 ImageView 来拿到 context,然后传给 Glide,如下:

class MyAdapter(private val dataList: List<MyData>) :
    RecyclerView.Adapter<MyAdapter.MyViewHolder>() {

    class MyViewHolder(val binding: RcyItemViewBinding) :
        RecyclerView.ViewHolder(binding.root) {

    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
        val binding = RcyItemViewBinding.inflate(
            LayoutInflater.from(parent.context),
            parent,
            false
        )
        return MyViewHolder(binding)
    }

    override fun getItemCount(): Int = dataList.size

    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
        val data = dataList[position]
        holder.binding.apply {
            Glide.with(imageIv.context).load(data.imageUrl).into(holder.binding.imageIv)
            holder.binding.contentTv.text = data.content
        }
    }
}
复制代码

3. 问题的本质

上面介绍了四种方法来获取 Context,想必大家都想弄清楚上面几种方法有什么区别吧,那我们就打印他们获取到的 context 出来瞧瞧看吧,如下:

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding
    
    ···
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        Log.e("jctest", "onCreate: this::class.java = ${this::class.java}")
        Log.e("jctest", "onCreate: binding.recyclerView.context::class.java = ${binding.recyclerView.context::class.java}")
        val adapter = MyAdapter(listData)
        binding.recyclerView.adapter = adapter

    }

    class MyAdapter(private val dataList: List<MyData>) :
        RecyclerView.Adapter<MyAdapter.MyViewHolder>() {

        class MyViewHolder(val binding: RcyItemViewBinding) :
            RecyclerView.ViewHolder(binding.root) {

        }

        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
            Log.e("jctest", "onCreateViewHolder: parent::class.java = ${parent::class.java}")
            Log.e("jctest", "onCreateViewHolder: parent.context::class.java = ${parent.context::class.java}")
            val binding = RcyItemViewBinding.inflate(
                LayoutInflater.from(parent.context),
                parent,
                false
            )
            return MyViewHolder(binding)
        }

        override fun getItemCount(): Int = dataList.size

        override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
            val data = dataList[position]
            holder.binding.apply {
                Log.e("jctest", "onBindViewHolder: imageIv.context::class.java = ${imageIv.context::class.java}")
                Glide.with(imageIv.context).load(data.imageUrl).into(holder.binding.imageIv)
                holder.binding.contentTv.text = data.content
            }
        }

        override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
            super.onAttachedToRecyclerView(recyclerView)
            Log.e("jctest", "onAttachedToRecyclerView: recyclerView.context::class.java = ${recyclerView.context::class.java}")
        }

    }

}
复制代码

打印出来的 Log 如下:

E/jctest: onCreate: this::class.java = class com.jc.test.MainActivity
E/jctest: onCreate: binding.recyclerView.context::class.java = class com.jc.test.MainActivity
E/jctest: onAttachedToRecyclerView: recyclerView.context::class.java = class com.jc.test.MainActivity
E/jctest: onCreateViewHolder: parent::class.java = class androidx.recyclerview.widget.RecyclerView
E/jctest: onCreateViewHolder: parent.context::class.java = class com.jc.test.MainActivity
E/jctest: onBindViewHolder: imageIv.context::class.java = class com.jc.test.MainActivity
复制代码

通过 Log,真的就一目了然了,这四种方法虽然看着写法很不一样,但是其获取到的 context 其实都是同一个,那就是 MainActivity。

我们再来看看这四个方法:

  • 2.1 通过 Adapter 构造函数传入 this,这很明显,就是 MainActivity。
  • 2.2 通过 Parent.context 获取,通过 Log 我们可以知道,parent 就是 recylerView,所以获取的是 recyclerView 运行于的上下文,也就是 MainActivity。
  • 2.3 通过 onAttachedToRecyclerView() 获取,也是获取 recyclerView 运行于的上下文,即 MainActivity。
  • 2.4 通过 ImageView 获取,获取的是 ImageView 运行于的上下文,ImageView 是运行于 recyclerView 上的,所以获取的也是 MainActivity。

3.1 到底应该怎么用呢?

刚刚,我们通过打印 Log 知道了这四种方法获取到的 Context 其实都是同一个,那~~,这四种方法又该如何来取舍呢?到底用哪一种方法比较好呢?

文章开头中,我说 2.1 是我之前最常用的一个方法,那我现在为什么不用它了呢??

原因很简单,那就是因为之前我不知道可以直接在 Adapter 内部直接获取到 Context 😅,既然可以在类内部直接获取到的参数,完全就没有必要再写一个参数从外部导入,这样会让代码看着更加的简洁。

还记得文章中留的那个疑问吗 -> 有人说通过 2.2 parent.context 方法获取 context 会导致内存泄漏,你认同吗?

该写法不会导致内存泄漏,但是我也不推荐这样的写法,毕竟 onCreateViewHolder() 方法的执行次数是由 itemCount 所决定的,所以也就意味着 mContext = parent.context 也会执行 itemCount 次,明明一次就行,其它多余的操作,会造成不必要的开销。

那通过 onAttachedToRecyclerView() 方法,只会进行一次赋值操作,很OK啊,但毕竟他又是定义全局变量,又是覆写方法,太麻烦了,我们可以在简单一点。

3.2 我选择的方式

好像我把前面介绍的方法都给否认了🙃,那我现在是怎么用的呢?还是以上面的例子,如下:

class MyAdapter(private val dataList: List<MyData>) :
    RecyclerView.Adapter<MyAdapter.MyViewHolder>() {

    class MyViewHolder(
        private val context: Context,
        private val binding: RcyItemViewBinding
    ) :
        RecyclerView.ViewHolder(binding.root) {

        fun bind(data: MyData) {
            Glide.with(context).load(data.imageUrl).into(binding.imageIv)
            binding.contentTv.text = data.content
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
        val binding = RcyItemViewBinding.inflate(
            LayoutInflater.from(parent.context),
            parent,
            false
        )
        return MyViewHolder(parent.context, binding)
    }

    override fun getItemCount(): Int = dataList.size

    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
        holder.bind(dataList[position])
    }
}
复制代码

我选择在 onCreateViewHolder() 通过 parent.context 方法获取 context,但是,不是赋值给全部变量 mContext,而是直接传给 ViewHolder 作为一个私有变量供其使用。

4. 总结

文章中的看法都是我自己的个人观点,毕竟对于一个方法的好坏,就看你站在什么角度看待它,我并没有认为我的方法是最好的最值得推荐的,从而要求大家也这么写。相反,写这篇文章,纯粹就是觉得很有意思,因为文章中的介绍的这几种方法都是我身边出现的,我特想知道大家是用什么方式的呢?欢迎大家留言与我一起讨论。


OK,文章到此也就结束啦。

其实分享文章的最大目的正是等待着有人指出我的错误,如果你发现哪里有错误,请毫无保留的指出即可,虚心请教。 另外,如果你觉得文章不错,对你有所帮助,请给我点个赞,就当鼓励,谢谢 ~Peace~!

文章分类
Android
文章标签