繁体   English   中英

为什么我的 android 应用程序在使用后退按钮导航和从 Internet 加载数据时会跳帧? (导航组件)

[英]Why my android app is skipping frames when navigating with back button and loading data fron internet? (Navigation component)

所以我有一个底部导航设置,其中包含 5 个带有导航组件的片段。 当应用程序启动时,将数据加载到回收器视图时,globalFragment 会跳过 90 多帧。 当我 go 到其他片段并按下后退按钮(将我带到 globalFragment)时,它再次跳过帧(> 100)但是当我 go 到我的最后一个片段时,这是一个偏好片段并按下它的应用程序挂起并跳过 2500+帧。

我试过的

从适配器中删除数据绑定似乎修复了应用程序启动时跳过的初始帧,但是当我从设置片段导航回来时,应用程序仍然跳过 2500 帧

我删除了首选项片段,现在应用程序没有挂起,但是在返回到全局片段时它仍然会跳帧

我从适配器中删除了单击侦听器,并从 onBind 中删除了其他不必要的逻辑,但问题仍然存在

该项目是开源的,所以你可以在这里查看https://github.com/destructo570/CovidTracker-kotlin

这是我的 Logcat

2020-05-20 13:19:09.658 26583-26583/com.destructo.covidtracker 
W/to.covidtracke: Accessing hidden method 
Ljava/lang/invoke/MethodHandles$Lookup;-><init>(Ljava/lang/Class;I)V 
(greylist, reflection, allowed)
2020-05-20 13:19:09.675 26583-26583/com.destructo.covidtracker 
D/NetworkSecurityConfig: No Network Security Config specified, using platform 
default
2020-05-20 13:19:09.678 26583-26583/com.destructo.covidtracker 
W/to.covidtracke: Accessing hidden method Ldalvik/system/CloseGuard;- 
>get()Ldalvik/system/CloseGuard; (greylist,core-platform-api, reflection, 
allowed)
2020-05-20 13:19:09.678 26583-26583/com.destructo.covidtracker 
W/to.covidtracke: Accessing hidden method Ldalvik/system/CloseGuard;- 
>open(Ljava/lang/String;)V (greylist,core-platform-api, reflection, allowed)
2020-05-20 13:19:09.678 26583-26583/com.destructo.covidtracker 
W/to.covidtracke: Accessing hidden method Ldalvik/system/CloseGuard;- 
>warnIfOpen()V (greylist,core-platform-api, reflection, allowed)
2020-05-20 13:19:10.163 26583-26583/com.destructo.covidtracker 

I/Choreographer: Skipped 41 frames!  The application may be doing too much 
work on its main thread.
2020-05-20 13:19:10.168 26583-26583/com.destructo.covidtracker W/Looper: Slow 
Looper main: doFrame is 686ms late because of 27 msg, msg 1 took 188ms 
(seq=38 running=106ms runnable=6ms late=134ms 
h=android.view.Choreographer$FrameHandler 
c=android.view.Choreographer$FrameDisplayEventReceiver), msg 24 took 451ms 
(seq=61 running=425ms runnable=2ms late=220ms h=android.os.Handler 
c=kotlinx.coroutines.DispatchedContinuation)
2020-05-20 13:19:13.541 26583-26583/com.destructo.covidtracker W/Looper: Slow 
Looper main: Long Msg: seq=116 plan=13:19:12.044  late=1ms wall=1495ms 
running=1411ms runnable=5ms h=android.view.Choreographer$FrameHandler 
c=android.view.Choreographer$FrameDisplayEventReceiver
2020-05-20 13:19:13.543 26583-26583/com.destructo.covidtracker 

