简体   繁体   English

在 Kotlin 中使用协程替换 AsyncTask,构建谷歌地图 Kotlin android 应用程序,我无法实现协程而不是 AsyncTask

[英]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 之前和尝试协程之后的代码:

Before, Using AsyncTask之前,使用 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
        }

        }

My attempt at coroutines我对协程的尝试

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

    }

My Whole activity of what Im trying to build for reference我试图构建的整个活动以供参考

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()
//        }
//    }


}
  1. 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.您应该从提供的lifecycleScopeviewModelScope如果它在 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()中任何正在进行的任务,并且不像协程那样简单。

  2. 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:对于仅使用doInBackgroundonPostExecute的 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.请记住,使用launchasync启动协程会异步触发它。 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.

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