Fragment stucks when, in OnCreateView, a Bitmap (path from SharedPreferences) is set by onBindViewHolder

主宰稳场 提交于 2020-04-30 06:31:33

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!