活动在后台被杀死后,应用崩溃

编程入门 行业动态 更新时间:2024-10-23 15:33:19
本文介绍了活动在后台被杀死后,应用崩溃的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧! 问题描述

我对使用ViewPager显示片段的应用程序有问题.一切正常,直到应用程序在后台运行并被操作系统杀死为止.似乎在还原后,我有2个IncidentScreenFragment处理事件,其中一个具有空的主持人(MVP),这会使我的应用程序崩溃.

我的HomeActivity看起来像:

override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) presenter.onViewCreated() initViews(savedInstanceState) } private fun initViews(savedInstanceState: Bundle?){ mapView.onCreate(savedInstanceState) mapView.getMapAsync(this) initFragment() initMenu() } private fun initFragment(){ homeFragment = HomeScreenFragment.newInstance() incidentFragment = IncidentScreenFragment.newInstance() chatFragment = ChatFragment.newInstance() weatherFragment = WeatherFragment.newInstance() viewPager.adapter = ViewPagerAdapter(supportFragmentManager, this) viewPager.offscreenPageLimit = 4 viewPager?.addOnPageChangeListener(object : ViewPager.OnPageChangeListener { override fun onPageScrollStateChanged(state: Int) {} override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {} override fun onPageSelected(position: Int) {bottom_navigation.currentItem = position} }) } override fun getFragmentByPos(pos: Int): Fragment { return when(pos){ 0 -> homeFragment 1 -> incidentFragment 2 -> chatFragment 3 -> weatherFragment else -> { homeFragment } } }

还有我的适配器:

class ViewPagerAdapter internal constructor(fm: FragmentManager, activity:infinite_software.intelligence_center.intelligencecenter.ui.home.FragmentManager) : FragmentPagerAdapter(fm) { private val COUNT = 4 private val activity = activity override fun getItem(position: Int): Fragment{ var fragment: Fragment? = null when (position) { 0 -> fragment = activity.getFragmentByPos(0) 1 -> fragment = activity.getFragmentByPos(1) 2 -> fragment = activity.getFragmentByPos(2) 3 -> fragment = activity.getFragmentByPos(3) } return fragment!! } override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) { super.destroyItem(container, position, `object`) } override fun getCount(): Int { return COUNT } override fun getPageTitle(position: Int): CharSequence? { return "Section " + (position + 1) } }

每个Fragment都有一个返回新Fragment的静态方法:

companion object { fun newInstance(): HomeScreenFragment { return HomeScreenFragment() } }

当应用在后台被杀死时,我发现有2个对象(片段)可以监听事件,其中一个带有Presenter正确实例化,另一个没有实例化.

在我的抽象BaseFragment类下面:

abstract class BaseFragment<P : BasePresenter<BaseView>> : BaseView,Fragment() { protected lateinit var presenter: P override fun getContext(): Context { return activity as Context } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { return super.onCreateView(inflater, container, savedInstanceState) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) presenter = instantiatePresenter() } override fun showError(error: String) { (activity as BaseActivity<BasePresenter<BaseView>>).showError(error) } override fun showError(errorResId: Int) { (activity as BaseActivity<BasePresenter<BaseView>>).showError(errorResId) } abstract fun onBackPressed(): Boolean /** * Instantiates the presenter the Fragment is based on. */ protected abstract fun instantiatePresenter(): P abstract val TAG: String

事件片段代码:

