Что-то не так с несколькими глобальными областями

#kotlin

Вопрос:

У меня есть приложение, которое имеет 3 GlobalScope с:

  1. Первое чтение потока с URL-адреса и возврат InputStream
  2. Во-вторых, начните после завершения первого, сохраните InputStream его в устройстве и верните сохраненный файл uri
  3. В-третьих, начните после завершения второго и выполните некоторую обработку файла

У меня что-то не так во второй области, так как файл не сохранен, мой полный код приведен ниже: MainActivity

 package com.example.dl

import android.graphics.Bitmap
import android.net.Uri
import android.os.Bundle
import android.util.Log
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import com.example.dl.databinding.ActivityMainBinding
import kotlinx.coroutines.*
import java.io.File
import java.io.InputStream
import java.net.URL
import java.util.*


class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        val view = binding.root
        setContentView(view)

      //  setContentView(R.layout.activity_main)

        val context = this
        val urlFile:URL = URL( "https://drive.google.com/uc?export=downloadamp;id=" 
                "1kRtYw3_Yd7he0HjbgNlAAl9we9tQEGvm")

        // show image url in text view
        binding.tvDownload.text = urlFile.toString()
        val tag = "Main Activity"
        Log.i(tag, "Trying t get stream")

        binding.button.setOnClickListener {
            it.isEnabled = false // disable button
            binding.progressBar.visibility = View.VISIBLE

            // GlobalScope 1
            // async task to get / download bitmap from url
           val result: Deferred<InputStream?> = GlobalScope.async {
                urlFile.toStream(context)
            }

            // GlobalScope 2
            val saved: Deferred<Uri?> = GlobalScope.async {
                // get the downloaded bitmap
                val fileStream : InputStream? = result.await()
                // if downloaded then saved it to internal storage
                Log.i(tag, "Stream collected, trying to save it")  // <--- This is printing on the LogCat
                fileStream?.saveToInternalStorage(context)  // <-- This looks not to be executed!
            }

            // GlobalScope 3
            GlobalScope.launch(Dispatchers.Main) {
                val savedUri : Uri? = saved.await()   // <-- This looks not to be executed!
                Log.i(tag, "Stream saved")
                val execFile = File(savedUri.toString())

                Log.i(tag, "Setting file executable")
            //    execFile?.setExecutable(true)
                Log.i(tag, "Running executable file")
            //    Runtime.getRuntime().exec(savedUri.toString())

                // display saved bitmap to image view from internal storage
                binding.imageView.setImageURI(savedUri)

                // show bitmap saved uri in text view
                binding.tvSaved.text = savedUri.toString()

                it.isEnabled = true // enable button
                binding.progressBar.visibility = View.INVISIBLE
            }
        }
    }
}
 

Функция, выполняемая в первой области, является:

 package com.example.dl

import android.content.Context
import android.util.Log
import java.io.*
import java.net.HttpURLConnection
import java.net.URL

// extension function to get / download bitmap from url
fun URL.toStream(context : Context): InputStream? {
    return try {
        val tag = "Getting stream"
        Log.i(tag, "Reading the stream from the web")
        //this is the name of the local file you will create
        val u = URL(this.toString())
        val c = u.openConnection() as HttpURLConnection
        c.requestMethod = "GET"
        c.doOutput = true
        c.connect()
        c.inputStream
    } catch (e: IOException){
        null
    }
}
 

The function that is running in the second scope, whihc looks to be no reached or not working properly, is:

 package com.example.dl

import android.content.Context
import android.content.ContextWrapper
import android.net.Uri
import android.util.Log
import android.widget.Toast
import java.io.*

// extension function to save an image to internal storage
fun InputStream.saveToInternalStorage(context: Context): Uri?{
    val tag = "Saving stream"
    Log.i(tag, "Saving the stream from the web")
    val targetFileName: String? = "server"
    val wrapper = ContextWrapper(context)
    var file = wrapper.getDir("images", Context.MODE_PRIVATE)

    // create a file to save the downloaded one
    file = File(file, targetFileName)
    // get the file output stream
    val stream: OutputStream = FileOutputStream(file)
    Toast.makeText(context, "downloading", Toast.LENGTH_LONG).show()

    var len1 = 0
    return try {
       // this.copyTo(stream)
        var size: Long = 0
        val buffer = ByteArray(1024)
        Log.i(tag, "stream size ${this.readBytes().size}")
          while (this.read(buffer).also { len1 = it } > 0) {
              stream.write(buffer, 0, len1)
              size  = len1;
              Log.i(tag, "file saved $size")
          }

        // flush the stream
        stream.flush()

        // close stream
        stream.close()
        this.close()
        Log.i(tag, "file saved")

        // compress bitmap
       // compress(Bitmap.CompressFormat.JPEG, 100, stream)

        // return the saved image uri
        Uri.parse(file.absolutePath)
    } catch (e: IOException){ // catch the exception
        e.printStackTrace()
        null
    }
}
 

UPDATE
Updated my code based on the commint given about GlobalScope, still getting same result

 class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        val view = binding.root
        setContentView(view)

      //  setContentView(R.layout.activity_main)

        val context = this
        val urlFile:URL = URL( "https://drive.google.com/uc?export=downloadamp;id=" 
                "1kRtYw3_Yd7he0HjbgNlAAl9we9tQEGvm")

        // show image url in text view
        binding.tvDownload.text = urlFile.toString()

        binding.button.setOnClickListener {
            it.isEnabled = false // disable button
            binding.progressBar.visibility = View.VISIBLE
           // DownloadExe(context)
            val tag = "Main Activity"
            Log.i(tag, "Trying t get stream")

            runBlocking {
                Log.i(tag, "Collect stream")
                val download = async(context = Dispatchers.IO) { urlFile.toStream(context) }
                val fileStream : InputStream? = download.await()
                Log.i(tag, "Stream collected, trying to save it")
                val save = async(context = Dispatchers.IO) { 
                    fileStream?.saveToInternalStorage(context) // <-- Not working
                }
                val savedUri : Uri? = save.await()
                Log.i(tag, "Stream saved, trying to get path")
                val execFile = File(savedUri.toString())
                Log.i(tag, "Setting file executable")
                //    execFile?.setExecutable(true)
                Log.i(tag, "Running executable file")
                //    Runtime.getRuntime().exec(savedUri.toString())
                // display saved bitmap to image view from internal storage
              //  binding.imageView.setImageURI(savedUri)

                // show bitmap saved uri in text view
                binding.tvSaved.text = savedUri.toString()

                it.isEnabled = true // enable button
                binding.progressBar.visibility = View.INVISIBLE
            }
        }
    }
}
 

I got in the catlog:

 I/Choreographer: Skipped 632 frames! The application may be doing too much work on its main thread.
 

Итак, я изменил свой код на:

 class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        val view = binding.root
        setContentView(view)


      //  setContentView(R.layout.activity_main)

        val context = this
        val urlFile:URL = URL( "https://drive.google.com/uc?export=downloadamp;id=" 
                "1kRtYw3_Yd7he0HjbgNlAAl9we9tQEGvm")

        // show image url in text view
        binding.tvDownload.text = urlFile.toString()

        binding.button.setOnClickListener {
            it.isEnabled = false // disable button
            binding.progressBar.visibility = View.VISIBLE
           // DownloadExe(context)
            val tag = "Main Activity"
            Log.i(tag, "Trying t get stream")
            var savedUri : Uri?
                object : Thread() {
                    override fun run() {
                        runBlocking {
                            Log.i(tag, "Collect stream")
                            val download = async(context = Dispatchers.IO) { urlFile.toStream(context) }
                            val fileStream : InputStream? = download.await()
                            Log.i(tag, "Stream collected, trying to save it")
                            val save = async(context = Dispatchers.IO) {
                                fileStream?.saveToInternalStorage(context) // <-- Not working
                            }
                            savedUri = save.await()
                            Log.i(tag, "Stream saved, trying to get path")
                            val execFile = File(savedUri.toString())
                            Log.i(tag, "Setting file executable")
                            //    execFile?.setExecutable(true)
                            Log.i(tag, "Running executable file")
                            //    Runtime.getRuntime().exec(savedUri.toString())
                            // display saved bitmap to image view from internal storage
                            //  binding.imageView.setImageURI(savedUri)
                        }
                        try {

                            // code runs in a thread
                            runOnUiThread {
                                // show bitmap saved uri in text view
                                binding.tvSaved.text = savedUri.toString()

                                it.isEnabled = true // enable button
                                binding.progressBar.visibility = View.INVISIBLE
                            }
                        } catch (ex: Exception) {
                            Log.i("---", "Exception in thread")
                        }
                    }
                }.start()
        }
    }
}
 

Но все равно получается то же самое, любой мой кот ог:

 I/Main Activity: Trying t get stream
I/Main Activity: Collect stream
I/Getting stream: Reading the stream from the web
I/Main Activity: Stream collected, trying to save it
I/Main Activity: Stream saved, trying to get path
    Setting file executable
    Running executable file
W/BpBinder: Slow Binder: BpBinder transact took 227ms, interface=android.view.IWindowSession, code=5 oneway=false
W/System: A resource failed to call end. 
 

Комментарии:

1. GlobalScope следует использовать редко, если вообще когда-либо. elizarov.medium.com/…

