Android studio 习惯懒惰<;递延<;T>&燃气轮机;乐趣导致跳帧和崩溃
我使用ROOM、Reformation、Koin DI等跟踪MVVM,在我的MainFragment类中,我调用了我的Android studio 习惯懒惰<;递延<;T>&燃气轮机;乐趣导致跳帧和崩溃,android-studio,kotlin,android-room,kotlin-coroutines,Android Studio,Kotlin,Android Room,Kotlin Coroutines,我使用ROOM、Reformation、Koin DI等跟踪MVVM,在我的MainFragment类中,我调用了我的bindUI()函数,该函数负责使用kotlin协程通过viewModel异步获取数据,如下所示。现在,当我运行我的应用程序时,它几乎立即崩溃 下面是我尝试的:我在bindUI()中放置了一个断点,特别是在我的第一个上。wait()调用val currentWeather并运行调试器。我注意到,一旦wait调用被解决,结果返回到变量,应用程序就会崩溃,说跳过了1501帧!应用程序
bindUI()
函数,该函数负责使用kotlin协程通过viewModel异步获取数据,如下所示。现在,当我运行我的应用程序时,它几乎立即崩溃
下面是我尝试的:我在bindUI()
中放置了一个断点,特别是在我的第一个上。wait()
调用val currentWeather
并运行调试器。我注意到,一旦wait调用被解决,结果返回到变量,应用程序就会崩溃,说跳过了1501帧!应用程序可能在其主线程上做了太多工作。
然后跳过了359帧!应用程序可能在其主线程上做了太多工作。
既然我在Dispathcers.IO
线程中运行这些异步调用,而在崩溃的那一刻,我只执行一个wait()调用,那么为什么会这样呢
这是我的MainFragment类:
const val UNIT_SYSTEM_KEY = "UNIT_SYSTEM"
class MainFragment(
private val weatherUnitConverter: WeatherUnitConverter
) : ScopedFragment() {
// Lazy inject the view model
private val viewModel: WeatherViewModel by viewModel()
private lateinit var unitSystem:String
private val TAG = MainFragment::class.java.simpleName
// View declarations
private lateinit var lcHourlyForecasts: LineChart
private lateinit var weeklyForecastRCV: RecyclerView
private lateinit var scrollView: NestedScrollView
private lateinit var detailsExpandedArrow:ImageView
private lateinit var detailsExpandedLayout: LinearLayout
private lateinit var dailyWeatherDetailsHeader:LinearLayout
private lateinit var settingsBtnImageView:ImageView
private lateinit var unitSystemImgView:ImageView
private lateinit var locationTxtView:TextView
// Current weather view declarations
private lateinit var currentWeatherDate:TextView
private lateinit var currentWeatherTemp:TextView
private lateinit var currentWeatherSummaryText:TextView
private lateinit var currentWeatherSummaryIcon:ImageView
private lateinit var currentWeatherPrecipProb:TextView
// Today/Details weather view declarations
private lateinit var todayHighLowTemp:TextView
private lateinit var todayWindSpeed:TextView
private lateinit var todayFeelsLike:TextView
private lateinit var todayUvIndex:TextView
private lateinit var todayPrecipProb:TextView
private lateinit var todayCloudCover:TextView
private lateinit var todayHumidity:TextView
private lateinit var todayPressure:TextView
private lateinit var todaySunriseTime:TextView
private lateinit var todaySunsetTime:TextView
// OnClickListener to handle the current weather's "Details" layout expansion/collapse
private val onCurrentWeatherDetailsClicked:View.OnClickListener = View.OnClickListener {
if(detailsExpandedLayout.visibility == View.GONE) {
detailsExpandedLayout.visibility = View.VISIBLE
detailsExpandedArrow.setImageResource(R.drawable.ic_arrow_up_black)
}
else {
detailsExpandedLayout.visibility = View.GONE
detailsExpandedArrow.setImageResource(R.drawable.ic_down_arrow)
}
}
// OnClickListener to allow navigating from this fragment to the settings one
private val onSettingsButtonClicked:View.OnClickListener = View.OnClickListener {
(activity as MainActivity).openSettingsPage()
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View {
val view = inflater.inflate(R.layout.main_fragment, container, false)
// View initializations
scrollView = view.findViewById(R.id.nsv_main)
lcHourlyForecasts = view.findViewById(R.id.lc_hourly_forecasts)
detailsExpandedLayout = view.findViewById(R.id.ll_expandable)
detailsExpandedArrow = view.findViewById(R.id.iv_arrow)
dailyWeatherDetailsHeader = view.findViewById(R.id.current_weather_details_header)
dailyWeatherDetailsHeader.setOnClickListener(onCurrentWeatherDetailsClicked)
settingsBtnImageView = view.findViewById(R.id.settings)
settingsBtnImageView.setOnClickListener(onSettingsButtonClicked)
unitSystemImgView = view.findViewById(R.id.unitSystemImg)
locationTxtView = view.findViewById(R.id.location)
initCurrentWeatherViews(view)
initTodayWeatherViews(view)
// RCV initialization
weeklyForecastRCV = view.findViewById(R.id.weekly_forecast_rcv)
weeklyForecastRCV.adapter = WeeklyWeatherAdapter(listOf(),viewModel.preferences, this,weatherUnitConverter) // init the adapter with empty data
weeklyForecastRCV.setHasFixedSize(true)
// Disable nested scrolling to control the RCV scrolling via the parent NestedScrollView
weeklyForecastRCV.isNestedScrollingEnabled = false
return view
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
initLineChart()
bindUI()
}
private fun SharedPreferences.stringLiveData(key: String, defValue: String): SharedPreferenceLiveData<String> {
return SharedPreferenceStringLiveData(this, key, defValue)
}
private fun bindUI() = launch(Dispatchers.Main) {
//TODO:sp get the coordinates dynamically
viewModel.setLocCoordinates(37.8267,-122.4233)
// fetch current weather
val currentWeather = viewModel.currentWeatherData.await()
// fetch weekly weather
val weeklyWeather = viewModel.weeklyWeatherEntries.await()
// fetch the location
val weatherLocation = viewModel.weatherLocation.await()
// Observe the location for changes
weatherLocation.observe(viewLifecycleOwner, Observer { location ->
if(location == null) return@Observer
launch {
updateLocation(location)
}
})
// Observe the current weather live data
currentWeather.observe(viewLifecycleOwner, Observer {currently ->
if(currently == null) return@Observer
setCurrentWeatherDate(currently.time.toDouble())
// Observe the unit system sharedPrefs live data for changes
viewModel.preferences.stringLiveData(UNIT_SYSTEM_KEY, UnitSystem.SI.name.toLowerCase(Locale.ROOT))
.observe(viewLifecycleOwner, Observer {unitSystem ->
when(unitSystem) {
UnitSystem.SI.name.toLowerCase(Locale.ROOT) -> {
setCurrentWeatherTemp(currently.temperature)
setUnitSystemImgView(unitSystem)
}
UnitSystem.US.name.toLowerCase(Locale.ROOT) -> {
setCurrentWeatherTemp(weatherUnitConverter.convertToFahrenheit(
currently.temperature
))
setUnitSystemImgView(unitSystem)
}
}
})
setCurrentWeatherSummaryText(currently.summary)
setCurrentWeatherSummaryIcon(currently.icon)
setCurrentWeatherPrecipProb(currently.precipProbability)
})
// observe the weekly weather live data
weeklyWeather.observe(viewLifecycleOwner, Observer {weatherEntries ->
if(weatherEntries == null) return@Observer
// update the recyclerView with the new data
(weeklyForecastRCV.adapter as WeeklyWeatherAdapter).updateWeeklyWeatherData(weatherEntries)
initTodayData(weatherEntries[0])
})
}
/**
* Uses the location param's lat & longt values
* to determine the selected location and updates
* the view.
*/
private suspend fun updateLocation(location: WeatherLocation) {
withContext(Dispatchers.IO) {
val geocoder = Geocoder(activity,Locale.getDefault())
try {
val addr = geocoder.getFromLocation(location.latitude,location.longitude,1)
val adobj = addr[0]
locationTxtView.text = adobj.countryName
} catch (e:IOException) {
Log.d(TAG, e.printStackTrace().toString())
}
}
}
/**
* Initializes the views for the current weather.
*/
private fun initCurrentWeatherViews(view: View) {
currentWeatherDate = view.findViewById(R.id.current_weather_date)
currentWeatherTemp = view.findViewById(R.id.current_temp_main)
currentWeatherSummaryText = view.findViewById(R.id.current_weather_summary_text)
currentWeatherSummaryIcon = view.findViewById(R.id.current_weather_summary_icon)
currentWeatherPrecipProb = view.findViewById(R.id.current_weather_precip_text)
}
/**
* Initializes the views for the Detailed Today weather view.
*/
private fun initTodayWeatherViews(view: View?) {
if(view == null) return
todayHighLowTemp = view.findViewById(R.id.today_lowHighTemp)
todayWindSpeed = view.findViewById(R.id.today_windSpeed)
todayFeelsLike = view.findViewById(R.id.today_feelsLike)
todayUvIndex = view.findViewById(R.id.today_uvIndex)
todayPrecipProb = view.findViewById(R.id.today_precipProb)
todayCloudCover = view.findViewById(R.id.today_cloudCover)
todayHumidity = view.findViewById(R.id.today_humidity)
todayPressure = view.findViewById(R.id.today_pressure)
todaySunriseTime = view.findViewById(R.id.today_sunriseTime)
todaySunsetTime = view.findViewById(R.id.today_sunsetTime)
}
private fun setUnitSystemImgView(unitSystem:String) {
val resource = when(unitSystem) {
UnitSystem.SI.name.toLowerCase(Locale.ROOT)
-> R.drawable.ic_celsius
UnitSystem.US.name.toLowerCase(Locale.ROOT)
-> R.drawable.ic_fahrenheit
else -> R.drawable.ic_celsius
}
unitSystemImgView.setImageResource(resource)
}
/**
* Links the data to the view for the Today(Details) Weather View.
*/
private fun initTodayData(weekDayWeatherEntry: WeekDayWeatherEntry) {
// Observe the unit system sharedPrefs live data for changes
viewModel.preferences.stringLiveData(UNIT_SYSTEM_KEY, UnitSystem.SI.name.toLowerCase(Locale.ROOT))
.observe(viewLifecycleOwner, Observer {unitSystem ->
when(unitSystem) {
UnitSystem.SI.name.toLowerCase(Locale.ROOT) -> {
setTodayWeatherLowHighTemp(weekDayWeatherEntry.temperatureLow,weekDayWeatherEntry.temperatureHigh)
setTodayWeatherWindSpeed(weekDayWeatherEntry.windSpeed,unitSystem)
setTodayWeatherFeelsLike(weekDayWeatherEntry.apparentTemperatureLow,weekDayWeatherEntry.apparentTemperatureHigh)
}
UnitSystem.US.name.toLowerCase(Locale.ROOT) -> {
setTodayWeatherLowHighTemp(weatherUnitConverter.convertToFahrenheit(
weekDayWeatherEntry.temperatureLow),
weatherUnitConverter.convertToFahrenheit(
weekDayWeatherEntry.temperatureHigh))
setTodayWeatherWindSpeed(weatherUnitConverter.convertToMiles(weekDayWeatherEntry.windSpeed),unitSystem)
setTodayWeatherFeelsLike(weatherUnitConverter.convertToFahrenheit(
weekDayWeatherEntry.apparentTemperatureLow)
,weatherUnitConverter.convertToFahrenheit(weekDayWeatherEntry.apparentTemperatureHigh))
}
}
})
setTodayWeatherUvIndex(weekDayWeatherEntry.uvIndex)
setTodayWeatherPrecipProb(weekDayWeatherEntry.precipProbability)
setTodayWeatherCloudCover(weekDayWeatherEntry.cloudCover)
setTodayWeatherHumidity(weekDayWeatherEntry.humidity)
setTodayWeatherPressure(weekDayWeatherEntry.pressure)
setTodayWeatherSunriseTime(weekDayWeatherEntry.sunriseTime)
setTodayWeatherSunsetTime(weekDayWeatherEntry.sunsetTime)
}
...
}
下面是我的Delegates.kt文件中的自定义Lazy
乐趣:
fun<T> lazyDeferred(block: suspend CoroutineScope.() -> T) : Lazy<Deferred<T>> {
return lazy {
GlobalScope.async(start = CoroutineStart.LAZY) {
block.invoke(this)
}
}
}
它将我的repository类的这一部分作为根本原因。我不知道为什么
private fun persistFetchedCurrentWeather(fetchedCurrentWeather:CurrentWeatherResponse) {
// Using a GlobalScope since a Repository class doesn't have a lifecycle
GlobalScope.launch(Dispatchers.IO) {
// cache the data
currentWeatherDao.upsert(fetchedCurrentWeather.currentWeatherEntry)
weatherLocationDao.upsert(fetchedCurrentWeather.location)
}
}
更新#2:
当前天气条目:
const val CURRENT_WEATHER_ID = 0
@Entity(tableName = "current_weather")
data class CurrentWeatherEntry(
val time: Long, // epoch timestamp
val icon: String,
val summary: String,
val precipProbability: Double,
val temperature: Double
) {
@PrimaryKey(autoGenerate = false)
var id:Int = CURRENT_WEATHER_ID
}
天气位置:
const val WEATHER_LOCATION_ID = 0
@Entity(tableName = "weather_location")
data class WeatherLocation(
val latitude: Double,
val longitude: Double,
val timezone: String
) {
@PrimaryKey(autoGenerate = false)
var id:Int = WEATHER_LOCATION_ID
private var epochTimeVal:Long = 0
val zonedDateTime:ZonedDateTime
get() {
val instant = Instant.ofEpochMilli(this.epochTimeVal)
val zoneId = ZoneId.of(timezone)
return ZonedDateTime.ofInstant(instant,zoneId)
}
fun setEpochTimeVal(time:Long) {
this.epochTimeVal = time}
fun getEpochTimeVal() : Long = epochTimeVal
}
及回应:
data class CurrentWeatherResponse(
// Tells GSON that the "currently" field of the JSON returned by the
// API should be tied with our CurrentWeatherEntry data class
@SerializedName("currently")
val currentWeatherEntry: CurrentWeatherEntry,
@Embedded
val location: WeatherLocation
) {
init {
location.setEpochTimeVal(currentWeatherEntry.time)
}
}
您从未指定应在哪里执行
forecastRepository.getWeatherLocation()
,以便在bindUI
函数的调度程序上执行它,该函数是Dispatchers.Main
。
这意味着请求会阻塞您的UI线程,并导致您在日志中看到的警告
您需要指定它在单独的调度程序上执行,以便UI可以继续正常更新:
懒散{
withContext(Dispatchers.IO){
forecasterepository.getWeatherLocation()
}
}
作为一个单独的问题,您的
lazydereferred
有点多余,因为它是“双懒人”。您可以删除外部的Lazy
,它仍然会以完全相同的方式工作,或者删除start=CoroutineStart.Lazy
,让结果稍早到达。(这基本上取决于请求是在解决了延迟时启动,还是在调用延迟.wait
时启动)您从未指定forecastRepository.getWeatherLocation()的位置
应该被执行,以便它在bindUI
函数的调度程序上执行,该函数是Dispatchers.Main
。
这意味着请求会阻塞您的UI线程,并导致您在日志中看到的警告
您需要指定它在单独的调度程序上执行,以便UI可以继续正常更新:
懒散{
withContext(Dispatchers.IO){
forecasterepository.getWeatherLocation()
}
}
作为一个单独的问题,您的lazydereferred
有点多余,因为它是“双懒人”。您可以删除外部的Lazy
,它仍然会以完全相同的方式工作,或者删除start=CoroutineStart.Lazy
,让结果稍早到达。(这基本上取决于请求是在解决了延迟
时启动的,还是在调用了延迟.等待
时启动的)根据您所做的进一步诊断,该问题与延迟
、阻塞主线程或通常的协同程序无关。您的CurrentWeatherDao
有时返回CurrentWeatherResponse
,其位置==null
根据您所做的进一步诊断,该问题与lazydereferred
、阻塞主线程或通常的协同程序无关。您的CurrentWeatherDao
有时会返回CurrentWeatherResponse
,其中location==null
谢谢您的帮助。我在这里可能会感到困惑,但在我的repository类中,我不是使用with context{}
将执行forecastRepository.getWeatherLocation()
的上下文指定为Dispatchers.IO
?根据您的回答,我也尝试在我的viewModel中指定它,它似乎已将跳过的帧减少到~150,现在只记录了一次。但是,我仍然不确定getWeatherLocation与getCurrentWeather
和GetWeatherDayWeatherList
有什么不同。我也在做同样的事情,我是否应该在viewmodel中再次指定上下文?谢谢您的帮助。我在这里可能会感到困惑,但在我的repository类中,我不是使用with context{}
将执行forecastRepository.getWeatherLocation()
的上下文指定为Dispatchers.IO
?根据您的回答,我也尝试在我的viewModel中指定它,它似乎已将跳过的帧减少到~150,现在只记录了一次。但是,我仍然不确定getWeatherLocation与getCurrentWeather
和GetWeatherDayWeatherList
有什么不同。我也在做同样的事情,我是否应该在viewmodel中再次指定上下文?您的DAO函数是否已经suspend fun
s?他们不需要任何带有上下文的。除此之外,您的lazydereferred
使用GlobalScope
,从而使用Default
dispatcher,因此DAO调用两次从UI线程中删除。难道使用调试器的行为不会导致那些“N帧跳过”警告,而崩溃本身的原因完全不同吗?顺便说一句,return@withContext
是多余的。块中的最后一个表达式决定其返回值。@MarkoTopolnik实际上不是,我的DAO函数是普通函数。将它们定义为suspend fun
是更好的做法吗?应该在哪个范围内
const val CURRENT_WEATHER_ID = 0
@Entity(tableName = "current_weather")
data class CurrentWeatherEntry(
val time: Long, // epoch timestamp
val icon: String,
val summary: String,
val precipProbability: Double,
val temperature: Double
) {
@PrimaryKey(autoGenerate = false)
var id:Int = CURRENT_WEATHER_ID
}
const val WEATHER_LOCATION_ID = 0
@Entity(tableName = "weather_location")
data class WeatherLocation(
val latitude: Double,
val longitude: Double,
val timezone: String
) {
@PrimaryKey(autoGenerate = false)
var id:Int = WEATHER_LOCATION_ID
private var epochTimeVal:Long = 0
val zonedDateTime:ZonedDateTime
get() {
val instant = Instant.ofEpochMilli(this.epochTimeVal)
val zoneId = ZoneId.of(timezone)
return ZonedDateTime.ofInstant(instant,zoneId)
}
fun setEpochTimeVal(time:Long) {
this.epochTimeVal = time}
fun getEpochTimeVal() : Long = epochTimeVal
}
data class CurrentWeatherResponse(
// Tells GSON that the "currently" field of the JSON returned by the
// API should be tied with our CurrentWeatherEntry data class
@SerializedName("currently")
val currentWeatherEntry: CurrentWeatherEntry,
@Embedded
val location: WeatherLocation
) {
init {
location.setEpochTimeVal(currentWeatherEntry.time)
}
}