当在 OnCreateView 中,位图(来自 SharedPreferences 的路径)由 onBindViewHolder 设置时,片段卡住
Fragment stucks when, in OnCreateView, a Bitmap (path from SharedPreferences) is set by onBindViewHolder
我会尝试更具体地说明我的问题。我开始说我已经看到 显示了同样的问题,我想,但我并没有真正理解正确的解决方案。
注意:我在创建新项目步骤
中从模板创建了抽屉activity和所有导航依赖项
最初,我发现当我在 ShowProfileFragment 中尝试更新导航抽屉的 header 时,同样的问题发生了,所以当我放置 if
语句时(见下面的代码) 仅在必要时更新它,问题似乎消失了(因为从抽屉中打开此片段不再执行此操作)。
现在,当我在 ItemListFragment 中添加我所有的 objects,从 SharedPreferences 到传递给适配器(ItemCardsAdapter
)的 ArrayList
时一切正常,直到 onBindViewHolder
我在 ImageView
上执行 setImageBitmap
。加载位图似乎在浪费时间...
注意:当我在真实设备上 运行 应用程序(我的 phone)时,问题非常突出,这与我电脑上的模拟器中发生的情况不同(此处,一开始就高亮了,当issue是抽屉里头像更新的时候header)
这是我的代码:
MainActivity.kt
import ...
class MainActivity : AppCompatActivity() {
private lateinit var appBarConfiguration: AppBarConfiguration
private var host: NavHostFragment? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val toolbar: Toolbar = findViewById(R.id.toolbar)
setSupportActionBar(toolbar)
host = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment?
val drawerLayout: DrawerLayout = findViewById(R.id.drawer_layout)
val navView: NavigationView = findViewById(R.id.nav_view)
var navController = host?.navController //findNavController(R.id.nav_host_fragment)
// Passing each menu ID as a set of Ids because each
// menu should be considered as top level destinations.
// if (savedInstanceState != null) {
// //Restore the fragment's instance
// navController = (supportFragmentManager.getFragment(savedInstanceState, "myFragmentEdit")!! as NavHostFragment?)!!.navController
// }
appBarConfiguration = AppBarConfiguration(setOf(R.id.nav_showprofile, R.id.nav_itemlist), drawerLayout)
setupActionBarWithNavController(navController!!, appBarConfiguration)
navView.setupWithNavController(navController)
/*short info in drawer's header taken from SharedPrefs*/
val sharedPref = getSharedPreferences(getString(R.string.shared_pref_key), Context.MODE_PRIVATE)
val parsedData = sharedPref.getString(getString(R.string.profile_json),"missing data")
if(parsedData != "missing data")
{
val json = Json(JsonConfiguration.Stable)
val obj = json.parse(User.serializer(),parsedData!!)
if(obj.photoPath != "")
navView.getHeaderView(0).header_userpic.setImageBitmap(getCircledBitmap(decodeSampledBitmapFromResource(obj.photoPath,256,256)))
navView.getHeaderView(0).header_username.text = obj.name
navView.getHeaderView(0).header_usermail.text = obj.email
}
else
{
//profile pic path as for as other info are taken from resources here
navView.getHeaderView(0).header_username.text = getString(R.string.user_name)
navView.getHeaderView(0).header_usermail.text = getString(R.string.user_mail)
}
}
// override fun onSaveInstanceState(outState: Bundle) {
// super.onSaveInstanceState(outState)
// supportFragmentManager.putFragment(outState, "myFragmentEdit", host!!)
// }
override fun onSupportNavigateUp(): Boolean {
val navController = findNavController(R.id.nav_host_fragment)
return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
}
override fun onBackPressed() {
super.onBackPressed()
}
}
ShowProfileFragment.kt
import ...
class ShowProfileFragment : Fragment() {
companion object {
fun newInstance() = ShowProfileFragment()
}
private lateinit var viewModel: ShowProfileViewModel
@SuppressLint("RestrictedApi")
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val root = inflater.inflate(R.layout.show_profile_fragment, container, false)
viewModel = ViewModelProviders.of(this).get(ShowProfileViewModel::class.java)
/*get User info from SharedPrefs*/
val sharedPref = this.activity!!.getSharedPreferences(getString(R.string.shared_pref_key), Context.MODE_PRIVATE)
val parsedData = sharedPref.getString(getString(R.string.profile_json),"missing data")
if(parsedData != "missing data")
{
val json = Json(JsonConfiguration.Stable)
val obj = json.parse(User.serializer(),parsedData!!)
/*update header info after user clicks save*/
val header = activity!!.findViewById(R.id.nav_view) as NavigationView
header.getHeaderView(0).header_username.text = obj.name
header.getHeaderView(0).header_usermail.text = obj.email
if(obj.photoPath != "")
{
if(arguments?.getString("headerPic") == "update") /*I mean this statement*/
header.getHeaderView(0).header_userpic.setImageBitmap(getCircledBitmap(decodeSampledBitmapFromResource(obj.photoPath,256,256)))
root.photo.setImageBitmap(getCircledBitmap(decodeSampledBitmapFromResource(obj.photoPath,256,256)))
}
viewModel.userKey = obj.userId
root.full_name.text = obj.name
root.nickname.text = obj.nickname
root.email.text = obj.email
root.geographic_area.text = obj.country
//Log.d("kkk","header info from showProfile: name = ${header.getHeaderView(0).header_username.text}")
}
val storageDir = activity!!.getExternalFilesDir(Environment.DIRECTORY_PICTURES)!!
removeIllegalProfilePic(storageDir,sharedPref)
setHasOptionsMenu(true)
return root
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.edit_profile, menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when(item.itemId){
R.id.editProfileFragment -> {
val bundle = bundleOf("userKey" to viewModel.userKey)
findNavController().navigate(R.id.action_nav_showprofile_to_editProfileFragment, bundle)
true
}
else -> {
super.onOptionsItemSelected(item)
}
}
//return NavigationUI.onNavDestinationSelected(item, view!!.findNavController()) || super.onOptionsItemSelected(item)
}
@SuppressLint("SimpleDateFormat")
private fun removeIllegalProfilePic(root: File, sPrefs: SharedPreferences){
/*check for illegal 0B file, caused of app termination from the camera activity*/
/*and for illegal file generated by any Activity.RESULT_OK caused of app termination from EditProfileActivity*/
val illegalProfilePic = sPrefs.getString(getString(R.string.illegal_profile_pic_key),"saved")
for(f in root.listFiles()!!)
if (f.length() <= 0 || f.absolutePath == illegalProfilePic)
f.delete()
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel = ViewModelProviders.of(this).get(ShowProfileViewModel::class.java)
// TODO: Use the ViewModel
}
}
ItemListFragment.kt
import ...
class ItemListFragment : Fragment() {
companion object {
fun newInstance() = ItemListFragment()
}
private lateinit var viewModel: ItemListViewModel
//var itemCardsArray= ArrayList<Item>()
lateinit var mAdapter : ItemCardsAdapter
@SuppressLint("RestrictedApi")
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
viewModel = ViewModelProviders.of(this).get(ItemListViewModel::class.java)
val root = inflater.inflate(R.layout.item_list_fragment, container, false)
val viewManagerPortrait = LinearLayoutManager(activity)
val viewManagerLandscape = GridLayoutManager(activity, 3)
/*adapter -> taken from SharedPrefs*/
var itemCardsArray= ArrayList<Item>()
//itemCardsArray.add( Item("path","Teddy Bear","Sweet Bear baby peluche","13.49","Toddler Toys","Turin","20/12/2020") )
val sharedPref = this.activity!!.getSharedPreferences(getString(R.string.shared_pref_key), Context.MODE_PRIVATE)
val itemCount = sharedPref.getInt(getString(R.string.item_count), 0)
if(itemCount == 0)
root.listItems.visibility = View.GONE
else
{
root.emptyAds.visibility = View.GONE
mAdapter = ItemCardsAdapter(itemCardsArray, this)
//viewAdapter.notifyDataSetChanged()
root.listItems.apply {
setHasFixedSize(true)
// use a linear layout manager if portrait, grid one else
layoutManager = if(activity!!.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE)
viewManagerLandscape
else
viewManagerPortrait
// specify an viewAdapter (see also next example)
adapter = mAdapter
//adapter!!.notifyDataSetChanged()
}
for(i in 1..itemCount)
{
val parsedData = sharedPref.getString(getString(R.string.item_json)+i.toString(),"missing data")
if(parsedData != "missing data")
{
val json = Json(JsonConfiguration.Stable)
val obj = json.parse(Item.serializer(),parsedData!!)
itemCardsArray.add(i-1, obj)
mAdapter.notifyItemInserted(i-1)
}
}
}
root.fab_addItem.setOnClickListener {
Snackbar.make(it, "Creating new advertisement...", Snackbar.LENGTH_LONG)
.setAction("Action", null).show()
findNavController().navigate(R.id.action_nav_itemlist_to_itemEditFragment)
}
return root
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel = ViewModelProviders.of(this).get(ItemListViewModel::class.java)
// TODO: Use the ViewModel
}
}
ItemCardsAdapter.kt
import ...
class ItemCardsAdapter(private val myDataset: ArrayList<Item>, private val f: Fragment) : RecyclerView.Adapter<ItemCardsAdapter.MyViewHolder>() {
class MyViewHolder(viewItem: View) : RecyclerView.ViewHolder(viewItem){
val itemphoto = viewItem.itemphoto
val itemtitle = viewItem.title
val itemprice = viewItem.itemprice
val itemlocation = viewItem.itemlocation
val editpencil = viewItem.editCard
}
// Create new views (invoked by the layout manager)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
// create a new view
val viewItem = LayoutInflater.from(parent.context).inflate(R.layout.item_card, parent, false)
// set the view's size, margins, paddings and layout parameters
return MyViewHolder(viewItem)
}
// Replace the contents of a view (invoked by the layout manager)
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.itemtitle.text = myDataset[position].title
holder.itemprice.text = myDataset[position].price
holder.itemlocation.text = myDataset[position].location
if(myDataset[position].photoPath != "") /*Here it stucks on load !?*/
holder.itemphoto.setImageBitmap(decodeSampledBitmapFromResource(myDataset[position].photoPath,256,256))
//holder.View.startAnimation(AnimationUtils.loadLayoutAnimation(this,R.anim.layout_animation))
val bundle = bundleOf("itemAdKey" to myDataset[position].adId)
holder.editpencil.setOnClickListener {
bundle.putString("nav", "editCard")
f.findNavController().navigate(R.id.action_nav_itemlist_to_itemEditFragment, bundle)
}
holder.itemView.setOnClickListener {
f.findNavController().navigate(R.id.action_nav_itemlist_to_nav_itemdetails, bundle)
}
}
// Return the size of your dataset (invoked by the layout manager)
override fun getItemCount() = myDataset.size
}
我按照官方文档找到了解决方案:
https://developer.android.com/topic/performance/graphics
建议使用此外部库来解决此类内存缓存问题:Glide
所以,我改变了,例如,这个
ItemCardsAdapter.kt
holder.itemphoto.setImageBitmap(decodeSampledBitmapFromResource(myDataset[position].photoPath,256,256))
有了这个
Glide.with(f.activity!!).load(myDataset[position].photoPath).into(holder.itemphoto)
我会尝试更具体地说明我的问题。我开始说我已经看到
注意:我在创建新项目步骤
中从模板创建了抽屉activity和所有导航依赖项最初,我发现当我在 ShowProfileFragment 中尝试更新导航抽屉的 header 时,同样的问题发生了,所以当我放置 if
语句时(见下面的代码) 仅在必要时更新它,问题似乎消失了(因为从抽屉中打开此片段不再执行此操作)。
现在,当我在 ItemListFragment 中添加我所有的 objects,从 SharedPreferences 到传递给适配器(ItemCardsAdapter
)的 ArrayList
时一切正常,直到 onBindViewHolder
我在 ImageView
上执行 setImageBitmap
。加载位图似乎在浪费时间...
注意:当我在真实设备上 运行 应用程序(我的 phone)时,问题非常突出,这与我电脑上的模拟器中发生的情况不同(此处,一开始就高亮了,当issue是抽屉里头像更新的时候header)
这是我的代码:
MainActivity.kt
import ...
class MainActivity : AppCompatActivity() {
private lateinit var appBarConfiguration: AppBarConfiguration
private var host: NavHostFragment? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val toolbar: Toolbar = findViewById(R.id.toolbar)
setSupportActionBar(toolbar)
host = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment?
val drawerLayout: DrawerLayout = findViewById(R.id.drawer_layout)
val navView: NavigationView = findViewById(R.id.nav_view)
var navController = host?.navController //findNavController(R.id.nav_host_fragment)
// Passing each menu ID as a set of Ids because each
// menu should be considered as top level destinations.
// if (savedInstanceState != null) {
// //Restore the fragment's instance
// navController = (supportFragmentManager.getFragment(savedInstanceState, "myFragmentEdit")!! as NavHostFragment?)!!.navController
// }
appBarConfiguration = AppBarConfiguration(setOf(R.id.nav_showprofile, R.id.nav_itemlist), drawerLayout)
setupActionBarWithNavController(navController!!, appBarConfiguration)
navView.setupWithNavController(navController)
/*short info in drawer's header taken from SharedPrefs*/
val sharedPref = getSharedPreferences(getString(R.string.shared_pref_key), Context.MODE_PRIVATE)
val parsedData = sharedPref.getString(getString(R.string.profile_json),"missing data")
if(parsedData != "missing data")
{
val json = Json(JsonConfiguration.Stable)
val obj = json.parse(User.serializer(),parsedData!!)
if(obj.photoPath != "")
navView.getHeaderView(0).header_userpic.setImageBitmap(getCircledBitmap(decodeSampledBitmapFromResource(obj.photoPath,256,256)))
navView.getHeaderView(0).header_username.text = obj.name
navView.getHeaderView(0).header_usermail.text = obj.email
}
else
{
//profile pic path as for as other info are taken from resources here
navView.getHeaderView(0).header_username.text = getString(R.string.user_name)
navView.getHeaderView(0).header_usermail.text = getString(R.string.user_mail)
}
}
// override fun onSaveInstanceState(outState: Bundle) {
// super.onSaveInstanceState(outState)
// supportFragmentManager.putFragment(outState, "myFragmentEdit", host!!)
// }
override fun onSupportNavigateUp(): Boolean {
val navController = findNavController(R.id.nav_host_fragment)
return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
}
override fun onBackPressed() {
super.onBackPressed()
}
}
ShowProfileFragment.kt
import ...
class ShowProfileFragment : Fragment() {
companion object {
fun newInstance() = ShowProfileFragment()
}
private lateinit var viewModel: ShowProfileViewModel
@SuppressLint("RestrictedApi")
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val root = inflater.inflate(R.layout.show_profile_fragment, container, false)
viewModel = ViewModelProviders.of(this).get(ShowProfileViewModel::class.java)
/*get User info from SharedPrefs*/
val sharedPref = this.activity!!.getSharedPreferences(getString(R.string.shared_pref_key), Context.MODE_PRIVATE)
val parsedData = sharedPref.getString(getString(R.string.profile_json),"missing data")
if(parsedData != "missing data")
{
val json = Json(JsonConfiguration.Stable)
val obj = json.parse(User.serializer(),parsedData!!)
/*update header info after user clicks save*/
val header = activity!!.findViewById(R.id.nav_view) as NavigationView
header.getHeaderView(0).header_username.text = obj.name
header.getHeaderView(0).header_usermail.text = obj.email
if(obj.photoPath != "")
{
if(arguments?.getString("headerPic") == "update") /*I mean this statement*/
header.getHeaderView(0).header_userpic.setImageBitmap(getCircledBitmap(decodeSampledBitmapFromResource(obj.photoPath,256,256)))
root.photo.setImageBitmap(getCircledBitmap(decodeSampledBitmapFromResource(obj.photoPath,256,256)))
}
viewModel.userKey = obj.userId
root.full_name.text = obj.name
root.nickname.text = obj.nickname
root.email.text = obj.email
root.geographic_area.text = obj.country
//Log.d("kkk","header info from showProfile: name = ${header.getHeaderView(0).header_username.text}")
}
val storageDir = activity!!.getExternalFilesDir(Environment.DIRECTORY_PICTURES)!!
removeIllegalProfilePic(storageDir,sharedPref)
setHasOptionsMenu(true)
return root
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.edit_profile, menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when(item.itemId){
R.id.editProfileFragment -> {
val bundle = bundleOf("userKey" to viewModel.userKey)
findNavController().navigate(R.id.action_nav_showprofile_to_editProfileFragment, bundle)
true
}
else -> {
super.onOptionsItemSelected(item)
}
}
//return NavigationUI.onNavDestinationSelected(item, view!!.findNavController()) || super.onOptionsItemSelected(item)
}
@SuppressLint("SimpleDateFormat")
private fun removeIllegalProfilePic(root: File, sPrefs: SharedPreferences){
/*check for illegal 0B file, caused of app termination from the camera activity*/
/*and for illegal file generated by any Activity.RESULT_OK caused of app termination from EditProfileActivity*/
val illegalProfilePic = sPrefs.getString(getString(R.string.illegal_profile_pic_key),"saved")
for(f in root.listFiles()!!)
if (f.length() <= 0 || f.absolutePath == illegalProfilePic)
f.delete()
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel = ViewModelProviders.of(this).get(ShowProfileViewModel::class.java)
// TODO: Use the ViewModel
}
}
ItemListFragment.kt
import ...
class ItemListFragment : Fragment() {
companion object {
fun newInstance() = ItemListFragment()
}
private lateinit var viewModel: ItemListViewModel
//var itemCardsArray= ArrayList<Item>()
lateinit var mAdapter : ItemCardsAdapter
@SuppressLint("RestrictedApi")
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
viewModel = ViewModelProviders.of(this).get(ItemListViewModel::class.java)
val root = inflater.inflate(R.layout.item_list_fragment, container, false)
val viewManagerPortrait = LinearLayoutManager(activity)
val viewManagerLandscape = GridLayoutManager(activity, 3)
/*adapter -> taken from SharedPrefs*/
var itemCardsArray= ArrayList<Item>()
//itemCardsArray.add( Item("path","Teddy Bear","Sweet Bear baby peluche","13.49","Toddler Toys","Turin","20/12/2020") )
val sharedPref = this.activity!!.getSharedPreferences(getString(R.string.shared_pref_key), Context.MODE_PRIVATE)
val itemCount = sharedPref.getInt(getString(R.string.item_count), 0)
if(itemCount == 0)
root.listItems.visibility = View.GONE
else
{
root.emptyAds.visibility = View.GONE
mAdapter = ItemCardsAdapter(itemCardsArray, this)
//viewAdapter.notifyDataSetChanged()
root.listItems.apply {
setHasFixedSize(true)
// use a linear layout manager if portrait, grid one else
layoutManager = if(activity!!.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE)
viewManagerLandscape
else
viewManagerPortrait
// specify an viewAdapter (see also next example)
adapter = mAdapter
//adapter!!.notifyDataSetChanged()
}
for(i in 1..itemCount)
{
val parsedData = sharedPref.getString(getString(R.string.item_json)+i.toString(),"missing data")
if(parsedData != "missing data")
{
val json = Json(JsonConfiguration.Stable)
val obj = json.parse(Item.serializer(),parsedData!!)
itemCardsArray.add(i-1, obj)
mAdapter.notifyItemInserted(i-1)
}
}
}
root.fab_addItem.setOnClickListener {
Snackbar.make(it, "Creating new advertisement...", Snackbar.LENGTH_LONG)
.setAction("Action", null).show()
findNavController().navigate(R.id.action_nav_itemlist_to_itemEditFragment)
}
return root
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel = ViewModelProviders.of(this).get(ItemListViewModel::class.java)
// TODO: Use the ViewModel
}
}
ItemCardsAdapter.kt
import ...
class ItemCardsAdapter(private val myDataset: ArrayList<Item>, private val f: Fragment) : RecyclerView.Adapter<ItemCardsAdapter.MyViewHolder>() {
class MyViewHolder(viewItem: View) : RecyclerView.ViewHolder(viewItem){
val itemphoto = viewItem.itemphoto
val itemtitle = viewItem.title
val itemprice = viewItem.itemprice
val itemlocation = viewItem.itemlocation
val editpencil = viewItem.editCard
}
// Create new views (invoked by the layout manager)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
// create a new view
val viewItem = LayoutInflater.from(parent.context).inflate(R.layout.item_card, parent, false)
// set the view's size, margins, paddings and layout parameters
return MyViewHolder(viewItem)
}
// Replace the contents of a view (invoked by the layout manager)
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.itemtitle.text = myDataset[position].title
holder.itemprice.text = myDataset[position].price
holder.itemlocation.text = myDataset[position].location
if(myDataset[position].photoPath != "") /*Here it stucks on load !?*/
holder.itemphoto.setImageBitmap(decodeSampledBitmapFromResource(myDataset[position].photoPath,256,256))
//holder.View.startAnimation(AnimationUtils.loadLayoutAnimation(this,R.anim.layout_animation))
val bundle = bundleOf("itemAdKey" to myDataset[position].adId)
holder.editpencil.setOnClickListener {
bundle.putString("nav", "editCard")
f.findNavController().navigate(R.id.action_nav_itemlist_to_itemEditFragment, bundle)
}
holder.itemView.setOnClickListener {
f.findNavController().navigate(R.id.action_nav_itemlist_to_nav_itemdetails, bundle)
}
}
// Return the size of your dataset (invoked by the layout manager)
override fun getItemCount() = myDataset.size
}
我按照官方文档找到了解决方案:
https://developer.android.com/topic/performance/graphics
建议使用此外部库来解决此类内存缓存问题:Glide
所以,我改变了,例如,这个
ItemCardsAdapter.kt
holder.itemphoto.setImageBitmap(decodeSampledBitmapFromResource(myDataset[position].photoPath,256,256))
有了这个
Glide.with(f.activity!!).load(myDataset[position].photoPath).into(holder.itemphoto)