Saltar la navegación

4.4.2. Ficheros

Si la App que planteamos hace que el uso de SharedPreferences no sea una solución, siempre podríamos escribir y leer de un archivo de disco. Será algo más tedioso, pero tendremos mayor libertad.

Android usa un sistema de ficheros similar a los sistemas de ficheros de otras plataformas. Para trabajar con el sistema de ficheros de Android
usaremos la API java.io.*

Se usarán ficheros para guardar e intercambiar información a través de la red, o para trabajar con recursos como imágenes, audio y video.

Los sistemas Android siempre disponen de dos zonas de almacenamiento:

Almacenamiento interno

Es un almacenamiento no volátil, inherente al propio dispositivo y, por tanto, siempre disponible. Los ficheros guardados aquí sólo son accesibles por la aplicación que, se mantendrán hasta su desinstalación.

No es necesario ningún permiso para leer o escribir en la memoria interna si es en el directorio correspondiente a la aplicación.

El método getFilesDir() devuelve el directorio interno disponible para nuestra aplicación. Así, con la siguiente instrucción abriríamos un flujo de salida o de entrada según nos conveniese (FileOutputStream, FileInputStream).

File fich = new File(context.getFilesDir(), nombre_fichero);

También podríamos haber utilizado openFileOutput para obtener un FileOutputStream que escribe en un fichero del directorio interno de nuestra aplicación.

Si queremos usar ficheros temporales, nos podemos valer de getCacheDir() para obtener nuestro directorio interno temporal y de createTempFile para crear el fichero.

Almacenamiento externo

Se empezó a llamar así porque hacía referencia al almacenamiento en un medio extraíble (generalmente tarjeta SD).

Por este motivo, debemos entender que no siempre puede estar disponible (el usuario puede montar la tarjeta SD como almacenamiento USB para otro dispositivo...)

El acceso es practicamente global y todos pueden leer su contenido. Los permisos necesarios para su acceso son:

  • WRITE_EXTERNAL_STORAGE es el permiso que hay que solicitar en el AndroidManifest para poder escribir en la memoria externa.
  • READ_EXTERNAL_STORAGE es el permiso para leer de la memoria externa (aunque si se pide el permiso de escritura, implícitamente se tendrá permiso de lectura también).

Cuando el usuario desinstala, los ficheros almacenados aquí sólo se eliminan si están guardados en el directorio proporcionado por
getExternalFilesDir().

Como la memoria externa puede no estar disponible, siempre hay que comprabar su disponibilidad antes de hacer uso de ella mediante el método getExternalStorageState():

  • si devuelve MEDIA_MOUNTED, la podemos usar en escritura y lectura,
  • si devuelve MEDIA_MOUNTED_READ_ONLY sólo podemos leer.

getExternalStoragePublicDirectory nos devuelve el directorio en el que podemos guardar datos que permanecerán aunque la aplicación se desinstale. Estos datos serán accesibles para otras aplicaciones (públicos).

Al método anterior le pasamos una constante (p.ej: DIRECTORY_MUSIC) para categorizar los datos a guardar.

getExternalFilesDir nos devuelve un directorio externo en el que podemos almacenar datos “privados”, que se borrarán al desinstalar la aplicación.


El almacenamiento interno es la mejor opción cuando no queremos que otros usuarios o aplicaciones puedan acceder a los datos de nuestra aplicación.

Usaremos el almacenamiento externo para guardar datos que queremos compartir con otras aplicaciones o que queremos que permanezcan aunque la aplicación se desinstale (por ej, un archivo de música editado por nuestra aplicación...)

Por defecto las aplicaciones se instalan en el almacenamiento interno, pero este comportamiento se puede modificar con la propiedad android:installLocation en nuestro AndroidManifest.xml.

File dispone de los métodos getFreeSpace() y getTotalSpace() que nos indican respectivamente el espacio libre y el espacio total. Sin embargo, no tenemos garantizado poder escribir tantos bytes como nos devuelva getFreeSpace().

No estamos obligados a comprobar el espacio libre antes de escribir, podemos capturar una IOException si no podemos escribir por falta de espacio (a veces no sabemos el tamaño de los datos a escribir...)

Para eliminar ficheros, que ya no necesitemos, podemos utilizar el método delete() de File.

En el caso de tratarse de ficheros almacenados internamente, también podemos borrarlos llamando a: myContext.deleteFile(nombre_fichero)

Ejemplo

class MainActivity : AppCompatActivity() {
    private val fichero:String="configuracion.txt"
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        writeToFile("HOla Mundo")
        readFromFile()
    }

    private fun writeToFile(data: String) {
        try {
            val f = File(this.filesDir,fichero)
            val file = openFileOutput(fichero, Context.MODE_PRIVATE)
            Log.e("Espacio libre", f.totalSpace.toString())
            val outputStreamWriter = OutputStreamWriter(file)
            findViewById<TextView>(R.id.textoE1).text="Dir: "+this.filesDir.toString()
            findViewById<TextView>(R.id.textoE2).text="Escrito: $data - "+f.usableSpace.toString()
            outputStreamWriter.write(data)
            outputStreamWriter.close()
        }
        catch(e: IOException) {
            Log.e("Error", "File write failed: ", e)
        }
    }
    private fun readFromFile(): String? {
        val f = File(this.filesDir,fichero)
        var ret = ""
        try {
            val inputStream: InputStream? = openFileInput(fichero)
            if (inputStream != null) {
                val inputStreamReader = InputStreamReader(inputStream)
                val bufferedReader = BufferedReader(inputStreamReader)
                var receiveString: String? = ""
                val stringBuilder = StringBuilder()
                while (bufferedReader.readLine().also { receiveString = it } != null) {
                    stringBuilder.append("\n").append(receiveString)
                }
                inputStream.close()
                ret = stringBuilder.toString()
                findViewById<TextView>(R.id.textoL1).text="Leído : "+ret+" - "+f.usableSpace.toString()
                f.delete()
                findViewById<TextView>(R.id.textoL2).text="Borrado : "+f.usableSpace.toString()

            }
        }
        catch (e: FileNotFoundException) {
            Log.e("login activity", "File not found: ", e)
        }
        catch (e: IOException) {
            Log.e("login activity", "Can not read file: ", e)
        }
        return ret
    }
}

En la función de escritura, vemos cómo mediante el método openFileOutput abrimos el archivo para escritura. Luego utilizamos ese stream para crear otro que nos permita escribir de forma cómoda nuestros datos, el OutputStreamWriter. Basta llamar a write con nuestros datos y luego a close para dar por finalizada la sesión de escritura.

El archivo contendrá nuestros datos. Ahora, para leerlos, utilizaremos el método openInputStream para abrir el archivo en modo lectura. El stream resultante lo usaremos para crear un InputStreamReader, y luego un BufferedReader, que nos facilita mucho las operaciones de lectura.

Llamaremos al método readLine una y otra vez hasta que no haya más líneas en el archivo. Cerraremos el archivo y devolvemos los datos.

En este caso escribimos cadenas de caracteres, pero podríamos haber escrito cualquier otro dato. Vemos cómo Android nos facilita bastante cualquier tarea, solo es necesario conocer las clases adecuadas para cada necesidad.