I/Choreographer: Skipped 87 frames!  The application may be doing too much 
work on its main thread.
2020-05-20 13:19:13.543 26583-26639/com.destructo.covidtracker 
I/OpenGLRenderer: Davey! duration=1498ms; Flags=0, 
IntendedVsync=645628150832172, Vsync=645628150832172, 
OldestInputEvent=9223372036854775807, NewestInputEvent=0, 
HandleInputStart=645628151894908, AnimationStart=645628152051210, 
PerformTraversalsStart=645628152055168, DrawStart=645629621464439, 
SyncQueued=645629644963710, SyncStart=645629645792251, 
IssueDrawCommandsStart=645629646424022, SwapBuffers=645629648726366, 
FrameCompleted=645629649800324, DequeueBufferDuration=188000, 
QueueBufferDuration=749000, 
2020-05-20 13:19:13.545 26583-26583/com.destructo.covidtracker W/Looper: Slow 
Looper main: doFrame is 1465ms late because of 13 msg, msg 1 took 1495ms 
(seq=116 running=1411ms runnable=5ms late=1ms 
h=android.view.Choreographer$FrameHandler 
c=android.view.Choreographer$FrameDisplayEventReceiver)
2020-05-20 13:19:16.918 26583-26632/com.destructo.covidtracker 
I/to.covidtracke: ProcessProfilingInfo new_methods=6942 is saved 
saved_to_disk=1 resolve_classes_delay=8000
2020-05-20 13:19:40.514 26583-26583/com.destructo.covidtracker 
D/ViewRootImpl: [TouchInput][ViewRootImpl] KeyEvent { action=ACTION_DOWN, 
keyCode=KEYCODE_BACK, scanCode=0, metaState=0, flags=0x48, repeatCount=0, 
eventTime=645656609, downTime=645656609, deviceId=-1, source=0x101, 
displayId=-1 }
2020-05-20 13:19:40.650 26583-26583/com.destructo.covidtracker 
D/ViewRootImpl: [TouchInput][ViewRootImpl] KeyEvent { action=ACTION_UP, 
keyCode=KEYCODE_BACK, scanCode=0, metaState=0, flags=0x48, repeatCount=0, 
eventTime=645656753, downTime=645656609, deviceId=-1, source=0x101, 
displayId=-1 }
2020-05-20 13:19:41.155 26583-26601/com.destructo.covidtracker 
I/to.covidtracke: Background concurrent copying GC freed 197245(8256KB) 
AllocSpace objects, 4(72KB) LOS objects, 49% free, 22MB/45MB, paused 77us 
total 121.932ms
2020-05-20 13:20:11.708 26583-26583/com.destructo.covidtracker W/Looper: Slow 
Looper main: Long Msg: seq=2203 plan=13:19:40.690  late=0ms wall=31018ms 
running=29292ms runnable=126ms h=android.view.Choreographer$FrameHandler 
c=android.view.Choreographer$FrameDisplayEventReceiver
2020-05-20 13:20:11.709 26583-26583/com.destructo.covidtracker W/Looper: Slow 
Looper main: MotionEvent is 10361ms late (event_seq=4, action=ACTION_DOWN) 
because of 1 msg, msg 1 took 31018ms (seq=2203 running=29292ms runnable=126ms 
h=android.view.Choreographer$FrameHandler 
c=android.view.Choreographer$FrameDisplayEventReceiver)
2020-05-20 13:20:11.709 26583-26583/com.destructo.covidtracker 
W/InputEventReceiver: App Input: 10361ms before dispatchInputEvent 
(MotionEvent: event_seq=4, seq=1959042, action=ACTION_DOWN)
2020-05-20 13:20:11.711 26583-26639/com.destructo.covidtracker 
I/OpenGLRenderer: Davey! duration=31020ms; Flags=1, 
IntendedVsync=645656796004117, Vsync=645656796004117, 
OldestInputEvent=9223372036854775807, NewestInputEvent=0, 
HandleInputStart=645656796923387, AnimationStart=645656796961876, 
PerformTraversalsStart=645656797074585, DrawStart=645687793910146, 
SyncQueued=645687812587698, SyncStart=645687813233010, 
IssueDrawCommandsStart=645687813928896, SwapBuffers=645687815157698, 
FrameCompleted=645687817258583, DequeueBufferDuration=189000, 
QueueBufferDuration=1646000, 
2020-05-20 13:20:11.714 26583-26583/com.destructo.covidtracker 
W/InputEventReceiver: App Input: 8571ms before dispatchInputEvent 
(MotionEvent: event_seq=5, seq=1959127, action=ACTION_CANCEL)
2020-05-20 13:20:11.715 26583-26583/com.destructo.covidtracker 

I/Choreographer: Skipped 1860 frames!  The application may be doing too much 
work on its main thread.
2020-05-20 13:20:11.774 26583-26583/com.destructo.covidtracker W/Looper: Slow 
Looper main: doFrame is 31009ms late because of 20 msg, msg 1 took 31018ms 
(seq=2203 running=29292ms runnable=126ms 
h=android.view.Choreographer$FrameHandler
c=android.view.Choreographer$FrameDisplayEventReceiver)
2020-05-20 13:20:11.776 26583-26639/com.destructo.covidtracker 
I/OpenGLRenderer: Davey! duration=31068ms; Flags=0, 
IntendedVsync=645656812653619, Vsync=645687812652379, 
OldestInputEvent=9223372036854775807, NewestInputEvent=0, 
HandleInputStart=645687822730146, AnimationStart=645687822796552, 
PerformTraversalsStart=645687865692802, DrawStart=645687866102750, 
SyncQueued=645687878454625, SyncStart=645687879030510, 
IssueDrawCommandsStart=645687880157177, SwapBuffers=645687881475146, 
FrameCompleted=645687882061812, DequeueBufferDuration=192000, 
QueueBufferDuration=255000, 

