问题
I will try to be more specific about my issue. I start saying I've already seen this question that shows the same kind of problem, I guess, but I didn't really understand the right solution.
Note: I have created the drawer activity and all navigation dependencies from a template in creating new project step
Initially, I found that when, in ShowProfileFragment, I tried to update the header of the navigation drawer, the same issue happened, so when I put that if
statement (see code below) to update it only if necessary, the problem seemed to be vanished (because opening this fragment from drawer doesn't perform this action anymore).
Now, when in ItemListFragment I add all my objects, taken from SharedPreferences to the ArrayList
passed to an adapter (ItemCardsAdapter
) everything works well until in onBindViewHolder
of my adapter I perform setImageBitmap
on the ImageView
. It appears to be wasting time loading bitmaps...
Note: when I run the app on a real device (my phone) the issue is very highlighted, unlike what happens in the emulator on my pc (here, it was highlighted at the beginning, when issue regarded the profile picture's updating on the drawer's header)
Here is my code:
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
}
回答1:
I found the solution, following this official documentation:
https://developer.android.com/topic/performance/graphics
that recommends to use this external library to solve that kind of memory caching issues: Glide
So, I changed, for example, this
ItemCardsAdapter.kt
holder.itemphoto.setImageBitmap(decodeSampledBitmapFromResource(myDataset[position].photoPath,256,256))
with this
Glide.with(f.activity!!).load(myDataset[position].photoPath).into(holder.itemphoto)
来源:https://stackoverflow.com/questions/61387288/fragment-stucks-when-in-oncreateview-a-bitmap-path-from-sharedpreferences-is