class IncidentScreenFragment: BaseFragment<IncidentScreenPresenter>(), BaseView, IncidentView, AlertFilterListener, AlertItemClickListener, IncidentDetailListener { var rvAdapter : IncidentAdapter? = null var state : Int = LIST_STATE override fun instantiatePresenter(): IncidentScreenPresenter { return IncidentScreenPresenter(this) } override val TAG: String get() = "INCIDENT" override fun getContext(): Context { return activity as Context } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { super.onCreateView(inflater, container, savedInstanceState) return inflater.inflate(R.layout.fragment_incident, container, false) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) initViews() presenter.onViewCreated() initObserve() } private fun initViews(){ //Reclycler view alertRV.layoutManager = LinearLayoutManager(context) rvAdapter = IncidentAdapter(ArrayList(), context, this) alertRV.adapter = rvAdapter //Apply Listeners headerBox.setFilterListener(this) incidentDetailView.setListener(this) } override fun initObserve() { //Init observe presenter model val alertObserver = Observer<ArrayList<AlertModel>> { alerts -> Timber.d("Data received from Presenter [$alerts]") showAlertList(alerts) } presenter.filteredAlertList.observe(context as BaseActivity<BasePresenter<BaseView>>,alertObserver) } override fun updateThisFilters(boxState: Boolean, level: Int) { presenter.updateFilterList(boxState,level) } fun showOnlyThisLevel(level:Int){ presenter.showOnlyThisLevel(level) headerBox.disableBoxExcept(level) } fun showAlertList(list: ArrayList<AlertModel>){ rvAdapter?.updateData(list) } override fun onItemClick(model: AlertModel) { presenter.loadAlertDetail(model) } override fun showAlertDetail(model: AlertModel) { incidentDetailView.setUpFromModel(model) WhiteWizard.slideLeftEffect(incidentDetailView,incidentListRootElement) state = DETAIL_STATE } override fun onbackFromDetailPressed() { WhiteWizard.slideRightEffect(incidentListRootElement,incidentDetailView) state = LIST_STATE } override fun showLoader() { loaderIncident.visibility = View.VISIBLE } override fun hideLoader() { loaderIncident.visibility = View.INVISIBLE } override fun onBackPressed(): Boolean { when(state){ LIST_STATE -> return false DETAIL_STATE -> { onbackFromDetailPressed() return true } else -> return false } } fun newInstance(): IncidentScreenFragment { return IncidentScreenFragment() } }

当我单击主页上的按钮以显示片段内容时,我得到了:

Process: XXXXXX, PID: 3192 kotlin.UninitializedPropertyAccessException: lateinit property presenter has not been initialized at infinite_software.intelligence_center.intelligencecenter.base.BaseFragment.getPresenter(BaseFragment.kt:11) at XXXXXX.ui.home.incidentScreen.IncidentScreenFragment.showOnlyThisLevel(IncidentScreenFragment.kt:78) at XXXXXX.ui.home.HomeActivity.filterDataWithSeverity(HomeActivity.kt:110) at XXXXXX.ui.home.homeScreen.HomeScreenFragment.filterBy(HomeScreenFragment.kt:76) at XXXXXX.ui.home.homeScreen.HomeScreenFragment$initViews$5.onClick(HomeScreenFragment.kt:56)

如果我尝试打印Fragment的ID,我会从方法调用showOnlyThisLevel()和onBackPressed()获得2个不同的ID.我想念什么?

解决方案

在进行了一些研究之后,问题似乎出在FragmentPagerAdapter方法的错误命名-命名为getItem(),但并未明确指出抽象方法getItem(int position)应该 返回一个片段的新实例 ,而不仅仅是获取一个实例".

当然,对于不正确的名称,已经有7年的历史了,我们对此无能为力,但至少我们可以在您的代码中解决由该问题引起的错误;)

事不宜迟,导致您发生NPE的原因是从未调用onCreateView(演示者实例化的地方).

发生这种情况是因为您要在此处创建片段:

override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) ... homeFragment = HomeScreenFragment.newInstance() incidentFragment = IncidentScreenFragment.newInstance() }

您从FragmentPagerAdapter中的getItem(int position)内部返回此片段:

override fun getItem(position: Int): Fragment = when(position) { ... 1 -> activity.incidentFragment ... }

所以我们对activity.incidentFragment的了解是,其中从未调用onCreateView().

这是由于它实际上从未添加到FragmentManager且从未在屏幕上显示.

这是因为活动 中的super.onCreate(savedInstanceState)使用其无参数构造函数通过反射重新创建所有片段,同时保留其标签(请参见findFragmentByTag) . /p>

因此,您可以在此答案中看到,或者也可以在这里引用:

// Do we already have this fragment? String name = makeFragmentName(container.getId(), itemId); Fragment fragment = mFragmentManager.findFragmentByTag(name); if (fragment != null) { if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment); mCurTransaction.attach(fragment); } else { fragment = getItem(position); if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment); mCurTransaction.add(container.getId(), fragment, makeFragmentName(container.getId(), itemId));

仅当FragmentPagerAdapter为该片段设置的fragment标签未找到Fragment时才调用getItem(position)方法,在内存不足导致应用中断后,该标签会自动重新创建.

因此,永远不会使用您的新片段(您在Activity中手动创建的片段),因此它没有视图,也没有初始化,也没有添加到FragmentManager,它与ViewPager内的实例不同,并且调用时会崩溃.轰!

