[英]Using coroutines in Kotlin to replace AsyncTask, Building a google maps Kotlin android app and I cant implement coroutines instead of AsyncTask
I have previously asked this question and on advice of a user I am posting again this time with my attempt at implementing Coroutines.我之前曾问过这个问题,并且根据用户的建议,这次我再次发布了我尝试实现协程的帖子。 I am trying to build an app to plot a route using google maps and directions API and the tutorial I am following is old and uses AsyncTask but this of course is depreciated so now I have to implement coroutines and I am struggling with this.
我正在尝试使用谷歌地图和方向 API 构建一个应用程序到 plot 的路线,我正在关注的教程是旧的并且使用 AsyncTask 但这当然已经贬值所以现在我必须实现协程并且我正在努力解决这个问题。 here is my code before with AsyncTask and after with my attempt at coroutines:
这是我在使用 AsyncTask 之前和尝试协程之后的代码:
inner class ParserTask :
AsyncTask<String?, Int?, List<List<HashMap<String, String>>>?> () {
override fun doInBackground(vararg jsonData: String?): List<List<HashMap<String, String>>>? {
val jObject: JSONObject
var routes: List<List<HashMap<String, String>>>? =
null
try {
jObject = JSONObject(jsonData[0])
val parser = DataParser()
routes = parser.parse(jObject)
} catch (e: java.lang.Exception) {
e.printStackTrace()
}
return routes
}
override fun onPostExecute(result: List<List<HashMap<String, String>>>?) {
val points = ArrayList<LatLng?>()
val lineOptions = PolylineOptions()
for (i in result!!.indices) {
val path =
result[i]
for (j in path.indices) {
val point = path[j]
val lat = point["lat"]!!.toDouble()
val lng = point["lng"]!!.toDouble()
val position = LatLng(lat, lng)
points.add(position)
}
lineOptions.addAll(points)
lineOptions.width(8f)
lineOptions.color(Color.RED)
lineOptions.geodesic(true)
}
if (points.size != 0) mMap!!.addPolyline(lineOptions)
}
}
inner class DownloadTask :
AsyncTask<String?, Void?, String>(){
override fun onPostExecute(result: String) {
super.onPostExecute(result)
val parserTask = ParserTask()
parserTask.execute(result)
}
override fun doInBackground(vararg url: String?): String {
var data = ""
try{
data = downloadUrl(url[0].toString()).toString()
} catch (e: java.lang.Exception) {
Log.d("Background Task", e.toString())
}
return data
}
}
private fun ParserTask (vararg jsonData: String?, result: List<List<HashMap<String, String>>>?): List<List<HashMap<String, String>>>? {
async {
val points = ArrayList<LatLng?>()
val lineOptions = PolylineOptions()
for (i in result!!.indices) {
val path =
result[i]
for (j in path.indices) {
val point = path[j]
val lat = point["lat"]!!.toDouble()
val lng = point["lng"]!!.toDouble()
val position = LatLng(lat, lng)
points.add(position)
}
lineOptions.addAll(points)
lineOptions.width(8f)
lineOptions.color(Color.RED)
lineOptions.geodesic(true)
}
if (points.size != 0) mMap!!.addPolyline(lineOptions)
}
val jObject: JSONObject
var routes: List<List<HashMap<String, String>>>? =
null
try {
jObject = JSONObject(jsonData[0])
val parser = DataParser()
routes = parser.parse(jObject)
} catch (e: java.lang.Exception) {
e.printStackTrace()
}
return routes
}
private fun DownloadTask (vararg url: String?, result String): String {
async {
super.onPostExecute(result)
val parserTask = ParserTask()
parserTask.execute(result)
}
var data = ""
try{
data = downloadUrl(url[0].toString()).toString()
} catch (e: java.lang.Exception) {
Log.d("Background Task", e.toString())
}
return data
}
class MainActivity : AppCompatActivity(), OnMapReadyCallback, CoroutineScope by MainScope ()
//, LocationListener, GoogleMap.OnCameraMoveListener, GoogleMap.OnCameraMoveStartedListener, GoogleMap.OnCameraIdleListener
{
private var mMap: GoogleMap? = null
lateinit var mapView: MapView
private val MAP_VIEW_BUNDLE_KEY = "MapViewBundleKey"
private val DEFAULT_ZOOM = 15f
lateinit var B_search: Button
lateinit var tvCurrentAddress: TextView
private var fusedLocationProviderClient: FusedLocationProviderClient? = null
var end_latitude = 0.0
var end_longitude = 0.0
lateinit var origin: MarkerOptions
lateinit var destination: MarkerOptions
var latitude = 0.0
var longitude = 0.0
override fun onMapReady(googleMap: GoogleMap) {
mapView.onResume()
mMap = googleMap
//askPermissionLocation()
if (ActivityCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_COARSE_LOCATION
) != PackageManager.PERMISSION_GRANTED
) {
return
}
mMap!!.setMyLocationEnabled(true)
// mMap!!.setOnCameraMoveListener (this)
// mMap!!.setOnCameraMoveStartedListener(this)
// mMap!!.setOnCameraIdleListener(this)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mapView = findViewById<MapView>(R.id.map1)
tvCurrentAddress = findViewById<TextView>(R.id.tvAdd)
B_search = findViewById(R.id.B_search)
//askPermissionLocation()
var mapViewBundle: Bundle? = null
if (savedInstanceState != null) {
mapViewBundle = savedInstanceState.getBundle(MAP_VIEW_BUNDLE_KEY)
}
mapView.onCreate(mapViewBundle)
mapView.getMapAsync(this)
B_search.setOnClickListener {
searchArea()
}
}
private fun searchArea() {
val tf_location =
findViewById<View>(R.id.TF_location) as EditText
val location = tf_location.text.toString()
var addressList: List<Address>? = null
val markerOptions = MarkerOptions()
if (location != "") {
val geocoder = Geocoder(applicationContext)
try {
addressList = geocoder.getFromLocationName(location, 5)
} catch (e: IOException) {
e.printStackTrace()
}
if (addressList != null) {
for (i in addressList.indices) {
val myAddress = addressList[i]
val latLng =
LatLng(myAddress.latitude, myAddress.longitude)
markerOptions.position(latLng)
mMap!!.addMarker(markerOptions)
end_latitude = myAddress.latitude
end_longitude = myAddress.longitude
mMap!!.animateCamera(CameraUpdateFactory.newLatLng(latLng))
val mo = MarkerOptions()
mo.title("Distance")
val results = FloatArray(10)
Location.distanceBetween(
latitude,
longitude,
end_latitude,
end_longitude,
results
)
val s =
String.format("%.1f", results[0] / 1000)
//Setting marker to draw route between these two points
origin = MarkerOptions().position(LatLng(latitude, longitude))
.title("HSR Layout").snippet("origin")
destination =
MarkerOptions().position(LatLng(end_latitude, end_longitude))
.title(tf_location.text.toString())
.snippet("Distance = $s KM")
mMap!!.addMarker(destination)
mMap!!.addMarker(origin)
Toast.makeText(
this@MainActivity,
"Distance = $s KM",
Toast.LENGTH_SHORT
).show()
tvCurrentAddress!!.setText("Distance = $s KM")
//getting URL to the google Directions API
val url: String =
getDirectionsUrl(origin!!.getPosition(), destination!!.getPosition())!!
//val downloadTask = DownloadTask()
//start downloading the json data from google directions API
DownloadTask(url)
}
}
}
}
@Throws(IOException::class)
private fun downloadUrl(strUrl: String): String? {
var data = ""
var iStream: InputStream? = null
var urlConnection: HttpURLConnection? = null
try {
val url = URL(strUrl)
urlConnection = url.openConnection() as HttpURLConnection
urlConnection.connect()
iStream = urlConnection!!.inputStream
val br =
BufferedReader(InputStreamReader(iStream))
val sb = StringBuffer()
var line: String? = ""
while (br.readLine().also { line = it } != null) {
sb.append(line)
}
data = sb.toString()
br.close()
} catch (e: java.lang.Exception) {
Log.d("Exception", e.toString())
} finally {
iStream!!.close()
urlConnection!!.disconnect()
}
return data
}
//parsing into JSON format
private fun ParserTask (vararg jsonData: String?, result: List<List<HashMap<String, String>>>?): List<List<HashMap<String, String>>>? {
async {
val points = ArrayList<LatLng?>()
val lineOptions = PolylineOptions()
for (i in result!!.indices) {
val path =
result[i]
for (j in path.indices) {
val point = path[j]
val lat = point["lat"]!!.toDouble()
val lng = point["lng"]!!.toDouble()
val position = LatLng(lat, lng)
points.add(position)
}
lineOptions.addAll(points)
lineOptions.width(8f)
lineOptions.color(Color.RED)
lineOptions.geodesic(true)
}
if (points.size != 0) mMap!!.addPolyline(lineOptions)
}
val jObject: JSONObject
var routes: List<List<HashMap<String, String>>>? =
null
try {
jObject = JSONObject(jsonData[0])
val parser = DataParser()
routes = parser.parse(jObject)
} catch (e: java.lang.Exception) {
e.printStackTrace()
}
return routes
}
private fun DownloadTask (vararg url: String?, result String): String {
async {
super.onPostExecute(result)
val parserTask = ParserTask()
parserTask.execute(result)
}
var data = ""
try{
data = downloadUrl(url[0].toString()).toString()
} catch (e: java.lang.Exception) {
Log.d("Background Task", e.toString())
}
return data
}
private fun getDirectionsUrl(origin: LatLng, dest: LatLng): String?{
//Origin of route
val str_origin = "origin=" + origin.latitude + "," + origin.longitude
//Destination of Route
val str_destination = "destination" + dest.latitude + "," + dest.longitude
//transportation mode
val mode = "mode=walking"
//building parameters of webservice
val parameters = "$str_origin&$str_destination&$mode"
//output format
val output = "json"
//building the url to the web service
return "https://maps.googleapis.com/maps/api/directions/$output?$parameters&key=AIzaSyCgraKSwPfUIyZLOmEDh_ptAbfRRAj7y1g"
}
public override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
//askPermissionLocation()
var mapViewBundle = outState.getBundle(MAP_VIEW_BUNDLE_KEY)
if (mapViewBundle == null){
mapViewBundle = Bundle()
outState.putBundle(MAP_VIEW_BUNDLE_KEY, mapViewBundle)
}
mapView.onSaveInstanceState(mapViewBundle)
}
/*private fun askPermissionLocation(){
askPermission(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION
){
getCurrentLocation()
// mapView.getMapAsync(this@MyNavigationActivity)
}
}*/
private fun getCurrentLocation() {
fusedLocationProviderClient =
LocationServices.getFusedLocationProviderClient(this@MainActivity)
try {
@SuppressLint ("MissingPermission")
val location =
fusedLocationProviderClient!!.getLastLocation()
location.addOnCompleteListener(object : OnCompleteListener<Location> {
override fun onComplete(loc: Task<Location>) {
if (loc.isSuccessful) {
val currentLocation = loc.result as Location?
if (currentLocation != null) {
moveCamera(
LatLng(currentLocation.latitude, currentLocation.longitude),
DEFAULT_ZOOM
)
latitude = currentLocation.latitude
longitude = currentLocation.longitude
}
} else {
//askPermissionLocation()
}
}
})
} catch (se: Exception) {
Log.e("TAG", "Security Exception")
}
}
private fun moveCamera(latLng: LatLng, zoom: Float) {
// mMap!!.moveCamera(CameraUpdateFactory.newLatLngZoom(latLng, zoom))
}
// override fun onLocationChanged(location: Location?) {
// val geocoder = Geocoder (this, Locale.getDefault())
// var addresses: List<Address>? = null
// try {
// addresses = geocoder.getFromLocation(location!!.latitude, location.longitude, 1)
// } catch (e: IOException) {
// e.printStackTrace()
// }
// setAddress(addresses!![0])
// }
//
// private fun setAddress(addresses: Address) {
// if (addresses != null) {
//
// if (addresses.getAddressLine(0) != null) {
// tvCurrentAddress!!.setText(addresses.getAddressLine(0))
// }
// if (addresses.getAddressLine(1) != null) {
// tvCurrentAddress!!.setText(
// tvCurrentAddress.getText().toString() + addresses.getAddressLine(1)
// )
// }
// }
// }
//
//
//
// override fun onStatusChanged(p0: String?, p1: Int, p2: Bundle?) {
//
// }
//
// override fun onProviderEnabled(p0: String?) {
//
// }
//
// override fun onProviderDisabled(p0: String?) {
//
// }
//
// override fun onCameraMove() {
//
// }
//
// override fun onCameraMoveStarted(p0: Int) {
//
// }
//
// override fun onCameraIdle() {
// var addresses: List<Address>? = null
// val geocoder = Geocoder (this, Locale.getDefault())
// try {
// addresses = geocoder.getFromLocation( mMap!!.getCameraPosition().target.latitude, mMap!!.getCameraPosition().target.longitude, 1)
//
// setAddress(addresses!![0])
//
// } catch (e: IndexOutOfBoundsException) {
// e.printStackTrace()
// } catch (e: IOException) {
// e.printStackTrace()
// }
// }
}
Don't do this: CoroutineScope by MainScope()
.不要这样做:
CoroutineScope by MainScope()
。 This pattern is from a long time ago when coroutines were brand new and the Android framework didn't have proper coroutine scopes provided yet.这种模式来自很久以前,当时协程是全新的,Android 框架还没有提供合适的协程作用域。 You should launch your coroutines from the provided
lifecycleScope
, or viewModelScope
if it's in a ViewModel.您应该从提供的
lifecycleScope
或viewModelScope
如果它在 ViewModel 中)启动协程。 These provided coroutine scopes have proper lifecycles and cancel their current work automatically when the associated lifecycle object (eg the Activity) is destroyed to avoid leaking memory. This is one of the big benefits of using coroutines.这些提供的协程范围具有适当的生命周期,并在关联的生命周期 object(例如 Activity)被销毁时自动取消它们当前的工作,以避免泄漏 memory。这是使用协程的一大好处。 Your AsyncTask implementations above are leaking memory when the Activity is destroyed.
当 Activity 被销毁时,上面的 AsyncTask 实现正在泄漏 memory。 Proper use of an AsyncTask would involve cancelling any ongoing tasks in
onDestroy()
, and is not trivial to do like it is with coroutines.正确使用 AsyncTask 将涉及取消
onDestroy()
中任何正在进行的任务,并且不像协程那样简单。
Don't do this:不要这样做:
fun someFunction() {
async {
//...
}
// some code that's supposed to happen after the asynchronous work
}
When you call async
, you are firing off an asynchronous coroutine.当您调用
async
时,您将触发一个异步协程。 The code beneath the async
block will run on the current thread immediately, likely before the coroutine code inside the async
block. async
块下面的代码将立即在当前线程上运行,可能在async
块内的协程代码之前运行。 Instead, make your whole function a suspend
function, and wrap the long-running/blocking code in withContext
using an appropriate Dispatcher.相反,使整个 function 成为
suspend
function,并使用适当的 Dispatcher 将长时间运行/阻塞代码包装在withContext
中。 This allows all the code in your function to run synchronously (top-to-bottom in order) without blocking the main thread.这允许您的 function 中的所有代码同步运行(按从上到下的顺序)而不会阻塞主线程。
For AsyncTasks that only utilized doInBackground
and onPostExecute
, here is how they can be converted into proper suspend functions:对于仅使用
doInBackground
和onPostExecute
的 AsyncTasks,以下是如何将它们转换为适当的挂起函数:
suspend fun foo(/* The parameters you would have passed to AsyncTask constructor */) {
val backgroundResult = withContext(Dispatchers.Default) {
// The code you would have had in doInBackground.
// Last line of withContext lambda should evaluate to your result, what you would have
// returned in doInBackground.
}
withContext(Dispatchers.Main) {
// The code you would have had in onPostExecute. You can use the value of
// backgroundResult here.
}
}
Replace Dispatchers.Default
with Dispatchers.IO
if your background work is primarily IO-based .network or disk access) instead of CPU-based.将
Dispatchers.Default
替换为Dispatchers.IO
(如果您的后台工作主要是基于 IO 的.network 或磁盘访问)而不是基于 CPU。
Since you have converted these tasks into suspend
functions, somewhere you will have to launch a coroutine to be able to call them.由于您已将这些任务转换为
suspend
函数,因此您必须在某处启动协程才能调用它们。 So you will launch a coroutine somewhere using lifecycleScope.launch { }
to call these.因此,您将使用
lifecycleScope.launch { }
在某处启动协程来调用它们。 Just remember that launching a coroutine with launch
or async
fires it off asynchronously.请记住,使用
launch
或async
启动协程会异步触发它。 It is only synchronous inside the coroutine.它仅在协程内部是同步的。 Alternatively, if you're only using these tasks from one spot and they don't have to return anything when they're finished, you could make them into regular functions that launch a coroutine internally.
或者,如果您只从一个地方使用这些任务并且它们在完成时不必返回任何东西,您可以将它们变成在内部启动协程的常规函数。 And if you are launching from
lifecycleScope
, you don't need to worry about specifying Dispatchers.Main since it is the default of the scope.如果您从
lifecycleScope
启动,则无需担心指定 Dispatchers.Main,因为它是 scope 的默认设置。
fun foo(/* The parameters you would have passed to AsyncTask constructor */) {
lifecycleScope.launch {
val backgroundResult = withContext(Dispatchers.Default) {
// The code you would have had in doInBackground.
// Last line of withContext lambda should evaluate to your result, what you would have
// returned in doInBackground.
}
// The code you would have had in onPostExecute. You can use the value of
// backgroundResult here.
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.