我的适配器

class GlobalCountryAdapter (private val onClickListener: GlobalClickListener):
ListAdapter<GlobalCountryStatistics, GlobalCountryAdapter.ViewHolder>(
    GlobalCountryDiffCallback()
) {


override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
    return ViewHolder.from(
        parent
    )
}

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
    val countryData = getItem(position)
    holder.bind(countryData)
    holder.itemView.setOnClickListener{
        onClickListener.onCLick(countryData)
    }
}


class ViewHolder private constructor(val binding: DataListItemViewBinding) : 
RecyclerView.ViewHolder(binding.root) {
    fun bind(countryData: GlobalCountryStatistics) {
        if (countryData.cases_today != null && countryData.cases_today > 0 ){
            binding.increaseIcon.visibility = View.VISIBLE
            binding.newCasesTxt.visibility = View.VISIBLE
        }else{
            binding.increaseIcon.visibility = View.GONE
            binding.newCasesTxt.visibility = View.GONE
        }
        binding.globalCountryData = countryData
        binding.executePendingBindings()
    }

    companion object {
        fun from(parent: ViewGroup): ViewHolder {
            var layoutInflater = LayoutInflater.from(parent.context)
            val binding = DataListItemViewBinding.inflate(layoutInflater, parent, false)
            return ViewHolder(
                binding
            )
        }
    }

}


class GlobalClickListener(val clickListener: (country:GlobalCountryStatistics) -> Unit){
    fun onCLick(country:GlobalCountryStatistics) = clickListener(country)
}

}

class GlobalCountryDiffCallback : DiffUtil.ItemCallback<GlobalCountryStatistics>() {
override fun areItemsTheSame(oldItem: GlobalCountryStatistics, newItem: GlobalCountryStatistics): 
Boolean {
    return oldItem.country_name == newItem.country_name
}

override fun areContentsTheSame(oldItem: GlobalCountryStatistics,
 newItem: GlobalCountryStatistics): Boolean {
    return oldItem == newItem
}

}

全球片段

class GlobalFragment : Fragment() {

private val mglobalViewModel: GlobalViewModel by lazy {
    ViewModelProvider(this).get(GlobalViewModel::class.java)
}

override fun onCreateView(
    inflater: LayoutInflater, container: ViewGroup?,
    savedInstanceState: Bundle?
): View? {

    val binding = FragmentGlobalBinding.inflate(inflater)

    binding.setLifecycleOwner(this)

    binding.globalViewModel = mglobalViewModel


    mglobalViewModel.globalCountryStats.observe(viewLifecycleOwner, Observer {

        val adap = GlobalCountryAdapter(GlobalCountryAdapter.GlobalClickListener {
            mglobalViewModel.navigationToCountryDetail(it)
        })
        adap.submitList(it)
        binding.countryRecycler.adapter = adap

    })

    mglobalViewModel.navigateToCountryDetail.observe(viewLifecycleOwner, Observer {
        if (null != it){
            this.findNavController().navigate(
                GlobalFragmentDirections.actionGlobalFragmentToCountryDetailsFragment2(it))
            mglobalViewModel.doneNavigationToCountryDetail() }
    })

    mglobalViewModel.globalStats.observe(viewLifecycleOwner, Observer { globalSummary ->
        if (null != globalSummary){
            binding.include.globalMoreButton.setOnClickListener{
                this.findNavController().navigate(
                    GlobalFragmentDirections.actionGlobalFragmentToGlobalSummaryDetailsFragment(
                        globalSummary
                    )
                )
            }
        }
    })

    return binding.root
}

}

用于回收站视图的 BindingAdapter

@BindingAdapter("countryList")
fun bindCountryRecyclerView(recyclerView: RecyclerView, mdata: List<GlobalCountryStatistics>?) {

val adapter = recyclerView.adapter as FinalAdapter
mdata?.let {
    adapter.submitList(mdata)
}
}

视图模型

