Truy Cập Tài Liệu Và Các Tệp Khác Từ Bộ Nhớ Dùng Chung

Trên thiết bị chạy Android 4.4 (API cấp 19) trở lên, ứng dụng của bạn có thể tương tác với nhà cung cấp tài liệu, bao gồm cả ổ đĩa bộ nhớ ngoài và bộ nhớ dựa trên đám mây, sử dụng Khung truy cập bộ nhớ (Storage Access Framework). Khung này cho phép người dùng tương tác với công cụ chọn của hệ thống để chọn nhà cung cấp tài liệu và chọn tài liệu cụ thể cũng như các tệp khác để ứng dụng của bạn tạo, mở hoặc sửa đổi.

Do người dùng tham gia vào việc chọn các tệp hoặc thư mục mà ứng dụng của bạn có thể truy cập nên cơ chế này không yêu cầu cấp bất kỳ quyền hệ thống nào, đồng thời quyền kiểm soát và quyền riêng tư của người dùng được nâng cao. Ngoài ra, các tệp này, được lưu trữ bên ngoài thư mục dành riêng cho ứng dụng và bên ngoài kho lưu trữ nội dung nghe nhìn, vẫn tồn tại trên thiết bị sau khi ứng dụng của bạn bị gỡ cài đặt.

Quá trình sử dụng khung này gồm các bước sau:

  1. Ứng dụng gọi một ý định chứa thao tác liên quan đến bộ nhớ. Thao tác này tương ứng với một trường hợp sử dụng cụ thể mà khung đó sẽ cung cấp.
  2. Người dùng xem công cụ chọn của hệ thống, cho phép họ duyệt qua nhà cung cấp tài liệu và chọn một vị trí hoặc tài liệu nơi diễn ra thao tác liên quan đến bộ nhớ.
  3. Ứng dụng có quyền đọc và ghi vào URI đại diện cho vị trí hoặc tài liệu do người dùng chọn. Bằng cách sử dụng URI này, ứng dụng có thể thực hiện các thao tác trên vị trí đã chọn.
Lưu ý: Nếu ứng dụng của bạn truy cập vào các tệp nội dung nghe nhìn, hãy cân nhắc sử dụng công cụ chọn ảnh. Công cụ này cung cấp giao diện thuận tiện để truy cập vào ảnh và video.

Để hỗ trợ quyền truy cập vào tệp nội dung nghe nhìn trên các thiết bị chạy Android 9 (API cấp 28) trở xuống, hãy khai báo quyền READ_EXTERNAL_STORAGE và đặt maxSdkVersion thành 28.

Hướng dẫn này giải thích các trường hợp sử dụng mà khung hỗ trợ làm việc với các tệp và tài liệu khác. Hướng dẫn này cũng giải thích cách thực hiện các thao tác trên vị trí do người dùng chọn.

Các trường hợp sử dụng khi truy cập vào các tài liệu và tệp khác

Khung truy cập bộ nhớ hỗ trợ những trường hợp sử dụng sau đây khi truy cập vào các tệp và tài liệu khác.

Tạo tệp mới Thao tác theo ý định ACTION_CREATE_DOCUMENT cho phép người dùng lưu tệp vào một vị trí cụ thể. Mở một tài liệu hoặc tệp Thao tác theo ý định ACTION_OPEN_DOCUMENT cho phép người dùng chọn một tài liệu hoặc tệp cụ thể cần mở. Cấp quyền truy cập vào nội dung của một thư mục Thao tác theo ý định ACTION_OPEN_DOCUMENT_TREE, có trên Android 5.0 (API cấp 21) trở lên, cho phép người dùng chọn một thư mục cụ thể, cấp quyền cho ứng dụng của bạn truy cập vào tất cả các tệp và thư mục con trong thư mục đó.

Các mục sau đây cung cấp hướng dẫn về cách định cấu hình từng trường hợp sử dụng.

Tạo tệp mới

Sử dụng thao tác theo ý định ACTION_CREATE_DOCUMENT để tải bộ chọn tệp của hệ thống và cho phép người dùng chọn vị trí ghi nội dung tệp. Quy trình này tương tự như quy trình được sử dụng trong hộp thoại "lưu dưới dạng" mà các hệ điều hành khác sử dụng.

