Android Shared Storage

İlk başlarda üretilen aygıtların depolama kapasitesi çok yavaş bir şekilde artarken bu seviyedeki teknoloji hızına erişmesi aklımızın ucundan bile geçmezdi fakat şuan telefonunuza yükleme yaparken çoğumuzun aklına yer var mı yok mu gibi bir düşünce dahi gelmiyor artık.

Bu yazıda da sizlere Android’in depolama yapısından bahsediyor olacağım.

Android üzerindeki depolama türleri

Bir Android telefondaki her bir uygulama internal ve external olmak üzere iki tip depolama türüne erişim hakkı vardır. Bu depoalama türlerini SharedPreferences gibi küçük verilerin saklanması için değil de daha çok büyük verilerin saklanması için kullanırız. Bunların anlatımına geçmeden önce storage ve cache arasındaki farkı iyi anlamamız gerekiyor:

Kalıcı olarak depolamak istediğimiz verileri storage dizinine; geçici olarak depolamak istediğimiz verileri de cache dizinine depolarız.

Internal Storage

Telefondaki çalışan her uygulama kendine ait bir internal storage’a sahiptir. Bu depolama sadece ve sadece uygulamanın kendisine aittir, yani sizin veya diğer uygulamaların bu dosyalara erişebilme olanağı yoktur. Birde sd kart gibi external depolama türünden olmadığından uygulama için her an erişebilir durumdadır. Ayrıca uygulama silindiğinde uygulamaya ait veriler de silinir, yani bi oyun yükleyip belirli bi aşama kaydettiniz diyelim, oyunu sildiğinizde katettiğiniz ilerleme de otomatik olarak silinecektir.

public void saveFileInternalStorage() {
   
        String FILENAME = "myFile";
        String inputToFile = "Hello From Internal Storage!";
   
        try {
            FileOutputStream fileOutputStream = openFileOutput(FILENAME, Context.MODE_PRIVATE);
            fileOutputStream.write(inputToFile.getBytes());
            fileOutputStream.close();
            Toast.makeText(getApplicationContext(),
                    "File " + FILENAME + " has been saved successfully",
                    Toast.LENGTH_SHORT).show();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            Toast.makeText(getApplicationContext(),
                    "File " + FILENAME + " has not been saved successfully due to an exception " + e.getLocalizedMessage(),
                    Toast.LENGTH_SHORT).show();
        } catch (IOException e) {
            e.printStackTrace();
            Toast.makeText(getApplicationContext(),
                    "File " + FILENAME + " has not been saved successfully due to an exception " + e.getLocalizedMessage(),
                    Toast.LENGTH_SHORT).show();
        }
 }

Görüldüğü üzere bu kodda myFile adlı dosyanın içerisine  “Hello From Internal Storage!” değerini sakladık.

openFileOutput(String fileName, int mode) : Dosya varsa verilen mod parametresine göre açar, dosya yoksa oluşturur. MODE _PRIVATE dosyayı sadece uygulamaya özel yapar; MODE_APPEND açılacak dosya mevcutsa veriyi dosyanın en sonuna ekler.

Bir uygulamanın bahsettiğimiz bu özel verilerini diğer uygulamalarla paylaşmasını sağlayan diğer iki parametre de MODE_WORLD_READABLE ve MODE_WORLD_WRITEABLE parametreleridir fakat bunlar API 17 ile kullanımdan kaldırılmıştır. Bu verileri paylaşıma açmak için bu dökümana bakabilirsiniz.

Dosya yazmak için kodu gösterdim, şimdi de bir dosyayı nasıl okuruz ona bakalım:

  1. Context çağrılır
  2. Dosya ismi girdi olarak verilir
  3. Dosya halihazırda var olmalıdır.
  4. FileInputStream kullanılarak dosya okunur.
// Calling:
/* 
    Context context = getApplicationContext();
    String filename = "log.txt";
    String str = read_file(context, filename);
*/  

public String read_file(Context context, String filename) {
    try {
        FileInputStream fis = context.openFileInput(filename);
        InputStreamReader isr = new InputStreamReader(fis, "UTF-8");
        BufferedReader bufferedReader = new BufferedReader(isr);
        StringBuilder sb = new StringBuilder();
        String line;
        while ((line = bufferedReader.readLine()) != null) {
            sb.append(line).append("\n");
        }
        return sb.toString();
    } catch (FileNotFoundException e) {
        return "";
    } catch (UnsupportedEncodingException e) {
        return "";
    } catch (IOException e) {
        return "";
    }
}