解决方案是在FragmentPagerAdapter的getItem(position)方法中实例化Fragment.要获取该片段的实例,请使用这个答案.

i have an issue with an app that use ViewPager for display fragment. All works fine until the app goes in background and be killed from OS. It seems that after restore i have 2 IncidentScreenFragment that handle events, one with a null presenter (MVP) that crash my app.

My HomeActivity looks like:

override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) presenter.onViewCreated() initViews(savedInstanceState) } private fun initViews(savedInstanceState: Bundle?){ mapView.onCreate(savedInstanceState) mapView.getMapAsync(this) initFragment() initMenu() } private fun initFragment(){ homeFragment = HomeScreenFragment.newInstance() incidentFragment = IncidentScreenFragment.newInstance() chatFragment = ChatFragment.newInstance() weatherFragment = WeatherFragment.newInstance() viewPager.adapter = ViewPagerAdapter(supportFragmentManager, this) viewPager.offscreenPageLimit = 4 viewPager?.addOnPageChangeListener(object : ViewPager.OnPageChangeListener { override fun onPageScrollStateChanged(state: Int) {} override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {} override fun onPageSelected(position: Int) {bottom_navigation.currentItem = position} }) } override fun getFragmentByPos(pos: Int): Fragment { return when(pos){ 0 -> homeFragment 1 -> incidentFragment 2 -> chatFragment 3 -> weatherFragment else -> { homeFragment } } }

And my Adapter:

class ViewPagerAdapter internal constructor(fm: FragmentManager, activity:infinite_software.intelligence_center.intelligencecenter.ui.home.FragmentManager) : FragmentPagerAdapter(fm) { private val COUNT = 4 private val activity = activity override fun getItem(position: Int): Fragment{ var fragment: Fragment? = null when (position) { 0 -> fragment = activity.getFragmentByPos(0) 1 -> fragment = activity.getFragmentByPos(1) 2 -> fragment = activity.getFragmentByPos(2) 3 -> fragment = activity.getFragmentByPos(3) } return fragment!! } override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) { super.destroyItem(container, position, `object`) } override fun getCount(): Int { return COUNT } override fun getPageTitle(position: Int): CharSequence? { return "Section " + (position + 1) } }

Each Fragment have a static method that return new Fragment:

companion object { fun newInstance(): HomeScreenFragment { return HomeScreenFragment() } }

When the app has been killed in background i figure out that there is 2 objects (Fragment) that listen to event, one with Presenter correctly instantiate and one without.

Below my abstract BaseFragment class:

abstract class BaseFragment<P : BasePresenter<BaseView>> : BaseView,Fragment() { protected lateinit var presenter: P override fun getContext(): Context { return activity as Context } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { return super.onCreateView(inflater, container, savedInstanceState) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) presenter = instantiatePresenter() } override fun showError(error: String) { (activity as BaseActivity<BasePresenter<BaseView>>).showError(error) } override fun showError(errorResId: Int) { (activity as BaseActivity<BasePresenter<BaseView>>).showError(errorResId) } abstract fun onBackPressed(): Boolean /** * Instantiates the presenter the Fragment is based on. */ protected abstract fun instantiatePresenter(): P abstract val TAG: String

Incident Fragment code:

class IncidentScreenFragment: BaseFragment<IncidentScreenPresenter>(), BaseView, IncidentView, AlertFilterListener, AlertItemClickListener, IncidentDetailListener { var rvAdapter : IncidentAdapter? = null var state : Int = LIST_STATE override fun instantiatePresenter(): IncidentScreenPresenter { return IncidentScreenPresenter(this) } override val TAG: String get() = "INCIDENT" override fun getContext(): Context { return activity as Context } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { super.onCreateView(inflater, container, savedInstanceState) return inflater.inflate(R.layout.fragment_incident, container, false) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) initViews() presenter.onViewCreated() initObserve() } private fun initViews(){ //Reclycler view alertRV.layoutManager = LinearLayoutManager(context) rvAdapter = IncidentAdapter(ArrayList(), context, this) alertRV.adapter = rvAdapter //Apply Listeners headerBox.setFilterListener(this) incidentDetailView.setListener(this) } override fun initObserve() { //Init observe presenter model val alertObserver = Observer<ArrayList<AlertModel>> { alerts -> Timber.d("Data received from Presenter [$alerts]") showAlertList(alerts) } presenter.filteredAlertList.observe(context as BaseActivity<BasePresenter<BaseView>>,alertObserver) } override fun updateThisFilters(boxState: Boolean, level: Int) { presenter.updateFilterList(boxState,level) } fun showOnlyThisLevel(level:Int){ presenter.showOnlyThisLevel(level) headerBox.disableBoxExcept(level) } fun showAlertList(list: ArrayList<AlertModel>){ rvAdapter?.updateData(list) } override fun onItemClick(model: AlertModel) { presenter.loadAlertDetail(model) } override fun showAlertDetail(model: AlertModel) { incidentDetailView.setUpFromModel(model) WhiteWizard.slideLeftEffect(incidentDetailView,incidentListRootElement) state = DETAIL_STATE } override fun onbackFromDetailPressed() { WhiteWizard.slideRightEffect(incidentListRootElement,incidentDetailView) state = LIST_STATE } override fun showLoader() { loaderIncident.visibility = View.VISIBLE } override fun hideLoader() { loaderIncident.visibility = View.INVISIBLE } override fun onBackPressed(): Boolean { when(state){ LIST_STATE -> return false DETAIL_STATE -> { onbackFromDetailPressed() return true } else -> return false } } fun newInstance(): IncidentScreenFragment { return IncidentScreenFragment() } }

When i click on the button in homePage to display fragment content i got:

Process: XXXXXX, PID: 3192 kotlin.UninitializedPropertyAccessException: lateinit property presenter has not been initialized at infinite_software.intelligence_center.intelligencecenter.base.BaseFragment.getPresenter(BaseFragment.kt:11) at XXXXXX.ui.home.incidentScreen.IncidentScreenFragment.showOnlyThisLevel(IncidentScreenFragment.kt:78) at XXXXXX.ui.home.HomeActivity.filterDataWithSeverity(HomeActivity.kt:110) at XXXXXX.ui.home.homeScreen.HomeScreenFragment.filterBy(HomeScreenFragment.kt:76) at XXXXXX.ui.home.homeScreen.HomeScreenFragment$initViews$5.onClick(HomeScreenFragment.kt:56)

If i try to print the id of Fragment, i obtain 2 different ids from method call showOnlyThisLevel() and onBackPressed(). What i miss ?

解决方案

After doing some research, it seems that the problem stems from the misnaming of FragmentPagerAdapter's method - being named getItem(), but not clearly specifying that the abstract method getItem(int position) is supposed to return a new instance of a fragment rather than just "get an instance of one".

Of course, there is not much we can do about an incorrect name after it's been out in the wild for 7 years, but at least we can fix the bug that stems from this issue in your code ;)

Without further ado, the cause of your NPE is that onCreateView (where your Presenter is instantiated) is never called.

This happens because you are creating the fragment here:

override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) ... homeFragment = HomeScreenFragment.newInstance() incidentFragment = IncidentScreenFragment.newInstance() }

You return this fragment from inside getItem(int position) in your FragmentPagerAdapter:

override fun getItem(position: Int): Fragment = when(position) { ... 1 -> activity.incidentFragment ... }

So what we know about activity.incidentFragment is that in it, onCreateView() is never called.

This is caused by the fact that it's never actually added to a FragmentManager and never displayed on the screen.

That's because super.onCreate(savedInstanceState) in Activity recreates all Fragments, using their no-args constructor, via reflection, while keeping their tag (see findFragmentByTag).

So as you can see in this answer, or as I can quote here:

// Do we already have this fragment? String name = makeFragmentName(container.getId(), itemId); Fragment fragment = mFragmentManager.findFragmentByTag(name); if (fragment != null) { if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment); mCurTransaction.attach(fragment); } else { fragment = getItem(position); if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment); mCurTransaction.add(container.getId(), fragment, makeFragmentName(container.getId(), itemId));

The getItem(position) method is only called if the Fragment is not found by the fragment tag that the FragmentPagerAdapter sets for the fragment, which IS automatically recreated after low memory condition kills your app.

Therefore, YOUR new fragment (that you create by hand in the Activity) is NEVER used, and therefore it has no view, never initialized, never added to FragmentManager, it's not the same instance as what's actually inside your ViewPager, and it crashes when you call it. Boom!

Solution is to instantiate the Fragment inside FragmentPagerAdapter's getItem(position) method. To get an instance of the fragment, use this answer.

更多推荐

活动在后台被杀死后,应用崩溃

本文发布于:2023-11-27 08:33:34,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1637300.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:死后   被杀   后台

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!