class GlobalViewModel : ViewModel() {

private var _globalStats = MutableLiveData<GlobalCoronaStatistics>()
val globalStats: LiveData<GlobalCoronaStatistics>
    get() = _globalStats

private var _globalCountryStats = MutableLiveData<List<GlobalCountryStatistics>>()
val globalCountryStats:LiveData<List<GlobalCountryStatistics>>
    get() = _globalCountryStats

private val _navigateToCountryDetail = MutableLiveData<GlobalCountryStatistics>()
val navigateToCountryDetail: LiveData<GlobalCountryStatistics>
    get() = _navigateToCountryDetail

private var globalViewModelJob = Job()

private val uiScope = CoroutineScope(globalViewModelJob + Dispatchers.Main)


init {
    getGlobalStatistics()
    getCountryStatsList()
}

private fun getGlobalStatistics() {
    uiScope.launch {
        var getGlobalDataDef = GlobalApi.retrofitService.getGlobalDataAsync()
        try {

            val globalData = getGlobalDataDef.await()
            _globalStats.value = globalData
        } catch (e: Exception) {

            Log.e("GlobalViewModel", "FAILED NETWORK\n" + e.message)
        }
    }
}

private fun getCountryStatsList(){
    uiScope.launch {

        var getCountryDeferred = GlobalApi.retrofitService.getGlobalCountryDataAsync()
        try {
            val globalCountry = getCountryDeferred.await()
            _globalCountryStats.value = globalCountry
        }catch (e:Exception){
            Log.e("GlobalViewModel","FAILED NETWORK\n" + e.message)
        }
    }
}

fun navigationToCountryDetail(selectedCountry:GlobalCountryStatistics){
    _navigateToCountryDetail.value = selectedCountry
}
fun doneNavigationToCountryDetail(){
    _navigateToCountryDetail.value = null
}

override fun onCleared() {
    super.onCleared()
    globalViewModelJob.cancel()
}

}

全球API服务

private const val BASE_URL = "https://disease.sh/v2/"

private val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()

private val retrofit = Retrofit.Builder()
.addConverterFactory(MoshiConverterFactory.create(moshi))
.addCallAdapterFactory(CoroutineCallAdapterFactory())
.baseUrl(BASE_URL)
.build()

interface GlobalApiService {

@GET("all")
fun getGlobalDataAsync(): Deferred<GlobalCoronaStatistics>


@GET("countries?sort=cases")
fun getGlobalCountryDataAsync(): Deferred<List<GlobalCountryStatistics>>

@GET("gov/india")
fun getIndiaDataAsync(): Deferred<IndiaStatistics>
}

object GlobalApi {
val retrofitService: GlobalApiService by lazy { 
retrofit.create(GlobalApiService::class.java) }
}

新答案:

好的,拉你的回购后,我看到了问题。 您对 RecyclerViews 使用layout_height="wrap_content" ,这导致 RecyclerView 必须一次渲染其所有子视图并冻结您的应用程序。 看看我的答案: https://stackoverflow.com/a/57918094/10720040

明白你想要滚动屏幕上半部分的信息和它下面的 RecyclerView,你应该尝试使用 RecyclerView 中的ViewType来实现。

这是我编辑的布局,只是为了让您知道解决方案可以工作:

fragment_country.xml

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">

<data>

    <variable
        name="indiaViewModel"
        type="com.destructo.corona_tracker.country.IndiaViewModel" />

</data>

<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".global.GlobalFragment">

    <ImageView
        android:id="@+id/imageView2"
        android:layout_width="wrap_content"
        android:layout_height="275dp"
        android:src="@drawable/remote_work_man"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <include
        android:id="@+id/include"
        layout="@layout/country_stats_card"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:indiaData="@{indiaViewModel.indiaSummaryData}"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/imageView2" />

    <TextView
        android:id="@+id/textView15"
        style="@style/heading_text_medium"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:alpha="0.6"
        android:text="@string/state_text"
        android:textAllCaps="true"
        app:layout_constraintBaseline_toBaselineOf="@+id/textView16"
        app:layout_constraintStart_toStartOf="parent" />

    <TextView
        android:id="@+id/textView16"
        style="@style/heading_text_medium"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="16dp"
        android:alpha="0.6"
        android:text="@string/infected_text"
        android:textAllCaps="true"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/include" />


    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/stateRecycler"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_marginTop="16dp"
        android:nestedScrollingEnabled="false"
        android:orientation="vertical"
        android:overScrollMode="never"
        app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView15"
        app:stateList="@{indiaViewModel.indiaStateData}" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

老答案:

也许GlobalApi.retrofitService方法没有在后台线程中运行。 您为uiScope设置了Dispatchers.Main ,因此launch {}中的所有内容都有可能在主线程上运行。 您可以注意到,在上面的代码中,您仍然可以在launch中调用_globalStats.value = globalData ,如果在后台线程上运行,则不允许这样做。 尝试这个:

uiScope.launch {
    try {
        val globalData = withContext(Dispatchers.IO) {
             GlobalApi.retrofitService.getGlobalDataAsync() 
        }
        _globalStats.value = globalData
    } catch (e: Exception) {

        Log.e("GlobalViewModel", "FAILED NETWORK\n" + e.message)
    }
}

同样的情况,我使用的代码结构与我遇到的和你制作的代码非常相似。 首先,不要担心模拟器正在模拟的这条消息是电话,因此会生成各种消息。 它只是通过您的手机进行的。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM