当在 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)