【问题标题】:App crash after activity has been killed in background活动在后台被杀死后应用程序崩溃
【发布时间】:2019-04-18 20:10:05
【问题描述】:

我对使用 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。我想念什么?

【问题讨论】:

  • 您在哪里找到将Fragments 设置为单身人士的提示?它会泄露活动并违反所有可能的生命周期方法。
  • 把代码贴在IncidentScreenFragment

标签: android kotlin android-viewpager fragment


【解决方案1】:

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

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


事不宜迟,您的 NPE 的原因是永远不会调用 onCreateView(您的 Presenter 被实例化的地方)。

这是因为您在此处创建片段:

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 并且从未显示在屏幕上。

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

正如您在this answer 中看到的那样,或者我可以在这里引用:

    // 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 设置的 Fragment 标记未找到该 Fragment 时调用,在内存不足的情况下会自动重新创建您的应用程序。

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



解决方案是在 FragmentPagerAdapter 的 getItem(position) 方法中实例化 Fragment。 要获取 Fragment 的实例,请使用 this answer

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-05-15
    • 2020-10-23
    • 1970-01-01
    • 1970-01-01
    • 2013-09-19
    • 1970-01-01
    相关资源
    最近更新 更多