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.MainActivityE/jctest: onCreate: binding.recyclerView.context::class.java = class com.jc.test.MainActivityE/jctest: onAttachedToRecyclerView: recyclerView.context::class.java = class com.jc.test.MainActivityE/jctest: onCreateViewHolder: parent::class.java = class androidx.recyclerview.widget.RecyclerViewE/jctest: onCreateViewHolder: parent.context::class.java = class com.jc.test.MainActivityE/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. 總結文章中的看法都是我自己的個人觀點,畢竟對於一個方法的好壞,就看你站在什麼角度看待它,我並沒有認為我的方法是最好的最值得推薦的,從而要求大家也這麼寫。相反,寫這篇文章,純粹就是覺得很有意思,因為文章中的介紹的這幾種方法都是我身邊出現的,我特想知道大家是用什麼方式的呢?歡迎大家留言與我一起討論。
最後在這裡我也分享一份由幾位大佬一起收錄整理的Flutter進階資料以及Android學習PDF+架構影片+面試文件+原始碼筆記,高階架構技術進階腦圖、Android開發面試專題資料,高階進階架構資料。
這些都是我閒暇時還會反覆翻閱的精品資料。可以有效地幫助大家掌握知識、理解原理。當然你也可以拿去查漏補缺,提升自身的競爭力。