android - 为什么我的 android 应用程序在使用后退按钮导航并从 Internet 加载数据时会跳帧?(导航组件)
问题描述
所以我有一个底部导航设置,其中包含 5 个带有导航组件的片段。当应用程序启动时,将数据加载到回收器视图时,globalFragment 会跳过 90 多帧。当我转到其他片段并按后退按钮(将我带到 globalFragment)时,它再次跳过帧(> 100)但是当我转到我的最后一个片段时,这是一个偏好片段并按回它的应用程序挂起并跳过 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) }
}
解决方案
新答案:
好的,拉你的回购后,我看到了问题。您使用layout_height="wrap_content"
RecyclerViews,这导致 RecyclerView 必须一次渲染其所有子视图并冻结您的应用程序。看看我的答案:https ://stackoverflow.com/a/57918094/10720040
明白你想要滚动屏幕上半部分的信息和它下面的 RecyclerView,你应该尝试ViewType
在 RecyclerView 中使用来实现。
这是我编辑的布局,只是为了让您知道解决方案可以工作:
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
方法没有在后台线程中运行。您设置Dispatchers.Main
为uiScope
,所以里面的所有东西都有可能launch {}
在主线程上运行。您可以注意到,在上面的代码中,您仍然可以调用_globalStats.value = globalData
inside launch
,如果在后台线程上运行,则不允许这样做。尝试这个:
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)
}
}
推荐阅读
- android - 在模拟器相机应用程序中看不到虚拟场景图像
- c# - String variable as pattern in Regex() constructor
- javascript - PDF 文件未打开打印给定错误
- javascript - How to make actual dots on a slick slideshow?
- vue.js - Vue - 禁用 Eslint
- html - 数据列表是否可以向下滚动?
- java - spring boot application would not statring from cmd how to start it from cmd
- c++ - C++中的纯虚拟赋值运算符
- android - 使用工具栏主页时共享元素转换不起作用
- vba - 将单词修订拆分为修订而不更改应用的段落样式