Android 10의 개인 정보 보호 변경
Android 11- 개인 정보 및 보안
행동 변경 : Android 11을 대상으로하는 앱 - 개인 정보
모든 파일 사용 액세스 (manage_external_storage) 권한
공유 스토리지 개요
Android 10 및 11 개인 정보 및 보안 변경 후, 응용 프로그램에 의한 스토리지에 대한 직접 액세스는 내부로만 범위를 지정하고 이제는 순수하고 단순히 4 API에 의해 가능한 외부 파일에 액세스 할 수 있습니다.
모든 파일 액세스 ACCEND ACCESS ACCESS ACCESS ACCESS ACCESS ACCEST API (Manage_EXTERNAL_STORAGE -ACTION_APP_APT_APL_ACCESS_PERMISSION ARMISSION 및 ACTION_MANAGE_STORAGE 의도) 파일 관리자 및 안티 바이러스 유형 앱에서만 사용할 수있는 다른 모든 유형 앱은 Google Play 스토어 게시에 의해 거부됩니다. 데이터베이스 (BlobstorEmanager) API는 Android 10 및 이전 버전이 아닌 Android 11 SDK 30 및 상단 버전 만 실행합니다. 따라서 두 API는 아래 주제를 제외했습니다.
Storage Access Framework를 사용하여 파일을 엽니 다
공유 스토리지에서 문서 및 기타 파일에 액세스하십시오
문서 및 기타 파일에 액세스하기위한 사례
Storage Access Framework는 파일 및 기타 문서에 액세스하기위한 다음 사용 사례를 지원합니다.
새 파일을 만듭니다
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 경로 기능 : 내부 스토리지, 외부 개인 내부 S, 공유 외부 스토리지
안드로이드 버전의 스토리지 변경
| 틀 | 암호 | SDK | 게시 | 변화 |
|---|---|---|---|---|
| 1 | - | 1 | 23.09.08 | 의도, 시스템 피커 (Action_)는 첫 번째 버전에서 시작되었습니다 |
| 4.4 | 케이 | 19 | 31.10.13 | FileProvider (Content API), DocumentSContract, DocumentProvider 클래스 |
| 7 | N | 25 | 04.10.16 | FileProvider 사용법의 필수 (이 버전을 대상으로 할 때) |
| 8 | 영형 | 26 | 21.03.17 | 2018 년 8 월 Google Play 스토어 및. 8.0 출판 요구 사항 |
| 10 | 큐 | 29 | 03.09.19 | 스코프 스토리지. 개인 정보 및 보안이 증가했습니다 |
| 11 | 아르 자형 | 30 | 09.09.20 | 2021 년 8 월 Android API 30 (Google Play 스토어 게시 요구 사항) |
문서 제공 업체, 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
델파이 버전의 안드로이드 지원
rad studio delphi 10+ 버전은 Android 10+ Java 라이브러리 (androidapi.jni.graphicscontentviext, androidapi.ioutils, androidapi.jni.media, androidapi.jni.jni.provider, androidapi.jni.app, androidapi.helpers, androidapi.jni.jni.jni.jni.jni. androidapi.jni.net, androidapi.jni.os, androidapi.jni.provider) 스코어 스토리지, SAF 및 일부 중디 스토어 명령을 지원합니다.
| 델파이 | 기계적 인조 인간 |
|---|---|
| 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 이전 버전의 경우 "파일 공유 및 파일 공유에 FileProvider 사용"에 설명 된대로 FileProvider API를 수동으로 추가해야합니다.
• "파일 스토리지 및 Android의 공유"샘플 프로젝트에서 외부 스토리지 복사 명령을 타겟팅 할 때 샘플 프로젝트가 실행됩니다. IE "Memo1.lines.savetofile (tpath.combine (tpath.getsharedDownloadspath, 'memo1external.txt')); Android 11 SDK 30이 대상이되면 동일한 앱이 "파일을 만들 수 없습니다"(스토리지/엠제이드/0/다운로드/메모 1external.txt ". 권한 거부"예외. "deletefile"명령은 다운로드/getsharedDownloadSpath 폴더에서 파일을 삭제하지 않습니다. 공유 된 모든 외부 폴더에 대해 유효합니다.
• Android 10 이전 및 Android 11의 필수 스토리지 파일 스토리지가 변경된 후 현재 상태 :
• "requestLegacyExternAlstorage"가 스코프 스토리지에서 제거되었으며 이제 액세스하려는 응용 프로그램이 GPStore에서 거부되었습니다. 앱을 게시하려면 AndroidManifest.template.xml ::에서 다음을 변경하십시오.
android:requestLegacyExternalStorage="false">
• 모든 파일 액세스 API (Action_Manage_Storage)는 Google Play Store에서 거부되지만이 권한이 부여되면 (및 앱이 게시되지 않으면) Android 11 이전과 같이 파일을 읽고 저장할 수 있습니다.이 경우 필요한 경우 "AndroidManifest.template.xml"에서 추가해야합니다. 의도.
• 공유 데이터베이스 Blobstoremanager API는 Android 11 SDK 30 이상에서만 작동하며 Android 10 이상에서 작동하지 않습니다.
• 공유 데이터베이스 Blobstoremanager API는 Android 11 SDK 30 이상의 버전 만 실행되며 Android 10 이상 버전을 실행하지 않습니다. 현재 대부분의 장치에서 지원되지 않으므로 고려 사항이 포함되어 있지 않습니다.
• SAF 및 MEEDSTORE를 사용하여 공유 (외부) 스토리지에 액세스 :
• URI 받기 : 위의 교육 코드에서 사용 사례의 첫 세 가지 예만 파일 URI와 액세스 권한을 모두 얻습니다. 외부 파일에 액세스하는 것은 OnactivityResult로 동작을 캡처하여 소개됩니다.
• 다른 모든 예는이 3 가지 사용 사례 작업에서 얻은 URI를 처리하는 방법을 설명합니다. URI 파일을 모르는 경우 사용할 수 없습니다.
• 또한, 이전 Action_Get_Content 의도는 Action_Open_Document와 유사한 시스템 선택기를 호출하여 URI를 얻는 데 사용될 수 있습니다. 그러나 영구적 인 액세스를 제공하지 않습니다.
• Medicstore.files를 사용하면 미디어 스토리지의 파일의 URI도 검색 할 수 있지만“MedicalStore.files의 내용은 애플리케이션이 Android 10 이상을 타겟팅하는 응용 프로그램에서 사용 가능한 광범위한 스토리지를 사용하는지 여부에 따라이 주제의 내용에 포함되지 않았습니다. MediaStore.downloads 및 GetMediauri 클래스도 외부 저장 파일 URI에 액세스 할 수 있지만 SDK 29 이상만 지원합니다.
• e 케이스를 사용하지 않고 코드로 파일에서 작동하려면 "java.lang.documents/16874와 함께"java.lang.documents/16874)를 직접 URI (eG : content : //com.android.providers.downloads.documents/16874)를 직접 사용할 수 있습니다.
• 간단히 말하면, 스코프 스토리지의 외부 스토리지 파일 녹음 앱은 URI 병목 현상에 있습니다. 또한 SDK 29 이전에 이전 버전을 사용하여 시장에 나와있는 모든 Android 장치를 다루려면 SAF 사용 사례를 사용해야합니다. Android Developer Conference, Video, 공지 사항에서는 범위 스토리지로 전환하는 목적은 불필요한 권한을 제거하는 것이지만 실제로는 개발자와 사용자에게 반대가 발생했다고 주장합니다.
• 샘플 코드 (모두 SAF 튜토리얼 및 대부분의 MediaStore)는 SDK 레벨 1-24이며 Delphi 10.X 버전에서 지원됩니다.
• DELPHI 11은 SDK 레벨 29 VE 30 미디어 스토어 명령을 사용해야합니다.“다운로드”,“GetMediaUri”클래스, 썸네일을로드, 하위 덤프 네일, roadthumbnail, volume_external_primary, 미디어 파일에 대한 보류 상태를 발표하는 중간 상태를 업데이트, 기타 apps '미디어 파일 관리, CreateRiteRiteRiteRiteRiteRiteRiteRiteRiteException, is_pending upgrate' CreateFavoriteRequest”.
• 외부 스토리지에서 파일 공유의 경우 먼저 SAF 파일 선택기에 의해 파일의 URI를 얻은 다음 필요한 의도를 공유합니다. 외부 파일 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에서 작동하지 않기 때문에 시도 할 수 없습니다).
• Storage Access Framework SAF를 사용하려면 프로젝트에서 얻을 수있는 권한이 없습니다 -> 사용 권한을 사용하면 모두 닫을 수 있습니다. 프로젝트에는 "권한 이사 서비스. requestPermissions"와 같은 권한이 필요하지 않습니다. 샘플 프로젝트의 모든 권한.
• SAF로 파일에 액세스하려면 사용 사례 (Action_Create_Document, Action_Open_Document, Action_Open_Document_Tree)를 간단히 호출하는 것이 충분합니다. 그러나 기존 tfile.copy와 달리 각각은 시스템 피커 인터페이스를 엽니 다.
• Action_Create_Document는 "Save AS"와 유사한 인터페이스가있는 새 파일을 저장하는 데 사용되며 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 10 버전으로 내장되어 있기 때문에 필요한 Android 라이브러리 및 샘플 코드로 간단히 호출하여 SDK 30을 대상으로하는 Delphi 프로젝트에서 즉시 사용할 수 있습니다.
• 버전 11 이상의 오디오, 이미지, 비디오 파일 및 다운로드에 대한 글쓰기는 MediaStore로 제한되지 않습니다. 즉, 허가없이 사진을 저장할 수 있습니다.
• 미디어 컬렉션은 허가를 받아 읽을 수 있습니다.
• 이미지 위치 데이터에는 Access_Media_location이 필요합니다.
• PDF, 텍스트 등. "시스템 선택기"가 사용할 수있는 파일에 액세스합니다.
• 컬렉션 밖에서 읽고 쓰는 데 "시스템 피커"가 필요합니다.
• project-> 사용 권한 -> write_external_storage 옵션은 SDK 29에서 꺼야합니다. 읽기하려면 read_external_storage가 필요합니다.
• SDK 29 이상의 파일에 액세스하기 위해 제거 된 델파이 11 개의 라이브러리에서는 Medicstore URI 클래스를 사용할 수 없습니다. 따라서 위의 Medicstore 튜토리얼 코드의 대부분은 Java에서 Pascal로 가져 왔습니다.
• 런타임시 장치의 SDK 수준에 따라 (SDK> = 29)가 아닌 (SDK <28)에 대한 쓰기 권한을 요청해야합니다.
• AndroidManifest.template.xml 변경 :
<uses-sdk android:minSdkVersion="%minSdkVersion%" android:targetSdkVersion="30" />
android:requestLegacyExternalStorage="false">
• MediaStore는 쓰기 권한이 아닌 외부 스토리지에 대한 읽기 권한이 필요합니다. Project-> 사용 권한에 따라 Android 10 이전의 버전과의 호환성을 위해서는 write_external_storage 옵션을 제거하고 Manifest에 다음을 추가하십시오.
<%uses-permission%>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" />
• 외부 저장소에서 단일 명령 (tfile.copy)으로 파일 복사를 더 이상 수행 할 수 없습니다. 먼저 유스 케이스 (Action_Open_Document 또는 Action_Create_Document)를 호출합니다. 그런 다음 "HandleSeageActivity"및 "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 ;