活动在后台被杀死后,应用崩溃 [英] App crash after activity has been killed in background

查看:53
本文介绍了活动在后台被杀死后,应用崩溃的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我对使用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.要获取该片段的实例,请使用

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.

这篇关于活动在后台被杀死后,应用崩溃的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