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

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

问题描述

我有一个使用 ViewPager 显示片段的应用程序的问题.一切正常,直到应用程序进入后台并被操作系统杀死.似乎在恢复之后,我有 2 个处理事件的 IncidentScreenFragment,其中一个带有使我的应用程序崩溃的空演示者 (MVP).

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.

我的 HomeActivity 看起来像:

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
            }
        }
    }

还有我的适配器:

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 的静态方法:

Each Fragment have a static method that return new Fragment:

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

当应用程序在后台被杀死时,我发现有 2 个对象 (Fragment) 监听事件,一个正确实例化了 Presenter,另一个没有.

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.

在我抽象的 BaseFragment 类下面:

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

事件片段代码:

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)

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

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

推荐答案

经过一番研究,似乎问题出在 FragmentPagerAdapter 的方法命名错误 - 被命名为 getItem(),但没有明确指定抽象方法 getItem(int position) 应该返回一个片段的新实例 而不仅仅是获取一个实例".

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".

当然,对于一个错误的名称已经存在 7 年,我们无能为力,但至少我们可以在您的代码中修复由该问题引起的错误;)

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 ;)

不用多说,你的 NPE 的原因是 onCreateView(你的 Presenter 被实例化的地方)从未被调用.

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()
}

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

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

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

所以我们对 activity.incidentFragment 的了解是,onCreateView() 永远不会被调用.

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

这是因为它从未真正添加到 FragmentManager 并且从未显示在屏幕上.

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

那是因为活动中的 super.onCreate(savedInstanceState) 使用它们的无参数构造函数,通过反射重新创建所有 Fragment,同时保留它们的标签(参见 findFragmentByTag).

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));

getItem(position) 方法仅在 FragmentPagerAdapter 为片段设置的片段标记未找到 Fragment 时调用,该片段在低内存条件杀死您的应用程序后会自动重新创建.

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.

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

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!

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

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天全站免登陆