การเปลี่ยนแปลงความเป็นส่วนตัวใน Android 10
Android 11 - ความเป็นส่วนตัวและความปลอดภัย
การเปลี่ยนแปลงพฤติกรรม: แอพกำหนดเป้าหมาย Android 11 - ความเป็นส่วนตัว
การใช้การเข้าถึงไฟล์ทั้งหมด (manage_external_storage) การอนุญาต)
ภาพรวมของที่เก็บข้อมูลที่ใช้ร่วมกัน
หลังจากการเปลี่ยนแปลงความเป็นส่วนตัวและความปลอดภัยของ Android 10 และ 11 แล้วการเข้าถึงการจัดเก็บโดยตรงโดยแอปพลิเคชันจะกำหนดขอบเขตไปยังภายในเท่านั้นและตอนนี้การเข้าถึงไฟล์ภายนอกที่เป็นไปได้อย่างหมดจดและง่ายๆโดย 4 APIs:
ไฟล์ทั้งหมดเข้าถึง API (MANACH_EXTRENAL_STORAGE - ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION PERVISTION และ ACTION_MANAGE_STORAGE โดยเจตนา) มีเฉพาะสำหรับตัวจัดการไฟล์และแอพประเภท Antivirus แอพประเภทอื่น ๆ ทั้งหมดจะถูกปฏิเสธโดยการเผยแพร่ ฐานข้อมูล (blobstoremanager) API ทำงานเฉพาะ Android 11 SDK 30 และรุ่นบนไม่ใช่ Android 10 และก่อนหน้า ดังนั้น API ทั้งสองที่ไม่รวมอยู่ด้านล่างเรื่อง
เปิดไฟล์โดยใช้เฟรมเวิร์กการเข้าถึงการจัดเก็บข้อมูล
เข้าถึงเอกสารและไฟล์อื่น ๆ จากที่เก็บข้อมูลที่ใช้ร่วมกัน
ใช้กรณีสำหรับการเข้าถึงเอกสารและไฟล์อื่น ๆ
เฟรมเวิร์กการเข้าถึงที่เก็บข้อมูลรองรับกรณีการใช้งานต่อไปนี้สำหรับการเข้าถึงไฟล์และเอกสารอื่น ๆ
สร้างไฟล์ใหม่
The ACTION_CREATE_DOCUMENT intent action allows users to save a file in a specific location.
เปิดเอกสารหรือไฟล์
The ACTION_OPEN_DOCUMENT intent action allows users to select a specific document or file to open.
ให้สิทธิ์การเข้าถึงเนื้อหาของไดเรกทอรี
The ACTION_OPEN_DOCUMENT_TREE intent action, available on Android 5.0 (API level 21) and higher, allows users to select a specific directory, granting your app access to all of the files and sub-directories within that directory.
ส่วนต่อไปนี้ให้คำแนะนำเกี่ยวกับวิธีการกำหนดค่าแต่ละกรณีการใช้งาน
สร้างไฟล์ใหม่
// Request code for creating a PDF document.
const CREATE_FILE : integer = 11 ; // CREATE_FILE = 1
procedure createFile (pickerInitialUri : JNet_Uri); (* PdfDosyasiOlustur *)
var
Intent : JIntent;
begin
Intent := TJIntent.Create;
Intent.setAction(TJIntent.JavaClass.ACTION_CREATE_DOCUMENT);
Intent.addCategory(TJIntent.JavaClass.CATEGORY_OPENABLE);
Intent.setType(StringToJString( ' application/pdf ' ));
Intent.putExtra(TJIntent.JavaClass.EXTRA_TITLE,StringToJString( ' invoice.pdf ' ));
// Optionally, specify a URI for the directory that should be opened in
// the system file picker when your app creates the document.
Intent.putExtra(TJDocumentsContract.JavaClass.EXTRA_INITIAL_URI,JParcelable(pickerInitialUri));
MainActivity.startActivityForResult(Intent, CREATE_FILE);
end ;เปิดไฟล์
// Request code for selecting a PDF document.
// const PICK_PDF_FILE : integer = 22; //PICK_PDF_FILE = 2
procedure openFile (pickerInitialUri : JNet_Uri); (* PdfDosyasiSec *)
var
Intent: JIntent;
begin
Intent := TJIntent.Create;
Intent.setAction(TJIntent.JavaClass.ACTION_OPEN_DOCUMENT);
Intent.addCategory(TJIntent.JavaClass.CATEGORY_OPENABLE);
Intent.setType(StringToJString( ' application/pdf ' ));
// Optionally, specify a URI for the file that should appear in the
// system file picker when it loads.
Intent.putExtra(TJDocumentsContract.JavaClass.EXTRA_INITIAL_URI,JParcelable(pickerInitialUri));
TAndroidHelper.Activity.startActivityForResult(Intent, PICK_PDF_FILE);
end ;ให้สิทธิ์การเข้าถึงเนื้อหาของไดเรกทอรี
procedure openDirectory (uriToLoad : JNet_Uri); (* DizinAc *)
// Choose a directory using the system's file picker.
var
Intent : JIntent;
begin
Intent := TJIntent.Create;
Intent.setAction(TJIntent.JavaClass.ACTION_OPEN_DOCUMENT_TREE);
// Optionally, specify a URI for the directory that should be opened in
// the system file picker when it loads.
Intent.putExtra(TJDocumentsContract.JavaClass.EXTRA_INITIAL_URI, JParcelable(uriToLoad));
Mainactivity.startActivityForResult(Intent, Open_Doc_Tree);
end ;ดำเนินการในสถานที่ที่เลือก
procedure TForm1.HandleMessageAction ( const Sender: TObject; const M: TMessage); (* IletiFaaliyetiYakala *)
begin
if M is TMessageResultNotification then
OnActivityResult(
TMessageResultNotification(M).RequestCode,
TMessageResultNotification(M).ResultCode,
TMessageResultNotification(M). Value );
end ;
procedure TForm1.OnActivityResult (RequestCode, ResultCode: Integer;
Data: JIntent);
var
Uri: Jnet_Uri;
begin
if ResultCode = TJActivity.JavaClass.RESULT_OK then
begin
// The result data contains a URI for the document or directory that
// the user selected.
Uri := nil ;
if Assigned(Data) then
begin
Uri := Data.getData;
if RequestCode = your-request-code then
begin
// Perform operations on the document using its URI.
end ;
end ;
end ;โดยการอ้างอิงถึง URI ของรายการที่เลือกแอปของคุณสามารถดำเนินการหลายรายการในรายการ ตัวอย่างเช่นคุณสามารถเข้าถึงข้อมูลเมตาของรายการแก้ไขรายการในสถานที่และลบรายการ ส่วนต่อไปนี้แสดงวิธีการดำเนินการเสร็จสิ้นในไฟล์ที่ผู้ใช้เลือก:
คงอยู่
// TakeFlags: integer;
Intent := TJIntent.Create;
TakeFlags := Intent.getFlags
and (TJIntent.JavaClass.FLAG_GRANT_READ_URI_PERMISSION
or TJIntent.JavaClass.FLAG_GRANT_WRITE_URI_PERMISSION);
// Check for the freshest data.
TAndroidHelper.Activity.getContentResolver.takePersistableUriPermission
(Uri, TakeFlags);ตรวจสอบข้อมูลเมตาดาต้า
procedure dumpImageMetaData (uri : JNet_Uri); (* GoruntuMetaVerisiDokumu *)
// The query, because it only applies to a single document, returns only
// one row. There's no need to filter, sort, or select fields,
// because we want all fields for one document.
var
displayName, size : JString;
sizeIndex : integer;
cursor : JCursor;
begin
cursor := TAndroidHelper.Activity.getContentResolver.query(uri, nil , nil , nil , nil , nil );
try
// moveToFirst() returns false if the cursor has 0 rows. Very handy for
// "if there's anything to look at, look at it" conditionals.
if (cursor<> nil ) then
if (cursor.moveToFirst) then
begin
displayName := cursor.getString (cursor.getColumnIndex (TJOpenableColumns.JavaClass.DISPLAY_NAME));
Memo1.Lines.Add( { TAG.ToString + } ' Display Name: ' + JStringToString (displayName));
sizeIndex:=cursor.getColumnIndex(TJOpenableColumns.JavaClass.SIZE);
size := nil ;
if not (cursor.isNull(sizeIndex)) then
size := cursor.getString(sizeIndex)
else
size:=StringToJString ( ' Unknown ' );
Memo1.Lines.Add( { TAG.ToString + } ' Size: ' + JStringToString (size));
end ;
finally
cursor.close;
end ;
end ;เปิดเอกสาร - บิตแมป
function getBitmapFromUri (uri : JNet_Uri): JBitmap; (* UridenBiteslemAl *)
var
fileDescriptor : JFileDescriptor;
parcelFileDescriptor : JParcelFileDescriptor;
image : JBitmap;
begin
Result := nil ;
try
parcelFileDescriptor := TAndroidHelper.Activity
.getContentResolver.openFileDescriptor(uri,StringToJString( ' r ' ));
fileDescriptor := parcelFileDescriptor.getFileDescriptor;
image := TJBitmapFactory.JavaClass.decodeFileDescriptor(fileDescriptor);
parcelFileDescriptor.close;
result := image;
except
on E: Exception do
ShowMessage(e.Message);
end ;เปิดเอกสาร - อินพุตสตรีม
function TForm1.readTextFromUri (Uri : JNet_Uri): string; (* MetinDosyasiOkuyucu *)
const
bufferSize = 4096 * 2 ;
var
inputStream : JInputStream;
b : TJavaArray<Byte>;
ms: TMemoryStream;
sl: TStringList;
bufflen: Integer;
begin
result := ' ' ;
try
inputStream := TAndroidHelper.Context.getContentResolver.openInputStream(Uri);
ms := TMemoryStream.Create;
bufflen := inputStream.available;
b := TJavaArray<Byte>.Create(bufflen);
inputStream.read(b);
ms.Write(b.Data^, bufflen);
ms.position := 0 ;
sl := TStringList.Create;
sl.LoadFromStream(ms);
result := sl.Text;
sl.Free;
b.Free;
ms.Free;
inputStream.Close;
except
on E: Exception do
Application.ShowException(E);
end ;
end ;แก้ไขเอกสาร
procedure alterDocument (uri : JNet_Uri); (* MetinBelgesiDegistir *)
var
pfd : JParcelFileDescriptor;
fileOutputStream : JFileOutputStream;
begin
try
pfd := TAndroidHelper.Activity.getContentResolver
.openFileDescriptor(uri,StringToJString( ' w ' ));
fileOutputStream := TJFileOutputStream.JavaClass.init(pfd.getFileDescriptor);
fileOutputStream.write(StringToJString( ' Overwritten at ' + timetostr(Now)).getBytes);
fileOutputStream.close;
pfd.close;
except
on E: Exception do
ShowMessage(e.Message);
end ;
end ; ลบเอกสาร
TJDocumentsContract.JavaClass.deleteDocument (TAndroidHelper.contentResolver, Uri);เปิดไฟล์เสมือนจริง
function isVirtualFile (Uri : JNet_Uri): boolean; (* SanalDosyami *)
var
flags : integer;
cursor : JCursor;
s : TJavaObjectArray<JString>;
begin
if ( not TJDocumentsContract.JavaClass.isDocumentUri(TAndroidHelper.Context,Uri)) then
begin
result := false;
exit;
end ;
s := TJavaObjectArray<JString>.Create( 0 );
s[ 0 ] := TJDocumentsContract_Document.JavaClass.COLUMN_FLAGS;
cursor := TAndroidHelper.Activity.getContentResolver.query(uri,s, nil , nil , nil );
flags:= 0 ;
if (cursor.moveToFirst) then
flags:=cursor.getInt( 0 );
cursor.close;
result := (flags and TJDocumentsContract_Document.JavaClass.FLAG_VIRTUAL_DOCUMENT) <> 0 ;
end ; ไฟล์เสมือนเป็นภาพ
function getInputStreamForVirtualFile (Uri : JNet_Uri; mimeTypeFilter : String): JInputStream; (* SanalDosyaIcinGirisAkisiAl *)
var
openableMimeTypes : TJavaObjectArray<JString>;
resolver : JContentResolver;
begin
resolver := TAndroidHelper.Activity.getContentResolver;
openableMimeTypes := resolver.getStreamTypes(uri,StringToJString(mimeTypeFilter));
if ((openableMimeTypes = nil ) or (openableMimeTypes.Length < 1 )) then
begin
raise Exception.Create( ' File not found! ' );
result := nil ;
exit;
end ;
result := resolver.openTypedAssetFileDescriptor(uri,openableMimeTypes[ 0 ], nil )
.createInputStream;
end ;ฟังก์ชั่นเส้นทาง RTL มาตรฐานในแพลตฟอร์มเป้าหมายที่รองรับ: ที่เก็บข้อมูลภายใน, ภายในส่วนตัวภายนอก, ที่เก็บข้อมูลภายนอกที่ใช้ร่วมกัน
การเปลี่ยนแปลงที่เก็บข้อมูลในเวอร์ชัน Android
| - | รหัส | SDK | เผยแพร่ | เปลี่ยน |
|---|---|---|---|---|
| 1 | - | 1 | 23.09.08 | เจตนาตัวเลือกระบบ (action_) เริ่มต้นในเวอร์ชันแรก |
| 4.4 | K | 19 | 31.10.13 | FileProvider (เนื้อหา API), DocumentsContract, คลาส DocumentProvider |
| 7 | n | 25 | 04.10.16 | บังคับใช้ FileProvider (เมื่อกำหนดเป้าหมายเวอร์ชันนี้) |
| 8 | โอ | 26 | 21.03.17 | สิงหาคม 2018 Google Play Store และ 8.0 ข้อกำหนดการเผยแพร่ |
| 10 | ถาม | 29 | 03.09.19 | ที่เก็บข้อมูลที่กำหนดไว้ ความเป็นส่วนตัวและความปลอดภัยเพิ่มขึ้น |
| 11 | R | 30 | 09.09.20 | สิงหาคม 2021 การกำหนดเป้าหมาย Android API 30 (ข้อกำหนดการเผยแพร่ Google Play Store) |
ผู้ให้บริการเอกสาร Android 4.3 Action_pick, Action_Get_Content; Android 4.4 (API 19) Action_Open_Document; Android 5.0 (API 21) ACTION_OPEN_DOCUMENT_TREE
การสนับสนุน Android ในเวอร์ชัน Delphi
Rad Studio Delphi 10+ มาพร้อมกับ Android 10+ Java Libraries (Androidapi.jni.graphicscontentViewText, Androidapi.ioutils, Androidapi.jni.media, Androidapi.jni.provider Androidapi.jni.javatypes, Androidapi.jni.net, Androidapi.jni.os, Androidapi.jni.provider) สนับสนุนการจัดเก็บที่ครอบคลุม SAF และคำสั่ง MediaStore
| Delphi | Android |
|---|---|
| XE5 | 2.33 - 4.4 |
| 10 ซีแอตเทิล | 4.03 - 5 |
| 10.1 เบอร์ลิน | 4.03 - 7 |
| 10.2 โตเกียว | 4.1 - 8 |
| 10.3 ริโอ | 5.1 - 10 |
| 10.4 ซิดนีย์ | 6 - 11 |
| 11 อเล็กซานเดรีย | 8.1 - 11 |
Rad Studio Delphi 11.0 Alexandria มีการสนับสนุน Android 30 API (ความต้องการ Google Play Store 2021)
ความแตกต่างในการจัดเก็บไฟล์และการแชร์ใน Android:
•ไม่มีความแตกต่างใน Android 11 สำหรับที่เก็บข้อมูลภายใน เช่นเดียวกับใน Android 10 และก่อนหน้านี้การเข้าถึงไฟล์ไม่ จำกัด ไม่จำเป็นต้องได้รับอนุญาต
•การแชร์ไฟล์เหมือนกันอีกครั้งเนื้อหา API (FileProvider) และวิธีการโทรด้วย“ Intent.SetAction (tjintent.javaclass.Action_Send);” เจตนาดำเนินต่อไป ในการเปิดใช้งานคุณสมบัติ FileProvider ให้เลือกตัวเลือก "การแชร์ไฟล์ที่ปลอดภัย" ในตัวเลือกโครงการ-> แอปพลิเคชัน-> รายการสิทธิ์ (มีอยู่ใน Delphi 10.3 Rio และใหม่กว่า) ควรถูกเพิ่มเพื่อใช้ API นี้ต่อไป สำหรับ Delphi 10.3 รุ่นก่อนหน้านี้ควรเพิ่ม API FileProvider ด้วยตนเองตามที่อธิบายไว้ใน "การจัดเก็บไฟล์และการแชร์ใน Android">“ การใช้ FileProvider สำหรับการแชร์ไฟล์”
•คำสั่งคัดลอกการจัดเก็บข้อมูลภายนอกใน "การจัดเก็บไฟล์และการแชร์ใน Android" โครงการตัวอย่างทำงานเมื่อกำหนดเป้าหมาย Android 10 SDK 29 รุ่นก่อนหน้าเช่น "memo1.lines.savetofile (tpath.combine (tpath.getshareddownloadspath, memo1external.txt ');" เมื่อ Android 11 SDK 30 กำหนดเป้าหมายแอพเดียวกันจะเพิ่ม“ ไม่สามารถสร้างไฟล์“ (Storage/Emulated/0/ดาวน์โหลด/Memo1External.txt” ยกเว้นสิทธิ์ปฏิเสธ” คำสั่ง“ DELETEFILE” ไม่ได้ลบไฟล์ภายใต้การดาวน์โหลด
•สถานะปัจจุบันก่อน Android 10 และหลัง Android 11 การจัดเก็บไฟล์ที่จัดเก็บข้อมูลที่กำหนดขอบเขตการเปลี่ยนแปลง:
•“ RequestlegacyexternalStorage” ถูกลบออกจากการจัดเก็บขอบเขตและตอนนี้แอปพลิเคชันที่ต้องการเข้าถึงมันถูกปฏิเสธใน GPSTORE ในการเผยแพร่แอปของคุณให้ทำการเปลี่ยนแปลงต่อไปนี้ใน AndroidManifest.template.xml ::
android:requestLegacyExternalStorage="false">
•ไฟล์ทั้งหมดเข้าถึง API (action_manage_storage) ถูกปฏิเสธโดย Google Play Store แต่ถ้าได้รับอนุญาตนี้ (และแอพจะไม่ถูกเผยแพร่) มันสามารถอ่านและบันทึกไฟล์ได้ก่อนที่ Android 11 หากคุณต้องการสิ่งนี้ action_manage_storage เจตนา
•ฐานข้อมูลที่ใช้ร่วมกัน BLOBSTOREMANAGER API ทำงานได้เฉพาะใน Android 11 SDK 30 ขึ้นไปไม่ทำงานใน Android 10 และรุ่นก่อนหน้า
•ฐานข้อมูลที่ใช้ร่วมกัน BLOBSTOREMANAGER API ทำงานเฉพาะ Android 11 SDK 30 และรุ่นที่สูงกว่าเท่านั้นไม่เรียกใช้ Android 10 และรุ่นที่ต่ำกว่า ไม่รวมการพิจารณาเนื่องจากอุปกรณ์ส่วนใหญ่ไม่ได้รับการสนับสนุนในปัจจุบัน
•การเข้าถึงพื้นที่เก็บข้อมูลที่ใช้ร่วมกัน (ภายนอก) ด้วย SAF และ MediaStore:
•รับ URI: มีเพียงสามตัวอย่างแรกของกรณีการใช้งานจากรหัสการฝึกอบรมด้านบนรับทั้งไฟล์ URI และอนุญาตให้เข้าถึงได้ การเข้าถึงไฟล์ภายนอก URIS ได้รับการแนะนำโดยการจับภาพการกระทำของพวกเขาด้วย onactivityResult
•ตัวอย่างอื่น ๆ ทั้งหมดอธิบายถึงวิธีจัดการกับ URIs ที่ได้จากการใช้งาน 3 กรณีเหล่านี้ หากคุณไม่ทราบไฟล์ URI คุณไม่สามารถใช้งานได้
•นอกจากนี้ยังสามารถใช้ความตั้งใจเก่า action_get_content เพื่อรับ URI โดยเรียกตัวเลือกระบบคล้ายกับ action_open_document; แต่มันไม่ได้ให้การเข้าถึงอย่างถาวร
•ด้วย mediastore.files, URI ของไฟล์ในที่เก็บข้อมูลสื่อยังสามารถเรียกคืนได้ แต่มันไม่ได้รวมอยู่ในเนื้อหาของหัวข้อนี้เนื่องจากคำแถลง“ เนื้อหาของ MediaStore.files ใน Media Store ยังขึ้นอยู่กับว่าแอปพลิเคชันของคุณใช้ที่เก็บข้อมูลที่กว้างขวางในแอปพลิเคชันที่กำหนดเป้าหมาย Android 10 หรือสูงกว่า” แม้ว่าคลาส MediaStore.Downloads และ GetMediauri ยังให้การเข้าถึงไฟล์หน่วยเก็บข้อมูลภายนอก URI แต่ก็รองรับ SDK 29 ขึ้นไปเท่านั้น
• E หากคุณต้องการใช้งานไฟล์ที่มีรหัสใด ๆ โดยไม่ต้องใช้เคสคุณสามารถใช้ไฟล์ URI โดยตรง (เช่น: เนื้อหา: //com.android.providers.downloads.documents/documents/16874) ด้วยการใช้งาน "java.lang.securityException
•สั้น ๆ แอพบันทึกไฟล์การจัดเก็บข้อมูลภายนอกในพื้นที่จัดเก็บขอบเขตอยู่ในคอขวด URI ยิ่งไปกว่านั้นหากคุณต้องการครอบคลุมอุปกรณ์ Android ทั้งหมดในตลาดโดยใช้รุ่นเก่าก่อน SDK 29 คุณต้องใช้กรณีการใช้ SAF ไม่มีทางออกอื่นนอกจากนั้น ในการประชุมนักพัฒนา Android วิดีโอประกาศพวกเขาอ้างว่าจุดประสงค์ในการเปลี่ยนไปใช้ที่เก็บข้อมูลคือการลบสิทธิ์ที่ไม่จำเป็น แต่ในความเป็นจริงแล้วสิ่งที่ตรงกันข้ามเกิดขึ้นสำหรับนักพัฒนาและผู้ใช้
•รหัสตัวอย่าง (ทั้งหมดในบทช่วยสอน SAF และส่วนใหญ่ใน MediaStore) คือ SDK Level 1-24 และได้รับการสนับสนุนโดย Delphi 10.x เวอร์ชัน
• Delphi 11 จำเป็นต้องใช้ระดับ SDK 29 VE 30 คำสั่ง MediaStore ของ "การดาวน์โหลด", "getMediauri" คลาส, โหลดภาพขนาดย่อ> loadthumbnail, เพิ่มรายการ> volume_external_primary, การจัดการสถานะการทำงานของ Media CreateFavoriteRequest”
•สำหรับการแชร์ไฟล์จากที่เก็บข้อมูลภายนอกก่อนที่จะได้รับ URI ของไฟล์โดยตัวเลือกไฟล์ SAF จากนั้นแบ่งปันความตั้งใจ หากมีไฟล์ URI ภายนอกสามารถใช้สำหรับการเปิดไฟล์ใหม่การอ่านการบันทึกการบันทึกวัตถุประสงค์การคัดลอก
•รายการค่าเสื่อมราคาสำหรับการจัดเก็บภายนอก:
// ExternalFile := TPath.Combine(TPath.GetSharedDownloadsPath,'delphican.txt');
Memo1.Lines.SaveToFile(ExternalFile);
TFile.Copy(InternalFile, ExternalFile);
DeleteFile(ExternalFile);
Uri := TJnet_Uri.JavaClass.fromFile(TJFile.JavaClass.init(StringToJString(ExternalFile)));
Uri := TJnet_Uri.JavaClass.parse(StringToJString( ' file:/// ' + ExternalFile));
JinputStream1 := TJFileInputStream.JavaClass.init(StringToJString(ExternalFile));
Uri := TJFileProvider.JavaClass.getUriForFile(TAndroidHelper.Context, LAuthority, TJFile.JavaClass.init(StringToJString(ExternalFile)));
Intent := TJFileProvider.JavaClass.getUriForFile(TJnet_Uri.JavaClass.fromFile(TJFile.JavaClass.init(StringToJString(ExternalFile))));
Uri := TAndroidHelper.JFileToJURI (TJFile.JavaClass.init(StringToJString(ExternalFile)));
Uri := TJFileProvider.JavaClass.getUriForFile(Context, LAuthority, AFile);ไฟล์เนื้อหาเนื้อหาการกระทำและความตั้งใจทั้งหมดทั้งหมดจะคิดค่าเสื่อมราคา
(PS: หากได้รับอนุญาตให้เข้าถึงไดเรกทอรีที่ได้รับไฟล์โดย ACTION_OPEN_DOCUMENT_TREE อาจเป็นไปได้ที่จะเข้าถึงไฟล์โดยคำสั่งเหล่านี้ แต่ไม่สามารถลองได้เนื่องจากเจตนานี้ไม่ทำงานใน Delphi 10))))
•ในการใช้ SAF Framework Storage Access ไม่จำเป็นต้องได้รับสิทธิ์ในการรับจากโครงการ -> ใช้สิทธิ์ทั้งหมดสามารถปิดได้ โครงการไม่จำเป็นต้องได้รับอนุญาตใด ๆ เช่น“ PermissionsService.requestPermissions” การอนุญาตทั้งหมดในโครงการตัวอย่างปิด
•ในการเข้าถึงไฟล์ด้วย SAF นั้นเพียงพอที่จะเรียกใช้กรณีการใช้งาน (Action_Create_Document, Action_OPEN_DOCUMENT, ACTION_OPEN_DOCUMENT_TREE) อย่างไรก็ตามแต่ละพวกเขาจะเปิดอินเทอร์เฟซตัวเลือกระบบซึ่งแตกต่างจาก tfile.copy เก่า
• action_create_document ใช้เพื่อบันทึกไฟล์ใหม่ด้วยอินเทอร์เฟซที่คล้ายกับ "บันทึกเป็น" และ action_open_document ใช้เพื่อเปิดแสดงและแก้ไขไฟล์ที่มีอยู่ action_open_document_tree สำหรับการเข้าถึงไม่เพียง แต่ไดเรกทอรี แต่ยังรวมถึงไฟล์ทั้งหมดที่อยู่ภายใต้
•เมื่อใช้ SAF ส่วนที่ผ่านการกรองโดยเจตนาใน AndroidManifest.template.xml พร้อมกับ Delphi ไม่จำเป็นต้องเพิ่มความตั้งใจอื่น
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
•กรณีการใช้งาน SAF จะพร้อมใช้งานทันทีในโครงการ Delphi ที่กำหนดเป้าหมาย SDK 30 โดยเรียกใช้กับไลบรารี Android และรหัสตัวอย่างที่จำเป็นเนื่องจากมีอยู่ในตัวกับ Delphi 10 ทั้งหมด
•การเขียนไปยังเสียงรูปภาพไฟล์วิดีโอและการดาวน์โหลดด้านบนเวอร์ชัน 11 ไม่ได้ จำกัด อยู่ด้วย MediaStore คือคุณสามารถบันทึกรูปภาพโดยไม่ได้รับอนุญาต
•คอลเลกชันสื่อสามารถอ่านได้โดยได้รับอนุญาตเท่านั้น
• access_media_location เป็นสิ่งจำเป็นสำหรับข้อมูลตำแหน่งภาพ
• PDF ข้อความ ฯลฯ การเข้าถึงไฟล์ที่มีอยู่โดย "ตัวเลือกระบบ"
•“ ตัวเลือกระบบ” เป็นสิ่งจำเป็นสำหรับการอ่านและการเขียนนอกคอลเลกชัน
•โครงการ -> ใช้การอนุญาต -> ตัวเลือก WRITE_EXTRENAL_STORAGE จะต้องปิดใน SDK 29. read_external_storage เป็นสิ่งจำเป็นสำหรับการอ่าน
•คลาส MediaStore URI ไม่สามารถใช้งานได้ในไลบรารี Pre-Delphi 11 เนื่องจากถูกลบออกสำหรับการเข้าถึงไฟล์เหนือ SDK 29 ดังนั้นรหัสการสอน MediaStore ส่วนใหญ่ข้างต้นจึงนำเข้าจาก Java ไปยัง Object Pascal
•เมื่อรันไทม์ขึ้นอยู่กับระดับ SDK ของอุปกรณ์ควรขออนุญาตเขียน (SDK <28) และไม่ใช่สำหรับ (SDK> = 29)
• AndroidManifest.template.xml การเปลี่ยนแปลง:
<uses-sdk android:minSdkVersion="%minSdkVersion%" android:targetSdkVersion="30" />
android:requestLegacyExternalStorage="false">
• MediaStore ต้องได้รับอนุญาตอ่านเกี่ยวกับที่เก็บข้อมูลภายนอกไม่ใช่การเขียนอนุญาต สำหรับความเข้ากันได้กับเวอร์ชันก่อน Android 10 ภายใต้โครงการ -> ใช้การอนุญาตให้ลบตัวเลือก write_external_storage และเพิ่มสิ่งต่อไปนี้ในรายการ:
<%uses-permission%>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" />
•การคัดลอกไฟล์ไม่สามารถทำได้อีกต่อไปด้วยคำสั่งเดียว (tfile.copy) จากที่เก็บข้อมูลภายนอก ก่อนอื่นเราเรียกกรณีการใช้งาน (action_open_document หรือ action_create_document) เจตนา จากนั้นเราจัดการรหัสคำขอด้วย "handlemessageactivity" และ "onactivityResult" ในที่สุดเราก็เขียนถึงไฟล์ที่เราเพิ่งเปิดด้วย URI และสตรีม (JinputStream, JFileOutputStream)
procedure TForm1.ButtonFileSharingClick (Sender: TObject);
var
Intent: JIntent;
mime: JMimeTypeMap;
ExtToMime: JString;
ExtFile: string;
File : string;
begin
File := File_name(UriCan);
ExtFile := AnsiLowerCase(StringReplace(TPath.GetExtension( File ),
' . ' , ' ' , []));
mime := TJMimeTypeMap.JavaClass.getSingleton();
ExtToMime := mime.getMimeTypeFromExtension(StringToJString(ExtFile));
Intent := TJIntent.Create;
Intent.setAction(TJIntent.JavaClass.ACTION_SEND);
Intent.setDataAndType(UriCan, ExtToMime);
Intent.putExtra(TJIntent.JavaClass.EXTRA_STREAM, JParcelable(UriCan));
Intent.addFlags(TJIntent.JavaClass.FLAG_GRANT_READ_URI_PERMISSION);
TAndroidHelper.Activity.startActivity(TJIntent.JavaClass.createChooser(Intent,
StrToJCharSequence( ' Let '' s share: ' )));
end ; procedure TForm1.ButtonCopyFileFromInternalToExternalClick (Sender: TObject);
(* TFile.Copy(TPath.Combine(TPath.GetDocumentsPath, 'delphican.pdf'),
TPath.Combine(TPath.GetSharedDownloadsPath, 'delphican.pdf')); *)
procedure CreateFilePdf (pickerInitialUri: JNet_Uri);
var
Intent: JIntent;
begin
Intent := TJIntent.Create;
Intent.setAction(TJIntent.JavaClass.ACTION_CREATE_DOCUMENT);
Intent.addCategory(TJIntent.JavaClass.CATEGORY_OPENABLE);
Intent.setType(StringToJString( ' application/pdf ' ));
Intent.putExtra(TJIntent.JavaClass.EXTRA_TITLE,
StringToJString(TPath.GetFileName(FileToBeCopied)));
Intent.putExtra(TJDocumentsContract.JavaClass.EXTRA_INITIAL_URI,
JParcelable(pickerInitialUri));
MainActivity.startActivityForResult(Intent,
Copy_File_FromInternal_ToExternal);
end ;
begin
FileToBeCopied := TPath.Combine(TPath.GetDocumentsPath, ' delphican.pdf ' );
CreateFilePdf( nil );
end ;
procedure TForm1.CopyFile_FromInternalToExternal ( File : string);
const
bufferSize = 4096 * 2 ;
var
noOfBytes: Integer;
b: TJavaArray<Byte>;
File_Read: JInputStream;
File_Write: JFileOutputStream;
pfd: JParcelFileDescriptor;
begin
if not FileExists( File ) then
begin
ShowMessage( File + ' not found! ' );
exit;
end ;
try
DosyaOku := TAndroidHelper.Context.getContentResolver.openInputStream
(TJnet_Uri.JavaClass.fromFile(TJFile.JavaClass.init
(StringToJString( File ))));
pfd := TAndroidHelper.Activity.getContentResolver.openFileDescriptor(UriCan,
StringToJString( ' w ' ));
DosyaYaz := TJFileOutputStream.JavaClass.init(pfd.getFileDescriptor);
b := TJavaArray<Byte>.Create(bufferSize);
noOfBytes := File_Read.read(b);
while (noOfBytes > 0 ) do
begin
File_Write.write(b, 0 , noOfBytes);
noOfBytes := File_Read.read(b);
end ;
File_Write.close;
File_Read.close;
except
on E: Exception do
Application.ShowException(E);
end ;
Showmessage( ' File copied from Internal to External : ' + DosyaAdi(UriCan));
end ; procedure TForm1.ButtonFileCopyFromExternalToInternalClick (Sender: TObject);
(* TFile.Copy(TPath.Combine(TPath.GetSharedDownloadsPath, 'delphican.pdf'),
TPath.Combine(TPath.GetPublicPath, 'delphican.pdf')); *)
var
Intent: JIntent;
begin
Intent := TJIntent.Create;
Intent.setAction(TJIntent.JavaClass.ACTION_OPEN_DOCUMENT);
Intent.addCategory(TJIntent.JavaClass.CATEGORY_OPENABLE);
Intent.setType(StringToJString( ' */* ' ));
TAndroidHelper.Activity.startActivityForResult(Intent,
Copy_File_FromExternal_ToInternal);
end ;
procedure TForm1.FileCopy_FromExternalToInternal ;
const
bufferSize = 4096 * 2 ;
var
noOfBytes: Integer;
b: TJavaArray<Byte>;
File_Read: JInputStream;
File_Write: JFileOutputStream;
File : string;
// pfd : JParcelFileDescriptor;
begin
try
Dosya := TPath.Combine(TPath.GetPublicPath, DosyaAdi(UriCan));
if FileExists( File ) then
begin
ShowMessage( ' " ' + File + ' " zaten mevcut! ' );
exit;
end ;
File_Write := TJFileOutputStream.JavaClass.init(StringToJString( File ));
File_Read := TAndroidHelper.Context.getContentResolver.
openInputStream(UriCan);
b := TJavaArray<Byte>.Create(bufferSize);
noOfBytes := File_Read.read(b);
while (noOfBytes > 0 ) do
begin
File_Write.write(b, 0 , noOfBytes);
noOfBytes := File_Read.read(b);
end ;
File_Write.close;
File_Read.close;
except
on E: Exception do
Application.ShowException(E);
end ;
ShowMessage( ' File copied from External to Internal : ' + File_Name(UriCan));
end ;