Servicios en Kotlin
Uso de servicios en Kotlin
Servicios en Android (Servicios)
Descripción general de los servicios
Cómo crear un servicio en segundo plano
IntentService
This class was deprecated in API level 30.
IntentService is subject to all the background execution limits imposed with Android 8.0 (API level 26). Consider using WorkManager instead.
Servicios en Android:
Un servicio en segundo plano:
La diferencia entre IntentService y Service
Service versus IntentService
Introducción a los servicios (ejemplo: Un servicio en segundo plano)
Ejemplo: Crear un servicio para reproducir música
Código del Servicio Reproductor de Música
Ejemplo: Crear un servicio para descargar un fichero en segundo plano
Crear el proyecto ServicioDescarga
build.gradle:
buildFeatures { viewBinding = true }
Dependencias
dependencies { implementation("com.squareup.okhttp3:okhttp:4.12.0") }
permisos en el manifiesto:
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="32" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32"/>
activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Descarga de fichero" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintHorizontal_bias="0.498" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintVertical_bias="0.044" /> <Switch android:id="@+id/switch1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="IntentService" app:layout_constraintBottom_toTopOf="@+id/botonIniciar" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.919" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/textView" app:layout_constraintVertical_bias="1.0" /> <Button android:id="@+id/botonIniciar" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="36dp" android:text="Iniciar" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.528" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/textView" /> <Button android:id="@+id/botonParar" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="32dp" android:text="Parar" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.534" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/botonIniciar" app:layout_constraintVertical_bias="0.0" /> <TextView android:id="@+id/salida" android:layout_width="260dp" android:layout_height="126dp" android:layout_marginTop="44dp" android:text="resultado de la descarga" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.582" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/botonParar" app:layout_constraintVertical_bias="0.0" /> </androidx.constraintlayout.widget.ConstraintLayout>
Crear el paquete Util y añadir el objeto Memoria
object Memoria { public fun escribirExterna(cadena: String?): String { val tarjeta: File = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) val miFichero: File = File(tarjeta.absolutePath, "frases.html") lateinit var bw: BufferedWriter lateinit var mensaje: String try { tarjeta.mkdirs() bw = BufferedWriter(FileWriter(miFichero)) bw.write(cadena) Log.i("Información: ", miFichero.absolutePath) mensaje = "Fichero escrito OK\n" + miFichero.absolutePath Log.i("Info", mensaje) bw.close() } catch (e: IOException) { mensaje = e.message.toString() Log.e("Error de E/S", mensaje) } return mensaje } }
MainActivity.java
class MainActivity : AppCompatActivity(), View.OnClickListener { lateinit var binding: ActivityMainBinding lateinit var intentFilter: IntentFilter lateinit var broadcastReceiver: BroadcastReceiver companion object { private const val REQUEST_CONNECT = 1 const val WEB = "https://dam.org.es/ficheros/frases.html" const val ACTION_RESP = "RESPUESTA_DESCARGA" } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) binding = ActivityMainBinding.inflate(layoutInflater) val view: View = binding.root setContentView(view) binding.botonIniciar.setOnClickListener(this) binding.botonParar.setOnClickListener(this) intentFilter = IntentFilter(ACTION_RESP) intentFilter.addCategory(Intent.CATEGORY_DEFAULT) broadcastReceiver = ReceptorOperacion() // registerReceiver(broadcastReceiver, intentFilter); if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { comprobarPermiso() } } public override fun onResume() { super.onResume() //---registrar el receptor --- //registerReceiver(broadcastReceiver, intentFilter) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { registerReceiver(broadcastReceiver, intentFilter, RECEIVER_EXPORTED) } else registerReceiver(broadcastReceiver, intentFilter) } public override fun onPause() { super.onPause() //--- anular el registro del recpetor --- //unregisterReceiver(broadcastReceiver) unregisterReceiver(broadcastReceiver); } inner class ReceptorOperacion : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { val respuesta = intent.getStringExtra("resultado") mostrarMensaje(respuesta!!) binding.salida.text = respuesta // mostrarMensaje(respuesta); } } override fun onClick(v: View) { lateinit var i:Intent binding.salida.text = "" if (v === binding.botonIniciar) { //if (comprobarPermiso()) { if (!binding.switch1.isChecked) { // uso con Service //mostrarMensaje("Servicio inciado") i = Intent(this, DownloadService::class.java) startService(i) } else { // uso con IntentService i = Intent(this, DownloadIntentService::class.java) i.putExtra("web", WEB) //mostrarMensaje("Intent Service inciado") startService(i) } //} } if (v === binding.botonParar) { if (!binding.switch1.isChecked) { stopService(Intent(this@MainActivity, DownloadService::class.java)) } else { stopService(Intent(this@MainActivity, DownloadIntentService::class.java)) } } } private fun comprobarPermiso(): Boolean { //https://developer.android.com/training/permissions/requesting?hl=es-419 val permiso = android.Manifest.permission.WRITE_EXTERNAL_STORAGE var concedido = false // comprobar los permisos if (ActivityCompat.checkSelfPermission( this, permiso ) != PackageManager.PERMISSION_GRANTED ) { // pedir los permisos necesarios, porque no están concedidos if (ActivityCompat.shouldShowRequestPermissionRationale(this, permiso)) { concedido = false } else { ActivityCompat.requestPermissions(this, arrayOf(permiso), REQUEST_CONNECT) // Cuando se cierre el cuadro de diálogo se ejecutará onRequestPermissionsResult } } else { concedido = true } return concedido } override fun onRequestPermissionsResult( requestCode: Int, permissions: Array<String>, grantResults: IntArray ) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) val permiso = android.Manifest.permission.WRITE_EXTERNAL_STORAGE // chequeo los permisos de nuevo if (requestCode == REQUEST_CONNECT) if (ActivityCompat.checkSelfPermission(this, permiso) == PackageManager.PERMISSION_GRANTED) // permiso concedido startService(Intent(this@MainActivity, DownloadService::class.java) ) else // no hay permiso mostrarMensaje("No se ha concedido permiso para guardar ficheros en meoria externa") } private fun mostrarMensaje(mensaje: String) { Toast.makeText(this, mensaje, Toast.LENGTH_SHORT).show() } }
Error:
One of RECEIVER_EXPORTED or RECEIVER_NOT_EXPORTED should be specified when a receiver isn't being registered exclusively for system broadcasts
Solución:
How to manage what broadcasts Broadcast Receivers receive in Kotlin
registerReceiver (broadcastReceiver, intentFilter, RECEIVER_EXPORTED);
Crear el servicio en Android Studio:
y comprobar que se ha declarado el servicio en el Manifiesto:
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="32" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" /> <application android:allowBackup="true" android:dataExtractionRules="@xml/data_extraction_rules" android:fullBackupContent="@xml/backup_rules" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.ServicioDescarga" tools:targetApi="31"> <activity android:name=".MainActivity" android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <service android:name=".DownloadService"/> <service android:name=".DownloadIntentService"/> </application> </manifest>
DownloadService.kt
class DownloadService : Service() { override fun onCreate() { super.onCreate() } override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { lateinit var url: URL try { url = URL(MainActivity.WEB) descargaOkHTTP(url) } catch (e: MalformedURLException) { e.printStackTrace() } return super.onStartCommand(intent, flags, startId) } override fun onDestroy() { super.onDestroy() } override fun onBind(intent: Intent): IBinder { // TODO: Return the communication channel to the service. throw UnsupportedOperationException("Not yet implemented") } private fun descargaOkHTTP(web: URL) { val client = OkHttpClient() val request = Request.Builder() .url(web) .build() client.newCall(request).enqueue(object : Callback { override fun onFailure(call: Call, e: IOException) { Log.e("Error: ", e.message.toString()) } @Throws(IOException::class) override fun onResponse(call: Call, response: Response) { response.body.use { responseBody -> if (!response.isSuccessful) { //throw new IOException("Unexpected code " + response); Log.e("Error: ", "Unexpected code $response") } else { // Read data on the worker thread val responseData = response.body!!.string() // guardar el fichero descargado en memoria externa val mensaje = Memoria.escribirExterna(responseData) Log.i("Descarga: ", mensaje) } } } }) } }
DownloadIntentService.kt
class DownloadIntentService : IntentService("DownloadIntentService") { override fun onHandleIntent(intent: Intent?) { lateinit var url: URL Log.i("info", "inicio de intent service") if (intent != null) { val web = intent.extras?.getString("web") try { url = URL(web) Log.i("info", "inicio de okhttp") descargaOkHTTP(url) } catch (e: MalformedURLException) { e.printStackTrace() enviarRespuesta("Error en la URL: " + e.message) } } } private fun descargaOkHTTP(web: URL) { val client = OkHttpClient() val request = Request.Builder() .url(web) .build() client.newCall(request).enqueue(object : Callback { override fun onFailure(call: Call, e: IOException) { Log.e("Error: ", e.message.toString()) enviarRespuesta("Fallo: " + e.message) } @Throws(IOException::class) override fun onResponse(call: Call, response: Response) { response.body.use { responseBody -> if (!response.isSuccessful) { //throw new IOException("Unexpected code " + response); Log.e("Error: ", "Unexpected code $response") enviarRespuesta("Error: Unexpected code $response") } else { // Read data on the worker thread val responseData = response.body!!.string() enviarRespuesta("Descarga: fichero descargado de Internet OK") Log.i("info", responseData) // guardar el fichero descargado en memoria externa val mensaje = Memoria.escribirExterna(responseData) enviarRespuesta(mensaje) Log.i("Descarga: ", mensaje) } } } }) } private fun enviarRespuesta(mensaje: String) { val i = Intent() i.action = MainActivity.ACTION_RESP i.addCategory(Intent.CATEGORY_DEFAULT) i.putExtra("resultado", mensaje) sendBroadcast(i) } }
Más información:
Permisos y acceso al almacenamiento externo
getExternalStoragePublicDirectory
Environment.getExternalStorageDirectory() deprecated in API level 29 java
Deja una respuesta
Lo siento, debes estar conectado para publicar un comentario.