29-09-2020, Saat: 12:37
Android’de Dosya Depolama ve Paylaşma
Dosya kaydetme *
Android 2 çeşit depolamaya sahiptir: Dahili/varsayılan "Internal storage" ve harici depolama "External storage". İlk zamanlardan beri dahili depolama cihaz içinde ve harici de cihaz dışında olabildiği için, bugün de çok fazla bir fark yok. Sadece dahiliyi hariciden farkı kılan şey, izinler: Uygulamanız dahili için tüm izinlere sahip olup, buradaki dosyalar uygulamanıza aittir. Uygulamanızı kaldırırsanız, dahili depolamadaki dosyalarınız da birlikte silinecektir. Buna karşın harici depolama sizin izin vermenize ihtiyaç duyar ve dosyaları diğer uygulamalara da açıktır.
Dahili depolama
Dahili depolama için çok fazla söylecek bir şey yok: İstediğiniz gibi bu dosyalardan oluşturabilir, değiştirebilir ve silebilirsiniz. Kod ile dahili depolamaya ulaşmak için bu yolu kullanabilirsiniz:
TPath.GetDocumentsPath **
Harici depolama
Harici depolama kendi içinde 2 farklı tipe bölünebilir: Özel Private ve Genel Public harici depolama. Her ikisi de diğer uygulamalar için kullanıma açıktır; fakat kapsama alanı olarak özel dosyalar uygulamanıza kullandırılırken, genel dosyalar serbestçe paylaştırılır.
Harici depolamaya dosya kaydetmek için, şunları kullanabilirsiniz:
GetPublicPath metodu biraz kafa karıştırıcı olabilir, zira özel Private harici depolama için kullanılır. Fakat yolu bu şekilde. Buradaki “public” ifadesi gerçekten de harici depolama içinde olan dosyalara işaret etmekte, ama genele açık harici depolama içinde olanlara değil.
Harici depolama için (Rad Studio yerine Xamarin’i hedef almasına rağmen, aynı muhteviyatı kullandığı için) buradan oldukça iyi bilgiler alabilirsiniz: https://docs.microsoft.com/en-us/xamarin/android/platform/files/external-storage
Ayrıca, dosyaları kaydebileceğiniz klasörler hakkında daha fazla bilgi için https://developer.android.com/training/data-storage/files inceleyebilirsiniz.
Harici depolama için izin alma
Harici depolamadan okuma veya yazma için, ilk olarak Android’den izin almalısınız. Eski Android sürümlerinde, manifest’e izinleri eklemek yeterliydi. Fakat Android 6 ve üstü için, kullanacağınız zamanlarda gerekli izinleri canlı olarak istemeniz gerekiyor.
Buradan ihtiyaç duyulabilecek izinleri bulabilirsiniz:
http://docwiki.embarcadero.com/RADStudio/Rio/en/Android_Permission_Model
Buradan da android izinleri hakkında daha fazla bilgi alabilirsiniz (yine Xamarin hedef alınmış olmasına rağmen kullanışlı): https://docs.microsoft.com/en-us/xamarin/android/app-fundamentals/permissions
Dosyaların paylaşılması
İki API hikayesi
Çok, çok uzun zamanlar önce Android’de dosya paylaşmanın yolu, dosyayı harici depolamaya kaydetmek ve sonra da dosyayı paylaşacak bir intent çağırmak idi. Bunun da pek çok sıkıntıları vardı, biri uygulama hem dosya paylaşırken ve bir yandan da uygulamayı zorla okuma/yazma harici depolama iznine sahip olmaya zorlamasıydı.
Bunu düzeltmek için, Android 4 (Ice Cream Sandwich) ve sonraki sürümlerden itibaren kullanılacak olan FileProvider sınıfını piyasaya sürdü.
Fakat FileProvider API’ye geçmek için fazla bir sebep yoktu, zira eski API sıkıntılarına rağmen hala çalışmaya devam etmekteydi.
Bu eski dosya paylaşma yolunu ortadan kaldıran Android 7 (Nougat) ile değişti: https://developer.android.com/about/versions/nougat/android-7.0-changes#perm
Şu anda da eski yolları değiştirmeye lüzum yok: Hala daha eski sürüm Android sürümlerini hedefliyorsanız, Android 7’de sıkıntısızca çalıştırabilirsiniz. Sadece yeni tutum doğrudan Android 7’yi hedefliyorsanız gerçekleşiyor.
NOT
Android sürümleri biraz karışık olabilir ve devam etmeden önce biraz açıklama yapmak faydalı olabilir.
Android’de hedef sürüm uygulamanızın kullanacağı minimum Android sürümü değildir. minSdkVersion tarafından verilen de minimum sürümdür. Halbuki hedef sürüm, uygulamanızın optimize edildiği**** sürümdür. Minimum sürüm olarak Android 4 sürümüne sahipken, aynı zamanda Android 7’yi hedef alabilirsiniz. Fakat Android 7’yi hedef alırsanız, Android 7 kuralları geçerli olacak ve dosya paylaşımının eski yolununu kullanan kodunuz da çalışmayacaktır. Eğer hedef olarak Android 4 alırsanız (ve minimum sürüm de Android 4 olursa), o zaman Android 7’de çalıştırsanız bile, Android 4 kuralları geçerli olur.
Bütün bunlar şu demek oluyor ki, Android 4’ü hedef alırsanız ve minimum SDK Android 4 ise, yeni Android 7 kurallarını dert etmek zorunda değilsiniz ve uygulamanız da Android 7’de çalışmaya devam eder. Eğer Android 7’yi hedef alırsanız, minimum desteklenen sürümünüz 4 olmasına rağmen, dosya paylaşımı için yeni yolu kullanmak zorundasınız.
Buradan Android sürümleri hakkında daha fazla bilgi alabilirsiniz: https://developer.android.com/guide/topics/manifest/uses-sdk-element?utm_campaign=adp_series_sdkversion_010616&utm_source=medium&utm_medium=blog#ApiLevels
Hikayemize geri dönecek olursak: Ağustos 2018’de tüm bunlar tekrar değişti. Google Play store’da yayına izin vermek için hedef sürüm olarak 8.0 veya üstünü istemeye başladı. Hedef olarak 8.0 seçtiğinizde (minimum sürüm daha düşük olsa bile), eski usul dosya paylaşımı Nougat ve üstü cihazlarda çalışmamaktadır. Eski hileler de artık işe yaramamaktadır ve bu da yeni sisteme geçmenin zamanı gelmiş demektir.
Dosya paylaşımı için FileProvider kullanma
Yeni usulde dosya paylaşma için FileProvider tanımlamanız gerekmektedir.
Muhteviyatı hakkında ayrıntılı bilgi almak için: https://developer.android.com/training/secure-file-sharing/
Bu bölümün kalanında da oluşturulan basit bir dosyanın paylaşımını inceleyeceğiz. Örnek için bkz: LangWars
1. adım - Fileprovider tanımlama
Bunu gerçekleştirmek için ihtiyacımız olan ilk şey AndroidManifest.template.xml dosyasının değiştirilmesi ve bir provider eklemektir.
Projenizdeki AndroidManifest.template.xml dosyasını açın ve <application> etiketinin altına <provider> ifadesini ilave edin:
<application ...>
...
<provider android:name="android.support.v4.content.FileProvider"
android:grantUriPermissions="true"
android:exported="false"
android:authorities="%package%.fileprovider">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_provider_paths" />
</provider>
</application>
ÖNEMLİ
Android manifest dosyasını elle değiştirdiğimiz için, Delphi’yi her seferinde güncellemeyi burada anlatıldığı şekilde güncellemeyi unutmuyoruz:
http://docwiki.embarcadero.com/RADStudio/Sydney/en/Preparing_an_Android_Application_for_Deployment#Re-creating_the_AndroidManifest.template.xml_File
2. adım - FileProvider'ın kullanacağı yolların tanımlanması
İlk adımda bir kaynak tanımlıyoruz:
Resource = "@xml/file_provider_paths"
Şimdi, provider’ın kullanacağı yolların hangileri olduğunu Android’e söyleyecek, bu kaynağı oluşturmamız gerekiyor. Bunun için, file_provider_paths.xml isimli bir dosya oluşturmamız gerekiyor. Bu örnekte bir "res" klasörüne koyacağız, fakat siz istediğiniz yere koyabilirsiniz.
Örneğimizde dahili hafızanın ana data klasörüne depolayacağız, ki onu da provider’daki yollara ekleyebilelim. file_provider_paths.xml dosyası:
<?xml version="1.0" encoding="utf-8" ?>
<paths>
<files-path name="files" path="." />
</paths>
Eğer bir dosyayı harici depolama içindeki veya farklı bir yoldaki bir dosyayı paylaşacaksanız, burayı ona göre değiştirmeniz gerekiyor. Bu dosyaya yazabilecekleriniz hakkında daha fazla için bakınız https://developer.android.com/reference/android/support/v4/content/FileProvider#SpecifyFiles
3. adım - Yolları gösteren dosyanın kaynak olarak yüklenmesi
Şimdi 2. adımda oluşturduğumuz dosyayı bir Android kaynağı olarak uygulamamıza yüklememiz gerekiyor.
NOT
Delphi kaynakları (resources) Android kaynakları ile aynı değildir. Delphi kaynakları bir tür çoklu-ortam tarzında tanımlanmış olup, TResourceStream ile ulaşılmaktadır. Fakat burada farklı olarak bir Android kaynağı tanımlamamız gerekiyor. Android kaynağı oluşturmak için, dosyayı uygulamanızın varsayılan klasörünün altında bir res klasörüne yüklemeniz gerekiyor.
Bunun için, Delphi’de Menu->Project->Deployment gidin ve file_provider_paths.xml dosyasının "res\xml" klasörüne yüklenecek olduğuna emin olun. platform combobox‘ında da "Android" seçili olmalı.
NOT
Delphi 10.3 Rio, Rad Studio Project Options->Application->Entitlement List kısmında "Secure File Sharing" seçeneğini içermektedir:
Bu checkbox’ı işaretlerseniz, Delphi Android manifest içinde hem bir provider üretecek hem de provider tarafından tanımlanmış yolları içeren provider_paths.xml adlı bir dosya oluşturacaktır. Bkz: http://docwiki.embarcadero.com/RADStudio/Rio/en/Entitlement_List#Entitlement_List_for_Android
Bu AndroidManifestTemplate.xml dosyasını elle değiştirmekten kurtardığı için ve 1’den 3’e kadar olan adımları yaptığı için neredeyse mükemmeldir. Fakat burada bir pürüz bulunmakta: Oluşturulan provider_paths.xml dosyası değiştiremediğiniz bir ifadeye sahip olmaktadır:
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="external_files" path="." />
</paths>
Gördüğünüz gibi, harici depolama için izin istemeyi gerektiren harici bir yol tanımlamaktadır, ki en baştan beri bundan kaçınmaya çalışmaktayız. O halde uygulamanız zaten dosya paylaşma için harici depolamayı kullanmıyorsa, bu çok uygun seçeneği kullanamıyorsunuz. Eğer paylaşmak istediğiniz dosyalarınız muhtelif sebeplerle zaten harici depolamada ise, ancak o zaman bu checkbox’ı işaretlemeli ve manifest’i elle değiştirmeyi unutmalısınız.
4. adım - Kod yazma
Provider doğru şekilde yayınlandığında, son adım dosya paylaşma kodunun yazılmasıdır.
(TMS FlexCel komponentine sahip olanlar için yalnızca TFlexCelDocExport bileşenini forma bırakmak ve şu kodu eklemek yeterli: FlexCelDocExport.ExportFile(btnShare, PdfPath); )
NOT
Aşağıdaki gibi NullPointerExeption hata alıyorsanız:
Project Reports.apk raised exception class EJNIException with message 'java.lang.NullPointerException: Attempt to invoke virtual method 'android.content.res.XmlResourceParser android.content.pm.ProviderInfo.loadXmlMetaData(android.content.pm.PackageManager, java.lang.String)' on a null object reference'.
Uygulamada provider doğru şekilde tanımlanmamış demektir.
Veya benzeri olarak şu hata çıkıyorsa:
Project Reports.apk raised exception class EJNIException with message 'java.lang.IllegalArgumentException: Failed to find configured root that contains /data/data/com.tms.Reports/files/delphiversions.pdf'.
problem file_provider_paths.xml içinde listelenmiş dosyalardadır.
Çevirenin notu:
* TMS Software FlexCel Android Guide https://doc.tmssoftware.com/flexcel/vcl/guides/android-guide.html sayfasından tercüme edilmiştir. Bu yazı Delphi 10.3 Rio sürümü için hazırlanmıştır.
** Buradaki dahili depolama dosyalarını Android sandbox koruması sebebi ile (root yapmadıkça) dosya yöneticisi ve PC bağlantısında göremezsiniz.
*** Özel harici depolama dosyalarını dosya yöneticisi ve diğer uygulamalar da görebilir.
**** Delphi’de derlemenin yapıldığı sırada fiziki olarak takılı cihaza ait android sürümü
Nitekim artık Mobil Uygulamadan Mp3 dosya paylaşı https://www.delphican.com/showthread.php?tid=821 konusunda anlatılan yol artık “android.os.FileUriExposedException: file:///ornek.txt exposed beyond app through ClipData.ltem.getUri0” hatası verip çalışmıyor. Sebebi yeni kuralların file provider yerine artık content provider API kullanılması istenmesi.
Fesih bey de bu konuda yol göstermiş: AndroidManifest Dosyasi Karakter Sorunu https://www.delphican.com/showthread.php?tid=2272
[DCC Warning]: W1000 Symbol 'SharedActivity' is deprecated: 'Use TAndroidHelper.Activity' ikazı da çıkıyor.
To share files between applications, you should send a content:// URI and grant a temporary access permission on the URI. The easiest way to grant this permission is by using the FileProvider class. For more information on permissions and sharing files, see Sharing Files.
Android sürüm farkları: https://socialcompare.com/en/comparison/android-versions-comparison
Örnek proje:
AndroidDosyaDepolamaPaylasma.dproj
Android'de özellikle Sqlite veritabanı gibi önemli dosyaları, harici klasöre ve eposta vs. ile yedekleklemek isteyenler için faydalı olacak şekilde düzenlenmiştir.
Dosya kaydetme *
Android 2 çeşit depolamaya sahiptir: Dahili/varsayılan "Internal storage" ve harici depolama "External storage". İlk zamanlardan beri dahili depolama cihaz içinde ve harici de cihaz dışında olabildiği için, bugün de çok fazla bir fark yok. Sadece dahiliyi hariciden farkı kılan şey, izinler: Uygulamanız dahili için tüm izinlere sahip olup, buradaki dosyalar uygulamanıza aittir. Uygulamanızı kaldırırsanız, dahili depolamadaki dosyalarınız da birlikte silinecektir. Buna karşın harici depolama sizin izin vermenize ihtiyaç duyar ve dosyaları diğer uygulamalara da açıktır.
Dahili depolama
Dahili depolama için çok fazla söylecek bir şey yok: İstediğiniz gibi bu dosyalardan oluşturabilir, değiştirebilir ve silebilirsiniz. Kod ile dahili depolamaya ulaşmak için bu yolu kullanabilirsiniz:
TPath.GetDocumentsPath **
Harici depolama
Harici depolama kendi içinde 2 farklı tipe bölünebilir: Özel Private ve Genel Public harici depolama. Her ikisi de diğer uygulamalar için kullanıma açıktır; fakat kapsama alanı olarak özel dosyalar uygulamanıza kullandırılırken, genel dosyalar serbestçe paylaştırılır.
Harici depolamaya dosya kaydetmek için, şunları kullanabilirsiniz:
- Özel harici depolama: TPath.GetPublicPath ***
- Genel harici depolama için: TPath.GetSharedDocumentsPath
GetPublicPath metodu biraz kafa karıştırıcı olabilir, zira özel Private harici depolama için kullanılır. Fakat yolu bu şekilde. Buradaki “public” ifadesi gerçekten de harici depolama içinde olan dosyalara işaret etmekte, ama genele açık harici depolama içinde olanlara değil.
Harici depolama için (Rad Studio yerine Xamarin’i hedef almasına rağmen, aynı muhteviyatı kullandığı için) buradan oldukça iyi bilgiler alabilirsiniz: https://docs.microsoft.com/en-us/xamarin/android/platform/files/external-storage
Ayrıca, dosyaları kaydebileceğiniz klasörler hakkında daha fazla bilgi için https://developer.android.com/training/data-storage/files inceleyebilirsiniz.
Harici depolama için izin alma
Harici depolamadan okuma veya yazma için, ilk olarak Android’den izin almalısınız. Eski Android sürümlerinde, manifest’e izinleri eklemek yeterliydi. Fakat Android 6 ve üstü için, kullanacağınız zamanlarda gerekli izinleri canlı olarak istemeniz gerekiyor.
Buradan ihtiyaç duyulabilecek izinleri bulabilirsiniz:
http://docwiki.embarcadero.com/RADStudio/Rio/en/Android_Permission_Model
Buradan da android izinleri hakkında daha fazla bilgi alabilirsiniz (yine Xamarin hedef alınmış olmasına rağmen kullanışlı): https://docs.microsoft.com/en-us/xamarin/android/app-fundamentals/permissions
Dosyaların paylaşılması
İki API hikayesi
Çok, çok uzun zamanlar önce Android’de dosya paylaşmanın yolu, dosyayı harici depolamaya kaydetmek ve sonra da dosyayı paylaşacak bir intent çağırmak idi. Bunun da pek çok sıkıntıları vardı, biri uygulama hem dosya paylaşırken ve bir yandan da uygulamayı zorla okuma/yazma harici depolama iznine sahip olmaya zorlamasıydı.
Bunu düzeltmek için, Android 4 (Ice Cream Sandwich) ve sonraki sürümlerden itibaren kullanılacak olan FileProvider sınıfını piyasaya sürdü.
Fakat FileProvider API’ye geçmek için fazla bir sebep yoktu, zira eski API sıkıntılarına rağmen hala çalışmaya devam etmekteydi.
Bu eski dosya paylaşma yolunu ortadan kaldıran Android 7 (Nougat) ile değişti: https://developer.android.com/about/versions/nougat/android-7.0-changes#perm
Şu anda da eski yolları değiştirmeye lüzum yok: Hala daha eski sürüm Android sürümlerini hedefliyorsanız, Android 7’de sıkıntısızca çalıştırabilirsiniz. Sadece yeni tutum doğrudan Android 7’yi hedefliyorsanız gerçekleşiyor.
NOT
Android sürümleri biraz karışık olabilir ve devam etmeden önce biraz açıklama yapmak faydalı olabilir.
Android’de hedef sürüm uygulamanızın kullanacağı minimum Android sürümü değildir. minSdkVersion tarafından verilen de minimum sürümdür. Halbuki hedef sürüm, uygulamanızın optimize edildiği**** sürümdür. Minimum sürüm olarak Android 4 sürümüne sahipken, aynı zamanda Android 7’yi hedef alabilirsiniz. Fakat Android 7’yi hedef alırsanız, Android 7 kuralları geçerli olacak ve dosya paylaşımının eski yolununu kullanan kodunuz da çalışmayacaktır. Eğer hedef olarak Android 4 alırsanız (ve minimum sürüm de Android 4 olursa), o zaman Android 7’de çalıştırsanız bile, Android 4 kuralları geçerli olur.
Bütün bunlar şu demek oluyor ki, Android 4’ü hedef alırsanız ve minimum SDK Android 4 ise, yeni Android 7 kurallarını dert etmek zorunda değilsiniz ve uygulamanız da Android 7’de çalışmaya devam eder. Eğer Android 7’yi hedef alırsanız, minimum desteklenen sürümünüz 4 olmasına rağmen, dosya paylaşımı için yeni yolu kullanmak zorundasınız.
Buradan Android sürümleri hakkında daha fazla bilgi alabilirsiniz: https://developer.android.com/guide/topics/manifest/uses-sdk-element?utm_campaign=adp_series_sdkversion_010616&utm_source=medium&utm_medium=blog#ApiLevels
Hikayemize geri dönecek olursak: Ağustos 2018’de tüm bunlar tekrar değişti. Google Play store’da yayına izin vermek için hedef sürüm olarak 8.0 veya üstünü istemeye başladı. Hedef olarak 8.0 seçtiğinizde (minimum sürüm daha düşük olsa bile), eski usul dosya paylaşımı Nougat ve üstü cihazlarda çalışmamaktadır. Eski hileler de artık işe yaramamaktadır ve bu da yeni sisteme geçmenin zamanı gelmiş demektir.
Dosya paylaşımı için FileProvider kullanma
Yeni usulde dosya paylaşma için FileProvider tanımlamanız gerekmektedir.
Muhteviyatı hakkında ayrıntılı bilgi almak için: https://developer.android.com/training/secure-file-sharing/
Bu bölümün kalanında da oluşturulan basit bir dosyanın paylaşımını inceleyeceğiz. Örnek için bkz: LangWars
1. adım - Fileprovider tanımlama
Bunu gerçekleştirmek için ihtiyacımız olan ilk şey AndroidManifest.template.xml dosyasının değiştirilmesi ve bir provider eklemektir.
Projenizdeki AndroidManifest.template.xml dosyasını açın ve <application> etiketinin altına <provider> ifadesini ilave edin:
<application ...>
...
<provider android:name="android.support.v4.content.FileProvider"
android:grantUriPermissions="true"
android:exported="false"
android:authorities="%package%.fileprovider">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_provider_paths" />
</provider>
</application>
ÖNEMLİ
Android manifest dosyasını elle değiştirdiğimiz için, Delphi’yi her seferinde güncellemeyi burada anlatıldığı şekilde güncellemeyi unutmuyoruz:
http://docwiki.embarcadero.com/RADStudio/Sydney/en/Preparing_an_Android_Application_for_Deployment#Re-creating_the_AndroidManifest.template.xml_File
2. adım - FileProvider'ın kullanacağı yolların tanımlanması
İlk adımda bir kaynak tanımlıyoruz:
Resource = "@xml/file_provider_paths"
Şimdi, provider’ın kullanacağı yolların hangileri olduğunu Android’e söyleyecek, bu kaynağı oluşturmamız gerekiyor. Bunun için, file_provider_paths.xml isimli bir dosya oluşturmamız gerekiyor. Bu örnekte bir "res" klasörüne koyacağız, fakat siz istediğiniz yere koyabilirsiniz.
Örneğimizde dahili hafızanın ana data klasörüne depolayacağız, ki onu da provider’daki yollara ekleyebilelim. file_provider_paths.xml dosyası:
<?xml version="1.0" encoding="utf-8" ?>
<paths>
<files-path name="files" path="." />
</paths>
Eğer bir dosyayı harici depolama içindeki veya farklı bir yoldaki bir dosyayı paylaşacaksanız, burayı ona göre değiştirmeniz gerekiyor. Bu dosyaya yazabilecekleriniz hakkında daha fazla için bakınız https://developer.android.com/reference/android/support/v4/content/FileProvider#SpecifyFiles
3. adım - Yolları gösteren dosyanın kaynak olarak yüklenmesi
Şimdi 2. adımda oluşturduğumuz dosyayı bir Android kaynağı olarak uygulamamıza yüklememiz gerekiyor.
NOT
Delphi kaynakları (resources) Android kaynakları ile aynı değildir. Delphi kaynakları bir tür çoklu-ortam tarzında tanımlanmış olup, TResourceStream ile ulaşılmaktadır. Fakat burada farklı olarak bir Android kaynağı tanımlamamız gerekiyor. Android kaynağı oluşturmak için, dosyayı uygulamanızın varsayılan klasörünün altında bir res klasörüne yüklemeniz gerekiyor.
Bunun için, Delphi’de Menu->Project->Deployment gidin ve file_provider_paths.xml dosyasının "res\xml" klasörüne yüklenecek olduğuna emin olun. platform combobox‘ında da "Android" seçili olmalı.
NOT
Delphi 10.3 Rio, Rad Studio Project Options->Application->Entitlement List kısmında "Secure File Sharing" seçeneğini içermektedir:
Bu checkbox’ı işaretlerseniz, Delphi Android manifest içinde hem bir provider üretecek hem de provider tarafından tanımlanmış yolları içeren provider_paths.xml adlı bir dosya oluşturacaktır. Bkz: http://docwiki.embarcadero.com/RADStudio/Rio/en/Entitlement_List#Entitlement_List_for_Android
Bu AndroidManifestTemplate.xml dosyasını elle değiştirmekten kurtardığı için ve 1’den 3’e kadar olan adımları yaptığı için neredeyse mükemmeldir. Fakat burada bir pürüz bulunmakta: Oluşturulan provider_paths.xml dosyası değiştiremediğiniz bir ifadeye sahip olmaktadır:
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="external_files" path="." />
</paths>
Gördüğünüz gibi, harici depolama için izin istemeyi gerektiren harici bir yol tanımlamaktadır, ki en baştan beri bundan kaçınmaya çalışmaktayız. O halde uygulamanız zaten dosya paylaşma için harici depolamayı kullanmıyorsa, bu çok uygun seçeneği kullanamıyorsunuz. Eğer paylaşmak istediğiniz dosyalarınız muhtelif sebeplerle zaten harici depolamada ise, ancak o zaman bu checkbox’ı işaretlemeli ve manifest’i elle değiştirmeyi unutmalısınız.
4. adım - Kod yazma
Provider doğru şekilde yayınlandığında, son adım dosya paylaşma kodunun yazılmasıdır.
(TMS FlexCel komponentine sahip olanlar için yalnızca TFlexCelDocExport bileşenini forma bırakmak ve şu kodu eklemek yeterli: FlexCelDocExport.ExportFile(btnShare, PdfPath); )
NOT
Aşağıdaki gibi NullPointerExeption hata alıyorsanız:
Project Reports.apk raised exception class EJNIException with message 'java.lang.NullPointerException: Attempt to invoke virtual method 'android.content.res.XmlResourceParser android.content.pm.ProviderInfo.loadXmlMetaData(android.content.pm.PackageManager, java.lang.String)' on a null object reference'.
Uygulamada provider doğru şekilde tanımlanmamış demektir.
Veya benzeri olarak şu hata çıkıyorsa:
Project Reports.apk raised exception class EJNIException with message 'java.lang.IllegalArgumentException: Failed to find configured root that contains /data/data/com.tms.Reports/files/delphiversions.pdf'.
problem file_provider_paths.xml içinde listelenmiş dosyalardadır.
Çevirenin notu:
* TMS Software FlexCel Android Guide https://doc.tmssoftware.com/flexcel/vcl/guides/android-guide.html sayfasından tercüme edilmiştir. Bu yazı Delphi 10.3 Rio sürümü için hazırlanmıştır.
** Buradaki dahili depolama dosyalarını Android sandbox koruması sebebi ile (root yapmadıkça) dosya yöneticisi ve PC bağlantısında göremezsiniz.
*** Özel harici depolama dosyalarını dosya yöneticisi ve diğer uygulamalar da görebilir.
**** Delphi’de derlemenin yapıldığı sırada fiziki olarak takılı cihaza ait android sürümü
Nitekim artık Mobil Uygulamadan Mp3 dosya paylaşı https://www.delphican.com/showthread.php?tid=821 konusunda anlatılan yol artık “android.os.FileUriExposedException: file:///ornek.txt exposed beyond app through ClipData.ltem.getUri0” hatası verip çalışmıyor. Sebebi yeni kuralların file provider yerine artık content provider API kullanılması istenmesi.
Fesih bey de bu konuda yol göstermiş: AndroidManifest Dosyasi Karakter Sorunu https://www.delphican.com/showthread.php?tid=2272
[DCC Warning]: W1000 Symbol 'SharedActivity' is deprecated: 'Use TAndroidHelper.Activity' ikazı da çıkıyor.
Google’ın Android 7.0 Nougat API 25 Uygulamalar Arasında Dosya Paylaşma hakkındaki duyurusu:
For apps targeting Android 7.0, the Android framework enforces the StrictMode API policy that prohibits exposing file:// URIs outside your app. If an intent containing a file URI leaves your app, the app fails with a FileUriExposedException exception.To share files between applications, you should send a content:// URI and grant a temporary access permission on the URI. The easiest way to grant this permission is by using the FileProvider class. For more information on permissions and sharing files, see Sharing Files.
(URI, İngilizce “Uniform Resourse Identifier” kelimelerini belirtmektedir. Türkçe anlamı ise “tekdüzen kaynak tanımlayıcısı” dır. URI, URL’in işaret ettiği kaynak konumundan sonra gelen ilgili kaynağın ayırıcı adresini belirtir yani web üzerinde belli bir kaynağa (internet sitesi, belge, resim vb.) ulaşmak için kullanılan 1994 te Tim Berners-Lee’ın oluşturmuş olduğu belli bir formata sahip karakter dizisi,metindir. Bu format UNIX dizin yapısı formatındadır: URI = scheme:[//authority]path[?query][#fragment] URI’ın en bilinen şekli Uniform Resource Locator (URL)’dir.)
Bu arada Deployment ile dosya yüklemede sıkıntı yok. Dahili klasörler koruma altında olduğu (ve root yapılmadığı) için Android dosya yöneticisi göstermiyor. FileExists ile yüklenen dosyalar bulunabiliyor
Dahili depolama klasörleri:
TPath.GetDocumentsPath: /data/user/0/com.embarcadero.Uygulamam/files
TPath.GetHomePath: /data/user/0/com.embarcadero.Uygulamam/files
TPath.GetTempPath: /storage/emulated/0/Android/data/com.embarcadero.Uygulamam/files/tmp
TPath.GetRingtonesPath: TPath./storage/emulated/0/Android/data/com.embarcadero.Uygulamam/files/Ringtones
TPath.GetDownloadsPath: /storage/emulated/0/Android/data/com.embarcadero.Uygulamam/files/Download
TPath.GetCachePath: /data/user/0/com.embarcadero.Uygulamam/cache
TPath.GetPicturesPath: /storage/emulated/0/Android/data/com.embarcadero.Uygulamam/files/Pictures
TPath.GetCameraPath: /storage/emulated/0/Android/data/com.embarcadero.Uygulamam/files/DCIM
TPath.GetMusicPath: /storage/emulated/0/Android/data/com.embarcadero.Uygulamam/files/Music
TPath.GetMoviesPath: /storage/emulated/0/Android/data/com.embarcadero.Uygulamam/files/Movies
TPath.GetAlarmsPath: /storage/emulated/0/Android/data/com.embarcadero.Uygulamam/files/Alarms
TPath.GetLibraryPath: /data/app/com.embarcadero.Uygulamam-gjDvLqqZBuqhRw43hNq8BA==/lib/arm
Harici özel depolama klasörü:
TPath.GetPublicPath: /storage/emulated/0/Android/data/com.embarcadero.Uygulamam/files
Harici depolama klasörleri:
TPath.GetSharedDocumentsPath: /storage/emulated/0/Documents
TPath.GetSharedDownloadsPath : /storage/emulated/0/Download
TPath.GetSharedPicturesPath : /storage/emulated/0/Pictures
TPath.GetSharedCameraPath: /storage/emulated/0/DCIM
TPath.GetSharedMusicPath: /storage/emulated/0/Music
TPath.GetSharedMoviesPath: /storage/emulated/0/Movies
TPath.GetSharedAlarmsPath: /storage/emulated/0/Alarms
TPath.GetSharedRingtonesPath: /storage/emulated/0/Ringtones
NOT: Delphi 10.3 veya üstünü kullanıyorsanız, izinler ve harici URI’lar için yerleşik destek kullanmanız gerekmektedir.
https://delphiworlds.com/2018/06/targeting-android-8-and-higher-continued/Örnek proje:
AndroidDosyaDepolamaPaylasma.dproj
unit brAna;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes,
System.Variants,
FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs,
FMX.Memo.Types,
FMX.ScrollBox, FMX.Memo, FMX.Controls.Presentation, FMX.StdCtrls,
System.IOUtils, Posix.Unistd, System.Permissions
{$IFDEF ANDROID},
Androidapi.Helpers, Androidapi.JNI.GraphicsContentViewText,
Androidapi.JNI.Net, Androidapi.JNI.Os, Androidapi.JNI.JavaTypes,
Androidapi.JNI.Webkit, Androidapi.JNI.App, Androidapi.JNI.Support,
FMX.Helpers.Android, Androidapi.JNIBridge
{$ENDIF};
type
TForm1 = class(TForm)
DosyalarMevcutmu: TButton;
DosyalariKaydet: TButton;
DosyalariSil: TButton;
DahilidenHariciyeKopyala: TButton;
Mp3Paylas: TButton;
GenelDosyaPaylas: TButton;
EpostaGonder: TButton;
PdfGoster: TButton;
Memo1: TMemo;
HariciDepolamaIcinIzinIste: TButton;
procedure FormActivate(Sender: TObject);
procedure DosyalarMevcutmuClick(Sender: TObject);
procedure DosyalariKaydetClick(Sender: TObject);
procedure DosyalariSilClick(Sender: TObject);
procedure HariciDepolamaIcinIzinIsteClick(Sender: TObject);
procedure DahilidenHariciyeKopyalaClick(Sender: TObject);
procedure Mp3PaylasClick(Sender: TObject);
procedure GenelDosyaPaylasClick(Sender: TObject);
procedure EpostaGonderClick(Sender: TObject);
procedure PdfGosterClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.fmx}
procedure TForm1.DahilidenHariciyeKopyalaClick(Sender: TObject);
begin
TFile.Copy(TPath.Combine(TPath.GetDocumentsPath, 'delphican.pdf'),
TPath.Combine(TPath.GetSharedDownloadsPath, 'delphican.pdf'));
end;
procedure TForm1.DosyalariKaydetClick(Sender: TObject);
begin
// dahili depolama
Memo1.Lines.SaveToFile(TPath.Combine(TPath.GetDocumentsPath,
'memo1Dahili.txt'));
// özel harici depolama
Memo1.Lines.SaveToFile(TPath.Combine(TPath.GetPublicPath,
'memo1ÖzelHarici.txt'));
// harici depolama
Memo1.Lines.SaveToFile(TPath.Combine(TPath.GetSharedDownloadsPath,
'memo1Harici.txt'));
end;
procedure TForm1.DosyalariSilClick(Sender: TObject);
begin
DeleteFile(TPath.Combine(TPath.GetDocumentsPath, 'memo1Dahili.txt'));
DeleteFile(TPath.Combine(TPath.GetPublicPath, 'memo1ÖzelHarici.txt'));
DeleteFile(TPath.Combine(TPath.GetSharedDownloadsPath, 'memo1Harici.txt'));
DeleteFile(TPath.Combine(TPath.GetSharedDownloadsPath, 'delphican.pdf'));
DeleteFile(TPath.Combine(TPath.GetPublicPath, 'delphican.pdf'));
DeleteFile(TPath.Combine(TPath.GetPublicPath, 'm.mp3'));
end;
procedure TForm1.DosyalarMevcutmuClick(Sender: TObject);
var
dosya: string;
begin
Memo1.Lines.Clear;
dosya := 'delphican.pdf';
if (FileExists(TPath.Combine(TPath.GetDocumentsPath, dosya))) then
Memo1.Lines.Add(dosya + ' dahili klasörde mevcut')
else
Memo1.Lines.Add(dosya + ' dahili klasörde yok');
dosya := 'm.mp3';
if (FileExists(TPath.Combine(TPath.GetDocumentsPath, dosya))) then
Memo1.Lines.Add(dosya + ' dahili klasörde mevcut')
else
Memo1.Lines.Add(dosya + ' dahili klasörde yok');
dosya := 'memo1Dahili.txt';
if (FileExists(TPath.Combine(TPath.GetDocumentsPath, dosya))) then
Memo1.Lines.Add(dosya + ' dahili klasörde mevcut')
else
Memo1.Lines.Add(dosya + ' dahili klasörde yok');
dosya := 'memo1ÖzelHarici.txt';
if (FileExists(TPath.Combine(TPath.GetPublicPath, dosya))) then
Memo1.Lines.Add(dosya + ' özel harici klasörde mevcut')
else
Memo1.Lines.Add(dosya + ' özel harici klasörde yok');
dosya := 'memo1Harici.txt';
if (FileExists(TPath.Combine(TPath.GetSharedDownloadsPath, dosya))) then
Memo1.Lines.Add(dosya + ' harici klasörde mevcut')
else
Memo1.Lines.Add(dosya + ' harici klasörde yok');
dosya := 'delphican.pdf';
if (FileExists(TPath.Combine(TPath.GetSharedDownloadsPath, dosya))) then
Memo1.Lines.Add(dosya + ' harici klasörde mevcut')
else
Memo1.Lines.Add(dosya + ' harici klasörde yok');
end;
procedure TForm1.EpostaGonderClick(Sender: TObject);
procedure EpostaGonder(const _Recipient, _Subject, _Content,
_Attachment: string);
var
LIntent: JIntent;
LAuthority: JString;
LUri: Jnet_Uri;
JRecipient: TJavaObjectArray<JString>;
begin
LAuthority := StringToJString
(JStringToString(TAndroidHelper.Context.getApplicationContext.
getPackageName) + '.fileprovider');
LUri := TJFileProvider.JavaClass.getUriForFile(TAndroidHelper.Context,
LAuthority, TJFile.JavaClass.init(StringToJString(_Attachment)));
LIntent := TJIntent.JavaClass.init(TJIntent.JavaClass.ACTION_SEND);
JRecipient := TJavaObjectArray<JString>.Create(1);
JRecipient.Items[0] := StringToJString(_Recipient);
LIntent.removeExtra(StringToJString(_Attachment));
LIntent.removeExtra(TJIntent.JavaClass.EXTRA_EMAIL);
LIntent.putExtra(TJIntent.JavaClass.EXTRA_EMAIL, JRecipient);
LIntent.putExtra(TJIntent.JavaClass.EXTRA_SUBJECT,
StringToJString(_Subject));
LIntent.putExtra(TJIntent.JavaClass.EXTRA_TEXT,
StringToJString(_Content));
LIntent.putExtra(TJIntent.JavaClass.EXTRA_STREAM,
TJParcelable.Wrap((LUri as ILocalObject).GetObjectID));
LIntent.setDataAndType(LUri,
StringToJString('vnd.android.cursor.dir/email'));
TAndroidHelper.Activity.startActivity(LIntent);
end;
begin
EpostaGonder('x@gmail.com', ' konu', ' dosya paylaş',
TPath.Combine(TPath.GetDocumentsPath, 'delphican.pdf'));
end;
procedure TForm1.FormActivate(Sender: TObject);
begin
DosyalarMevcutmuClick(Self);
end;
(* Dosya Paylaşma İçin:
Project Options->Application->Entitlement List kısmında "Secure File Sharing" seçeneği işaretli olmalı
https://github.com/DelphiWorlds/Kastri -> Code-> Download ZIP kaydedip ileride değişmeyecek bir klasöre açın
ve Project->Options->Delphi Compiler-> Search path ... Add ile .\KastriFree-master, API, Include klasörlerini ekleyip, Save tıklayın *)
procedure TForm1.Mp3PaylasClick(Sender: TObject);
procedure SetArsMp3Gonder(Mp3DosyaAdı: String);
{$IFDEF ANDROID}
var
Intent: JIntent;
fileuri: JParcelable;
javafile: JFile;
{$ENDIF}
begin
{$IFDEF ANDROID}
Intent := TJIntent.Create;
Intent.setAction(TJIntent.JavaClass.ACTION_SEND);
javafile := TJFile.JavaClass.init(StringToJString(Mp3DosyaAdı));
Intent.setDataAndType(TAndroidHelper.JFileToJURI(javafile),
StringToJString('audio/mpeg'));
fileuri := JParcelable(TJnet_Uri.JavaClass.fromFile(javafile));
Intent.putExtra(TJIntent.JavaClass.EXTRA_STREAM, fileuri);
Intent.addFlags(TJIntent.JavaClass.FLAG_GRANT_READ_URI_PERMISSION);
TAndroidHelper.Activity.startActivity
(TJIntent.JavaClass.createChooser(Intent,
StrToJCharSequence('Paylaş Bakalım: ')));
// SharedActivity.startActivity (TJIntent.JavaClass.createChooser(Intent, StrToJCharSequence ('Share With')));
{$ENDIF}
end;
var
dosya: string;
begin
dosya := 'm.mp3';
if (not FileExists(TPath.Combine(TPath.GetPublicPath, dosya))) then
TFile.Copy(TPath.Combine(TPath.GetDocumentsPath, dosya),
TPath.Combine(TPath.GetPublicPath, dosya));
dosya := TPath.Combine(TPath.GetPublicPath, dosya);
SetArsMp3Gonder(dosya);
end;
procedure TForm1.GenelDosyaPaylasClick(Sender: TObject);
procedure DosyaGonder(DosyaAdi: String);
{$IFDEF ANDROID}
var
Intent: JIntent;
fileuri: JParcelable;
javafile: JFile;
mime: JMimeTypeMap;
ExtToMime: JString;
ExtFile: string;
{$ENDIF}
begin
{$IFDEF ANDROID}
ExtFile := AnsiLowerCase(StringReplace(TPath.GetExtension(DosyaAdi),
'.', '', []));
mime := TJMimeTypeMap.JavaClass.getSingleton();
ExtToMime := mime.getMimeTypeFromExtension(StringToJString(ExtFile));
Intent := TJIntent.Create;
Intent.setAction(TJIntent.JavaClass.ACTION_SEND);
javafile := TJFile.JavaClass.init(StringToJString(DosyaAdi));
Intent.setDataAndType(TAndroidHelper.JFileToJURI(javafile), ExtToMime);
fileuri := JParcelable(TJnet_Uri.JavaClass.fromFile(javafile));
Intent.putExtra(TJIntent.JavaClass.EXTRA_STREAM, fileuri);
Intent.addFlags(TJIntent.JavaClass.FLAG_GRANT_READ_URI_PERMISSION);
TAndroidHelper.Activity.startActivity
(TJIntent.JavaClass.createChooser(Intent,
StrToJCharSequence('Paylaş Bakalım: ')));
{$ENDIF}
end;
var
dosya: string;
begin
dosya := 'delphican.pdf';
if (not FileExists(TPath.Combine(TPath.GetPublicPath, dosya))) then
TFile.Copy(TPath.Combine(TPath.GetDocumentsPath, dosya),
TPath.Combine(TPath.GetPublicPath, dosya));
dosya := TPath.Combine(TPath.GetPublicPath, 'delphican.pdf');
DosyaGonder(dosya);
end;
procedure TForm1.HariciDepolamaIcinIzinIsteClick(Sender: TObject);
begin
PermissionsService.RequestPermissions
([JStringToString(TJManifest_permission.JavaClass.READ_EXTERNAL_STORAGE),
JStringToString(TJManifest_permission.JavaClass.WRITE_EXTERNAL_STORAGE)],
procedure(const APermissions: TArray<string>;
const AGrantResults: TArray<TPermissionStatus>)
begin
if (AGrantResults[0] = TPermissionStatus.Granted) then
Memo1.Lines.Add('READ_EXTERNAL_STORAGE İZİNLİ')
else
Memo1.Lines.Add('READ_EXTERNAL_STORAGE İZİNSİZ');
if (AGrantResults[1] = TPermissionStatus.Granted) then
Memo1.Lines.Add('WRITE_EXTERNAL_STORAGE İZİNLİ')
else
Memo1.Lines.Add('WRITE_EXTERNAL_STORAGE İZİNSİZ');
end);
end;
procedure TForm1.PdfGosterClick(Sender: TObject);
procedure pdfAc(const AFileName: string);
var
LIntent: JIntent;
LUri: Jnet_Uri;
begin
LUri := TAndroidHelper.JFileToJURI
(TJFile.JavaClass.init(StringToJString(AFileName)));
LIntent := TJIntent.JavaClass.init(TJIntent.JavaClass.ACTION_VIEW);
LIntent.setDataAndType(LUri, StringToJString('application/pdf'));
LIntent.setFlags(TJIntent.JavaClass.FLAG_GRANT_READ_URI_PERMISSION);
TAndroidHelper.Activity.startActivity(LIntent);
end;
var
dosya: string;
begin
dosya := 'delphican.pdf';
pdfAc(TPath.Combine(TPath.GetDocumentsPath, dosya));
end;
end.
Android'de özellikle Sqlite veritabanı gibi önemli dosyaları, harici klasöre ve eposta vs. ile yedekleklemek isteyenler için faydalı olacak şekilde düzenlenmiştir.