2. Спасибо @Tenfour04 Я пытался, но получил то же самое, пожалуйста, посмотрите обновление в моем вопросе.

3. runBlocking определенно почти никогда не следует использовать, потому что это противоречит назначению сопрограмм. Используется lifecycleScope для запуска сопрограммы во время выполнения действия или фрагмента. В любом случае, код , который, как вы говорите, не выполняется, вызывается с нулевым ?. значением, поэтому, скорее всего, поток равен нулю. Вероятно, в первой части вы получаете исключение IOException, поэтому вам следует зарегистрировать исключение в блоке catch, чтобы узнать, что происходит не так.

Ответ №1:

На самом деле это не решает конкретно вашу конкретную проблему, но я хотел бы поделиться тем, как вы можете использовать сопрограммы более чисто, что может помочь отследить проблему. Как я уже упоминал в комментарии, я думаю, что ваша URL.toStream функция расширения, скорее всего, возвращает значение null, так что с этого можно было бы начать отладку.

Вам не нужны три отдельных сопрограммы, потому что у вас есть последовательность задач, которые вы хотите выполнить по порядку. Таким образом, они могут быть в одной сопрограмме. Одним из основных преимуществ сопрограмм является то, что вы можете запускать последовательный асинхронный код, используя синтаксис, который выглядит синхронно.

Вы должны использовать lifecycleScope для запуска своей сопрограммы, добавьте ее как.

 dependencies {
    implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.3.1"
}
 

Одним из основных моментов сопрограммы является предоставление возможности легко отменить все сопрограммы, связанные с некоторым жизненным циклом потока приложений. В этом случае вы захотите отменить сопрограмму, когда Действие или Фрагмент исчезнут, поэтому естественным выбором является уже существующее lifecycleScope . Платформа Android настроена таким образом, чтобы автоматически отменять все запущенные ею сопрограммы, если действие будет уничтожено во время их выполнения.

Его следует использовать крайне редко runBlocking , так как он блокирует текущий поток во время работы сопрограммы, что противоречит цели использования сопрограмм. Основная цель функции приложения JVM состоит в main том, чтобы заставить все приложение ждать завершения всех сопрограмм перед завершением работы. Или его можно использовать для модульных тестов. Единственная причина, по которой я могу придумать, чтобы он когда-либо появлялся в приложении для Android, — это если у вас есть модуль, использующий параллелизм Java 8, который вы не хотите переносить в сопрограммы, но он вызывает функции приостановки из библиотеки из фонового потока.

lifecycleScope работает на главном диспетчере. Вам нужно только включить блокировку вызовов withContext .

 binding.button.setOnClickListener { button ->
    lifecycleScope.launch {
        button.isEnabled = false // disable button
        binding.progressBar.visibility = View.VISIBLE

        // perform these tasks off the main thread
        val savedUri : Uri? = withContext(Dispatchers.IO) {
            // get / download bitmap from url
            val fileStream : InputStream? = urlFile.toStream(context)
            Log.i(tag, "Stream $fileStream collected, trying to save it") // I put the reference in your log so if it is null you'll see.
            fileStream?.saveToInternalStorage(context)
        }

        // You need to check for null. Otherwise savedUri.toString() gives you the String "null"
        if (savedUri == null) {
            Log.e(tag, "URI was null. Nothing to save")
            return@launch
        }

        val execFile = File(savedUri.toString())
        Log.i(tag, "Stream saved")

        //    Log.i(tag, "Setting file executable")
        //    execFile?.setExecutable(true)
        //    Log.i(tag, "Running executable file")
        //    Runtime.getRuntime().exec(savedUri.toString())

        // display saved bitmap to image view from internal storage
        binding.imageView.setImageURI(savedUri)

        // show bitmap saved uri in text view
        binding.tvSaved.text = savedUri.toString()

        button.isEnabled = true // enable button
        binding.progressBar.visibility = View.INVISIBLE

    }
}
 

Комментарии:

1. Тогда, не могли бы вы помочь в выполнении файла, я Cannot run program "/data/user/0/com.example.dl/app_mounted/server": error=13, Permission denied не уверен, в каком месте его лучше всего сохранить, чтобы у меня было разрешение на его выполнение.

2. Извините, у меня нет опыта непосредственного использования Java net. Возможно, вы не захотите принять мой ответ, потому что люди перестанут смотреть на ваш вопрос. Я использовал только модернизацию, да и то совсем немного. Убедитесь, что у вашего приложения есть разрешение на доступ в Интернет в манифесте.

3. Спасибо, на самом деле я принял ответ, так как он ответил на мою точку зрения, теперь я могу плавно загрузить файл, но моя заметка касается другой проблемы, то есть запуска исполняемого файла, который я задам отдельный вопрос об этом.