Lưu ý: ACTION_CREATE_DOCUMENT không thể ghi đè tệp hiện có. Nếu ứng dụng của bạn cố gắng lưu một tệp có cùng tên, thì hệ thống sẽ thêm một số trong dấu ngoặc đơn vào cuối tên tệp.

Ví dụ: Nếu ứng dụng của bạn cố gắng lưu một tệp có tên là confirmation.pdf vào thư mục đã chứa tệp có tên đó, thì hệ thống sẽ lưu tệp mới bằng tên là confirmation(1).pdf.

Khi định cấu hình ý định, hãy chỉ định tên và loại MIME của tệp, đồng thời tuỳ ý chỉ định URI của tệp hoặc thư mục mà bộ chọn tệp phải hiển thị khi tải lần đầu bằng cách sử dụng ý định bổ sung EXTRA_INITIAL_URI.

Đoạn mã sau đây cho biết cách tạo và gọi ý định tạo tệp:

Kotlin

// Request code for creating a PDF document. constvalCREATE_FILE=1 privatefuncreateFile(pickerInitialUri:Uri){ valintent=Intent(Intent.ACTION_CREATE_DOCUMENT).apply{ addCategory(Intent.CATEGORY_OPENABLE) type="application/pdf" putExtra(Intent.EXTRA_TITLE,"invoice.pdf") // Optionally, specify a URI for the directory that should be opened in // the system file picker before your app creates the document. putExtra(DocumentsContract.EXTRA_INITIAL_URI,pickerInitialUri) } startActivityForResult(intent,CREATE_FILE) }

Java

// Request code for creating a PDF document. privatestaticfinalintCREATE_FILE=1; privatevoidcreateFile(UripickerInitialUri){ Intentintent=newIntent(Intent.ACTION_CREATE_DOCUMENT); intent.addCategory(Intent.CATEGORY_OPENABLE); intent.setType("application/pdf"); intent.putExtra(Intent.EXTRA_TITLE,"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(DocumentsContract.EXTRA_INITIAL_URI,pickerInitialUri); startActivityForResult(intent,CREATE_FILE); }

Mở tệp

Ứng dụng của bạn có thể sử dụng tài liệu làm đơn vị lưu trữ, trong đó người dùng nhập dữ liệu mà họ có thể muốn chia sẻ với các ứng dụng ngang hàng hoặc nhập vào các tài liệu khác. Một số ví dụ: Người dùng mở tài liệu năng suất hoặc mở một cuốn sách được lưu dưới dạng tệp EPUB.

Trong những trường hợp này, hãy cho phép người dùng chọn tệp muốn mở bằng cách gọi ý định ACTION_OPEN_DOCUMENT. Thao tác gọi này sẽ mở ứng dụng bộ chọn tệp của hệ thống. Để chỉ hiển thị các loại tệp mà ứng dụng của bạn hỗ trợ, hãy chỉ định loại MIME. Ngoài ra, bạn cũng có thể tuỳ ý chỉ định URI của tệp mà bộ chọn tệp sẽ hiển thị khi tải lần đầu bằng cách sử dụng ý định bổ sung EXTRA_INITIAL_URI.

Đoạn mã sau đây cho biết cách tạo và gọi ý định mở tài liệu PDF:

Kotlin

// Request code for selecting a PDF document. constvalPICK_PDF_FILE=2 funopenFile(pickerInitialUri:Uri){ valintent=Intent(Intent.ACTION_OPEN_DOCUMENT).apply{ addCategory(Intent.CATEGORY_OPENABLE) type="application/pdf" // Optionally, specify a URI for the file that should appear in the // system file picker when it loads. putExtra(DocumentsContract.EXTRA_INITIAL_URI,pickerInitialUri) } startActivityForResult(intent,PICK_PDF_FILE) }

Java

// Request code for selecting a PDF document. privatestaticfinalintPICK_PDF_FILE=2; privatevoidopenFile(UripickerInitialUri){ Intentintent=newIntent(Intent.ACTION_OPEN_DOCUMENT); intent.addCategory(Intent.CATEGORY_OPENABLE); intent.setType("application/pdf"); // Optionally, specify a URI for the file that should appear in the // system file picker when it loads. intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI,pickerInitialUri); startActivityForResult(intent,PICK_PDF_FILE); }

Hạn chế quyền truy cập

Trên Android 11 (API cấp 30) trở lên, bạn không thể sử dụng thao tác theo ý định ACTION_OPEN_DOCUMENT để yêu cầu người dùng chọn các tệp riêng lẻ từ những thư mục sau:

  • Thư mục Android/data/ và tất cả các thư mục con.
  • Thư mục Android/obb/ và tất cả các thư mục con.

Cấp quyền truy cập vào nội dung của một thư mục

Lưu ý: Bạn có thể sử dụng thao tác theo ý định thảo luận trong mục này trên Android 5.0 (API cấp 21) trở lên.

Các ứng dụng quản lý tệp và tạo nội dung nghe nhìn thường quản lý các nhóm tệp trong hệ phân cấp của thư mục. Để cung cấp khả năng này trong ứng dụng của bạn, hãy sử dụng thao tác theo ý định ACTION_OPEN_DOCUMENT_TREE. Thao tác này cho phép người dùng cấp quyền truy cập vào toàn bộ cây thư mục, trừ một số trường hợp ngoại lệ bắt đầu trong Android 11 (API cấp 30). Sau đó, ứng dụng của bạn có thể truy cập mọi tệp trong thư mục đã chọn và bất kỳ thư mục con nào trong đó.

Khi sử dụng ACTION_OPEN_DOCUMENT_TREE, ứng dụng của bạn sẽ chỉ có quyền truy cập vào các tệp trong thư mục mà người dùng chọn. Bạn không có quyền truy cập vào các tệp của những ứng dụng khác nằm bên ngoài thư mục do người dùng chọn này. Quyền truy cập do người dùng kiểm soát này cho phép người dùng chọn chính xác nội dung mà họ có thể chia sẻ thoải mái với ứng dụng của bạn.

Nếu muốn, bạn có thể chỉ định URI của thư mục mà bộ chọn tệp sẽ hiển thị khi tải lần đầu bằng cách sử dụng ý định bổ sung EXTRA_INITIAL_URI.

Đoạn mã sau đây cho biết cách tạo và gọi ý định mở thư mục:

Kotlin

