Android studio 习惯懒惰<;递延<;T>&燃气轮机;乐趣导致跳帧和崩溃

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帧!应用程序

我使用ROOM、Reformation、Koin DI等跟踪MVVM,在我的MainFragment类中,我调用了我的
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)
    }
}