openFileInput(String fileName) : İçeriğini okumak istediğimiz dosyayı FileInputStream nesnesi olarak döndürür. Yine dosyayı okuduktan sonra akışı kapatmamız gerekir.

Dosyadaki verileri okurken bu fis objesinden yararlanırırz. Böylece append metoduyla string değerinin sonuna ekleye ekleye -1 değerini görene kadar okuruz.

Şimdi de internal cache dizinine nasıl dosya kaydederiz ondan bahsedeyim.

Geçici olarak dosya kaydetme işlemlerimizi de Internal Cache dizinine yapmalıyız. Yine her bir uygulama kendine özel cache dizinine sahiptir. Internal storage dizininde yeterli yer kalmadığı zaman sistem buradan verileri otomatik siler. Belki silmez deyip buraya olanca şeyi değil de en fazla 1 MB dosya kaydetminizi öneririm, eski cihazlar için de böylece sorun olmaz diye düşünüyorum. Internal olduğu için kullanıcı uygulamayı kaldırdığı zaman yine buradaki dosyalar da silinecektir.

getCacheDir() : Uygulamaya ait cache dizinini File nesnesi şeklinde döndürür.

createTempFile(String prefix, String suffix) : Cache dizinine saklamak istediğimiz veriler için geçici bir dosya oluşturur.

private File getTempFile(Context context, String url) {
    File file;
    try {
        String fileName = Uri.parse(url).getLastPathSegment();
        file = File.createTempFile(fileName, null, context.getCacheDir());
    } catch (IOException e) {
        // Error while creating file
    }
    return file;
}

External Storage

İsminden de anlayacağımız gibi herzaman erişemeyeceğimiz depolama birimidir. Bu bi SD kart (secondary external storage) olabileceği gibi telefonun kendi hafızası (primary external storage) da olabilir. Buraya depolanan herhangi bir dosya diğer tüm uygulamalar tarafından da görülebilir, uygulama kaldırıldığında buradan herhangi bir veri silinmez.

Bu dosyalama türü de public ve private diye ikiye ayrılır. İsimlerinden de anlaşılacağı üzere, private kısmı uygulamanın kendisine aittir; public kısmındaki verilere erişim ise diğer uygulamalara açıktır.

 getExternalFilesDir() : External storage dizinindeki private dosyaların kaydedildiği dizini getirir. Uygulama kaldırıldığında bu da silinir.

getExternalStoragePublicDirectory() : External storage dizinindeki public dosyaların kaydedildiği dizini getirir. Uygulama kaldırıldığında silinmez. Müzik, resim ve video dosyaları gibi dosyalar bu dizinde tutulur.

Buraya veri kaydedebilmek için ilk önce yeterli yer var mı kontrol etmek gerekir, ardından veri yazmak için izin (permission) sağlamak gerekir.

Yeterli yer kontrolü için kodumuza şu satırları çalıştırmanız gerekir:

//Check if you can read/write to external storage
public boolean isExternalStorageWritable() {
    String state = Environment.getExternalStorageState();
    if (Environment.MEDIA_MOUNTED.equals(state)) 
        return true;
    return false;
}

ve manifest dosyasına şu satırı eklemek gerekir:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

Bu arada external storage birimine yazmak gibi tehlikeli bir durum için uygulama kurulurken değil de uygulama çalışırken (run time permission) izin alınması özelliği de API 23 ile birlikte gelmiştir.

Aşağıdaki kod yardımıyla external storage birimine veri saklayabiliriz.

public void writeFileToExternalStorage() {
            String root = Environment.getExternalStorageDirectory().toString();
            File myDir = new File(root + "/saved_files");
            if (!myDir.exists()) {
                myDir.mkdirs();
            }
            try {
                File file = new File(myDir, "myfile.txt");
                FileOutputStream out = new FileOutputStream(file);
                out.write(inputToFile.getBytes());
                out.close();
                Toast.makeText(getApplicationContext(),
                        "File myfile.txt" + " has been saved successfully to external storage",
                        Toast.LENGTH_SHORT).show();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

Geçici olarak kaydetmek istediğimiz dosyaları yine external cache dizinine kaydedebiliriz. Bu sayede dosya boyutu büyüdükçe eski cihazlar için sorun olmaz ama sd kartın herhangi bir bilgisayara takılmasıyla buradaki dosyalara erişilebilir, yani bir güvenlik açığı oluşuyor. Internal cache dizininden dosya okuma ve dizine dosya yazması gibi bir farkı yok, sadece metod ismi değişiyor:

getExternalCacheDir() :  External cache storage dizinini File tipinde döndürür.

File cacheDir = getExternalCacheDir();
File cacheFile = new File(cacheDir, "externalCacheFile.txt");