funopenDirectory(pickerInitialUri:Uri){ // Choose a directory using the system's file picker. valintent=Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply{ // Optionally, specify a URI for the directory that should be opened in // the system file picker when it loads. putExtra(DocumentsContract.EXTRA_INITIAL_URI,pickerInitialUri) } startActivityForResult(intent,your-request-code) }

Java

publicvoidopenDirectory(UriuriToLoad){ // Choose a directory using the system's file picker. Intentintent=newIntent(Intent.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(DocumentsContract.EXTRA_INITIAL_URI,uriToLoad); startActivityForResult(intent,your-request-code); } Thận trọng: Nếu bạn lặp lại qua một số lượng lớn các tệp trong thư mục mà bạn truy cập bằng ACTION_OPEN_DOCUMENT_TREE, thì hiệu suất của ứng dụng có thể bị giảm.

Hạn chế quyền truy cập

Trên Android 11 (API cấp 30) trở lên, bạn không thể sử dụng thao tác theo ý định ACTION_OPEN_DOCUMENT_TREE để yêu cầu quyền truy cập vào các thư mục sau:

  • Thư mục gốc của phương tiện bộ nhớ trong.
  • Thư mục gốc của mỗi phương tiện ghi thẻ SD mà nhà sản xuất thiết bị cho là đáng tin cậy, bất kể thẻ đó là loại mô phỏng hay có thể tháo rời. Phương tiện ghi đáng tin cậy là phương tiện ghi mà hầu như lúc nào ứng dụng cũng có thể truy cập thành công.
  • Thư mục Download.

Hơn nữa, trên Android 11 (API cấp 30) trở lên, bạn không được sử dụng thao tác theo ý định ACTION_OPEN_DOCUMENT_TREE để yêu cầu người dùng chọn các tệp riêng lẻ từ những thư mục sau:

  • Thư mục Android/data/ và tất cả các thư mục con.
  • Thư mục Android/obb/ và tất cả các thư mục con.

Thực hiện các thao tác đối với vị trí đã chọn

Sau khi người dùng chọn một tệp hoặc thư mục bằng bộ chọn tệp của hệ thống, bạn có thể truy xuất URI của mục đã chọn bằng mã sau trong onActivityResult():

Kotlin

overridefunonActivityResult( requestCode:Int,resultCode:Int,resultData:Intent?){ if(requestCode==your-request-code && resultCode==Activity.RESULT_OK){ // The result data contains a URI for the document or directory that // the user selected. resultData?.data?.also{uri-> // Perform operations on the document using its URI. } } }

Java

@Override publicvoidonActivityResult(intrequestCode,intresultCode, IntentresultData){ if(requestCode==your-request-code && resultCode==Activity.RESULT_OK){ // The result data contains a URI for the document or directory that // the user selected. Uriuri=null; if(resultData!=null){ uri=resultData.getData(); // Perform operations on the document using its URI. } } }

Bằng cách tham chiếu đến URI của mục đã chọn, ứng dụng của bạn có thể thực hiện một số thao tác với mục đó. Ví dụ: Bạn có thể truy cập vào siêu dữ liệu của mục, chỉnh sửa mục tại chỗ và xoá mục.

Các phần sau đây cho biết cách hoàn tất thao tác trên các tệp mà người dùng chọn.

Xác định những thao tác mà nhà cung cấp hỗ trợ

Các nhà cung cấp nội dung khác nhau cho phép thực hiện các thao tác khác nhau trên tài liệu, chẳng hạn như sao chép tài liệu hoặc xem hình thu nhỏ của tài liệu. Để xác định những thao tác mà một nhà cung cấp cụ thể hỗ trợ, hãy kiểm tra giá trị của Document.COLUMN_FLAGS. Khi đó, giao diện người dùng của ứng dụng chỉ có thể hiển thị các tuỳ chọn được nhà cung cấp hỗ trợ.

Quyền có tác dụng lâu dài

Khi ứng dụng của bạn mở một tệp để đọc hoặc ghi, hệ thống sẽ cấp cho ứng dụng quyền URI của tệp đó. Quyền này sẽ có hiệu lực cho đến khi thiết bị của người dùng khởi động lại. Tuy nhiên, giả sử rằng ứng dụng của bạn là ứng dụng chỉnh sửa hình ảnh và bạn muốn người dùng có thể truy cập vào 5 hình ảnh mà họ chỉnh sửa gần đây nhất, ngay trên ứng dụng của bạn. Nếu thiết bị của người dùng khởi động lại, bạn phải đưa người dùng quay lại bộ chọn của hệ thống để tìm tệp.

Để lưu giữ quyền truy cập vào các tệp trong các lần thiết bị khởi động lại và tạo trải nghiệm người dùng tốt hơn, ứng dụng của bạn có thể "lấy" (take) quyền URI lâu dài mà hệ thống cung cấp, như trong đoạn mã sau đây:

Kotlin

valcontentResolver=applicationContext.contentResolver valtakeFlags:Int=Intent.FLAG_GRANT_READ_URI_PERMISSIONor Intent.FLAG_GRANT_WRITE_URI_PERMISSION // Check for the freshest data. contentResolver.takePersistableUriPermission(uri,takeFlags)

Java

finalinttakeFlags=intent.getFlags() & (Intent.FLAG_GRANT_READ_URI_PERMISSION |Intent.FLAG_GRANT_WRITE_URI_PERMISSION); // Check for the freshest data. getContentResolver().takePersistableUriPermission(uri,takeFlags); Thận trọng: Ngay cả sau khi gọi takePersistableUriPermission(), ứng dụng của bạn vẫn không giữ lại quyền truy cập vào URI nếu tài liệu liên quan đã bị di chuyển hoặc bị xoá. Trong những trường hợp đó, bạn cần yêu cầu quyền này một lần nữa để lấy lại quyền truy cập vào URI.

Kiểm tra siêu dữ liệu trong tài liệu

Khi có URI cho một tài liệu, bạn sẽ có quyền truy cập vào siêu dữ liệu của tài liệu đó. Đoạn mã này lấy siêu dữ liệu cho tài liệu do URI chỉ định và ghi lại siêu dữ liệu đó:

Kotlin

valcontentResolver=applicationContext.contentResolver fundumpImageMetaData(uri:Uri){ // 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. valcursor:Cursor? =contentResolver.query( uri,null,null,null,null,null) cursor?.use{ // moveToFirst() returns false if the cursor has 0 rows. Very handy for // "if there's anything to look at, look at it" conditionals. if(it.moveToFirst()){ // Note it's called "Display Name". This is // provider-specific, and might not necessarily be the file name. valdisplayName:String= it.getString(it.getColumnIndex(OpenableColumns.DISPLAY_NAME)) Log.i(TAG,"Display Name: $displayName") valsizeIndex:Int=it.getColumnIndex(OpenableColumns.SIZE) // If the size is unknown, the value stored is null. But because an // int can't be null, the behavior is implementation-specific, // and unpredictable. So as // a rule, check if it's null before assigning to an int. This will // happen often: The storage API allows for remote files, whose // size might not be locally known. valsize:String=if(!it.isNull(sizeIndex)){ // Technically the column stores an int, but cursor.getString() // will do the conversion automatically. it.getString(sizeIndex) }else{ "Unknown" } Log.i(TAG,"Size: $size") } } }

Java

publicvoiddumpImageMetaData(Uriuri){ // 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. Cursorcursor=getActivity().getContentResolver() .query(uri,null,null,null,null,null); 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!=null && cursor.moveToFirst()){ // Note it's called "Display Name". This is // provider-specific, and might not necessarily be the file name. StringdisplayName=cursor.getString( cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)); Log.i(TAG,"Display Name: "+displayName); intsizeIndex=cursor.getColumnIndex(OpenableColumns.SIZE); // If the size is unknown, the value stored is null. But because an // int can't be null, the behavior is implementation-specific, // and unpredictable. So as // a rule, check if it's null before assigning to an int. This will // happen often: The storage API allows for remote files, whose // size might not be locally known. Stringsize=null; if(!cursor.isNull(sizeIndex)){ // Technically the column stores an int, but cursor.getString() // will do the conversion automatically. size=cursor.getString(sizeIndex); }else{ size="Unknown"; } Log.i(TAG,"Size: "+size); } }finally{ cursor.close(); } }

Mở tài liệu

Bằng cách tham chiếu đến URI của tài liệu, bạn có thể mở một tài liệu để xử lý thêm. Phần này trình bày các ví dụ về cách mở bitmap và luồng dữ liệu đầu vào.

Bitmap

Đoạn mã sau đây cho biết cách mở tệp Bitmap dựa trên URI:

Kotlin

valcontentResolver=applicationContext.contentResolver @Throws(IOException::class) privatefungetBitmapFromUri(uri:Uri):Bitmap{ valparcelFileDescriptor:ParcelFileDescriptor= contentResolver.openFileDescriptor(uri,"r") valfileDescriptor:FileDescriptor=parcelFileDescriptor.fileDescriptor valimage:Bitmap=BitmapFactory.decodeFileDescriptor(fileDescriptor) parcelFileDescriptor.close() returnimage }

Java

privateBitmapgetBitmapFromUri(Uriuri)throwsIOException{ ParcelFileDescriptorparcelFileDescriptor= getContentResolver().openFileDescriptor(uri,"r"); FileDescriptorfileDescriptor=parcelFileDescriptor.getFileDescriptor(); Bitmapimage=BitmapFactory.decodeFileDescriptor(fileDescriptor); parcelFileDescriptor.close(); returnimage; } Lưu ý: Bạn nên hoàn thành thao tác này trong luồng nền, chứ không phải trong luồng giao diện người dùng.

Sau khi mở bitmap, bạn có thể hiển thị bitmap trong ImageView.

Luồng dữ liệu đầu vào

Đoạn mã sau đây cho biết cách mở đối tượng InputStream dựa trên URI. Trong đoạn mã này, các dòng của tệp sẽ được đọc thành chuỗi:

Kotlin

valcontentResolver=applicationContext.contentResolver @Throws(IOException::class) privatefunreadTextFromUri(uri:Uri):String{ valstringBuilder=StringBuilder() contentResolver.openInputStream(uri)?.use{inputStream-> BufferedReader(InputStreamReader(inputStream)).use{reader-> varline:String?=reader.readLine() while(line!=null){ stringBuilder.append(line) line=reader.readLine() } } } returnstringBuilder.toString() }

Java

privateStringreadTextFromUri(Uriuri)throwsIOException{ StringBuilderstringBuilder=newStringBuilder(); try(InputStreaminputStream= getContentResolver().openInputStream(uri); BufferedReaderreader=newBufferedReader( newInputStreamReader(Objects.requireNonNull(inputStream)))){ Stringline; while((line=reader.readLine())!=null){ stringBuilder.append(line); } } returnstringBuilder.toString(); }

Chỉnh sửa tài liệu

Bạn có thể sử dụng Khung truy cập bộ nhớ để chỉnh sửa tài liệu văn bản tại chỗ.

Lưu ý: Phương thức canWrite() của lớp DocumentFile không nhất thiết cho biết ứng dụng của bạn có thể chỉnh sửa tài liệu. Lý do là phương thức này trả về true nếu Document.COLUMN_FLAGS chứa FLAG_SUPPORTS_DELETE hoặc FLAG_SUPPORTS_WRITE. Để xác định xem ứng dụng của bạn có thể chỉnh sửa một tài liệu nhất định hay không, hãy truy vấn trực tiếp giá trị của FLAG_SUPPORTS_WRITE.

Đoạn mã sau đây sẽ ghi đè lên nội dung của tài liệu được trình bày bằng URI nhất định:

Kotlin

valcontentResolver=applicationContext.contentResolver privatefunalterDocument(uri:Uri){ try{ contentResolver.openFileDescriptor(uri,"w")?.use{ FileOutputStream(it.fileDescriptor).use{ it.write( ("Overwritten at ${System.currentTimeMillis()}\n") .toByteArray() ) } } }catch(e:FileNotFoundException){ e.printStackTrace() }catch(e:IOException){ e.printStackTrace() } }

Java

privatevoidalterDocument(Uriuri){ try{ ParcelFileDescriptorpfd=getActivity().getContentResolver(). openFileDescriptor(uri,"w"); FileOutputStreamfileOutputStream= newFileOutputStream(pfd.getFileDescriptor()); fileOutputStream.write(("Overwritten at "+System.currentTimeMillis()+ "\n").getBytes()); // Let the document provider know you're done by closing the stream. fileOutputStream.close(); pfd.close(); }catch(FileNotFoundExceptione){ e.printStackTrace(); }catch(IOExceptione){ e.printStackTrace(); } }

Xoá tài liệu

Nếu bạn có URI của tài liệu và Document.COLUMN_FLAGS của tài liệu đó có chứa SUPPORTS_DELETE, thì bạn có thể xoá tài liệu. Ví dụ:

Kotlin

DocumentsContract.deleteDocument(applicationContext.contentResolver,uri)

Java

DocumentsContract.deleteDocument(applicationContext.contentResolver,uri);

Truy xuất URI phương tiện tương đương

Phương thức getMediaUri() cung cấp một URI kho phương tiện tương đương với URI nhất định của nhà cung cấp tài liệu. 2 URI này tham chiếu đến cùng một mục cơ bản. Khi sử dụng URI kho nội dung nghe nhìn, bạn có thể truy cập các tệp nội dung nghe nhìn từ bộ nhớ dùng chung dễ dàng hơn.

Lưu ý: Phương thức này không cấp quyền mới. Ứng dụng của bạn phải có sẵn các quyền cần thiết để truy cập vào một URI nhất định của nhà cung cấp tài liệu, chẳng hạn như bằng cách mở tài liệu.

Phương thức getMediaUri() hỗ trợ các URI ExternalStorageProvider. Trên Android 12 (API cấp 31) trở lên, phương thức này cũng hỗ trợ URI MediaDocumentsProvider.

Mở tệp ảo

Trên Android 7.0 (API cấp 25) trở lên, ứng dụng của bạn có thể sử dụng các tệp ảo mà Khung truy cập bộ nhớ cung cấp. Mặc dù tệp ảo không chứa cách biểu diễn nhị phân, nhưng ứng dụng của bạn có thể mở nội dung trong đó bằng cách chuyển đổi những tệp ảo này thành loại tệp khác hoặc bằng cách xem các tệp đó thông qua thao tác theo ý định ACTION_VIEW.

Để mở các tệp ảo, ứng dụng máy khách của bạn cần có logic đặc biệt nhằm xử lý các tệp đó. Nếu muốn xem cách biểu diễn byte của tệp, chẳng hạn như xem trước tệp, bạn cần yêu cầu loại MIME thay thế từ nhà cung cấp tài liệu.

Lưu ý: Không dùng danh mục CATEGORY_OPENABLE khi tạo ý định chứa thao tác ACTION_OPEN_DOCUMENT hoặc ACTION_OPEN_DOCUMENT_TREE, bởi vì ứng dụng không thể mở trực tiếp một tệp ảo bằng cách sử dụng phương thức openInputStream().

Sau khi người dùng lựa chọn, hãy sử dụng URI trong dữ liệu kết quả để xác định tệp có phải là tệp ảo hay không, như trong đoạn mã sau:

Kotlin

privatefunisVirtualFile(uri:Uri):Boolean{ if(!DocumentsContract.isDocumentUri(this,uri)){ returnfalse } valcursor:Cursor? =contentResolver.query( uri, arrayOf(DocumentsContract.Document.COLUMN_FLAGS), null, null, null ) valflags:Int=cursor?.use{ if(cursor.moveToFirst()){ cursor.getInt(0) }else{ 0 } }?:0 returnflagsandDocumentsContract.Document.FLAG_VIRTUAL_DOCUMENT!=0 }

Java

privatebooleanisVirtualFile(Uriuri){ if(!DocumentsContract.isDocumentUri(this,uri)){ returnfalse; } Cursorcursor=getContentResolver().query( uri, newString[]{DocumentsContract.Document.COLUMN_FLAGS}, null,null,null); intflags=0; if(cursor.moveToFirst()){ flags=cursor.getInt(0); } cursor.close(); return(flags & DocumentsContract.Document.FLAG_VIRTUAL_DOCUMENT)!=0; }

Sau khi xác minh rằng tài liệu là tệp ảo, bạn có thể chuyển đổi tệp đó thành một loại MIME thay thế, chẳng hạn như "image/png". Đoạn mã sau đây cho biết cách kiểm tra xem tệp ảo có thể được biểu thị dưới dạng hình ảnh hay không và nếu có, thì bạn sẽ nhận được luồng dữ liệu đầu vào từ tệp ảo:

Kotlin

@Throws(IOException::class) privatefungetInputStreamForVirtualFile( uri:Uri,mimeTypeFilter:String):InputStream{ valopenableMimeTypes:Array<String>? = contentResolver.getStreamTypes(uri,mimeTypeFilter) returnif(openableMimeTypes?.isNotEmpty()==true){ contentResolver .openTypedAssetFileDescriptor(uri,openableMimeTypes[0],null) .createInputStream() }else{ throwFileNotFoundException() } }

Java

privateInputStreamgetInputStreamForVirtualFile(Uriuri,StringmimeTypeFilter) throwsIOException{ ContentResolverresolver=getContentResolver(); String[]openableMimeTypes=resolver.getStreamTypes(uri,mimeTypeFilter); if(openableMimeTypes==null|| openableMimeTypes.length < 1){ thrownewFileNotFoundException(); } returnresolver .openTypedAssetFileDescriptor(uri,openableMimeTypes[0],null) .createInputStream(); }

Tài nguyên khác

Để biết thêm thông tin về cách lưu trữ và truy cập tài liệu cũng như các tệp khác, hãy tham khảo các tài nguyên sau.

Mẫu

  • ActionOpenDocument, có sẵn trên GitHub.
  • ActionOpenDocumentTree, có sẵn trên GitHub.

Video

  • Chuẩn bị cho Bộ nhớ có giới hạn (Hội nghị Nhà phát triển Android năm 2019)

Từ khóa » Sửa File Hệ Thống Android