במכשירים שבהם פועלת גרסת Android 4.4 (רמת API 19) ואילך, האפליקציה יכולה לקיים אינטראקציה באמצעות ספק מסמכים, כולל נפחי אחסון חיצוניים ואחסון מבוסס-ענן, באמצעות Access Framework. המסגרת הזו מאפשרת למשתמשים ליצור אינטראקציה עם בורר המערכת כדי לבחור ספק מסמכים ולבחור מסמכים ספציפיים וקבצים אחרים עבור כדי ליצור, לפתוח או לשנות את האפליקציה.
המשתמש מעורב בבחירת הקבצים או הספריות שאפליקצייתכם יכולה לגשת אליהם, ולכן המנגנון הזה לא דורש הרשאות מערכת, והשליטה של המשתמש בפרטיות משופר. בנוסף, הקבצים האלה, שמאוחסנים מחוץ בספרייה הספציפית לאפליקציה ומחוץ לחנות המדיה, נשארים במכשיר אחרי הסרת האפליקציה.
כדי להשתמש במסגרת, מבצעים את השלבים הבאים:
- אפליקציה מפעילה Intent שכולל פעולה שקשורה לאחסון. הפעולה הזו תואמת לתרחיש לדוגמה ספציפי שזמין באמצעות המסגרת.
- המשתמש רואה בורר מערכת שמאפשר לו לעיין בספק מסמכים ולבחור מיקום או מסמך שבהם תתבצע הפעולה שקשורה לאחסון.
- האפליקציה מקבלת גישת קריאה וכתיבה ב-URI שמייצג את המיקום או המסמך שנבחרו. באמצעות ה-URI הזה, האפליקציה יכולה לבצע פעולות במיקום שנבחר.
כדי לתמוך בגישה לקובצי מדיה במכשירים עם Android 9 (רמת API 28) ואילך, צריך להצהיר על ההרשאה READ_EXTERNAL_STORAGE
ולהגדיר את maxSdkVersion
לערך 28
.
מדריך זה מסביר את התרחישים לדוגמה השונים שבהם ה-framework תומך עבודה עם קבצים ומסמכים אחרים. בנוסף, מוסבר איך לבצע פעולות במיקום שבחר המשתמש.
תרחישים לדוגמה של גישה למסמכים ולקבצים אחרים
ה-Storage Access Framework תומך בתרחישי השימוש הבאים לגישה לקבצים ולמסמכים אחרים.
- יצירת קובץ חדש
-
ACTION_CREATE_DOCUMENT
פעולת Intent מאפשרת למשתמשים לשמור קובץ במיקום ספציפי. - פתיחת מסמך או קובץ
-
ACTION_OPEN_DOCUMENT
פעולת Intent מאפשרת למשתמשים לבחור מסמך או קובץ ספציפיים לפתיחה. - איך מעניקים גישה לתוכן של ספרייה
- פעולת הכוונה
ACTION_OPEN_DOCUMENT_TREE
זמינה ב-Android 5.0 (רמת API 21) ואילך, ומאפשרת למשתמשים לבחור ספרייה ספציפית, וכך מעניקה לאפליקציה גישה לכל הקבצים והספריות המשניות בספרייה הזו.
בקטעים הבאים מפורטות הנחיות להגדרה של כל תרחיש לדוגמה.
יצירת קובץ חדש
משתמשים ב
ACTION_CREATE_DOCUMENT
פעולת Intent כדי לטעון את בוחר קובצי המערכת ולאפשר למשתמש לבחור
המיקום שבו צריך לכתוב את תוכן הקובץ. התהליך הזה דומה לתהליך
אחד שנעשה בו שימוש בתפריט "שמירה בשם" תיבות דו-שיח שבהן משתמשות מערכות הפעלה אחרות.
הערה: ACTION_CREATE_DOCUMENT
לא יכול להחליף
קיים. אם האפליקציה תנסה לשמור קובץ עם אותו שם, המערכת תוסיף מספר בסוגריים בסוף שם הקובץ.
לדוגמה, אם האפליקציה מנסה לשמור קובץ בשם
confirmation.pdf
בספרייה שכבר יש בה קובץ
השם, המערכת שומרת את הקובץ החדש בשם
confirmation(1).pdf
.
כשמגדירים את ה-Intent, מציינים את שם הקובץ ואת סוג ה-MIME, וכן
לציין את ה-URI של הקובץ או הספרייה שבוחר הקבצים
מוצג בטעינה הראשונה באמצעות
EXTRA_INITIAL_URI
כוונה נוספת.
בקטע הקוד הבא מוסבר איך ליצור ולהפעיל את הכוונה יצירת קובץ:
// Request code for creating a PDF document. const val CREATE_FILE = 1 private fun createFile(pickerInitialUri: Uri) { val intent = 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) }
// Request code for creating a PDF document. private static final int CREATE_FILE = 1; private void createFile(Uri pickerInitialUri) { Intent intent = new Intent(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); }
פתיחת קובץ
האפליקציה שלך עשויה להשתמש במסמכים כיחידת האחסון שבה משתמשים מזינים נתונים שאולי ירצו לשתף עם עמיתים או לייבא למסמכים אחרים. כמה דוגמאות: משתמש פותח מסמך פרודוקטיביות או פותח ספר שנשמר כקובץ EPUB.
במקרים כאלה, המשתמשים יכולים לבחור את הקובץ לפתיחה על ידי הפעלת
ACTION_OPEN_DOCUMENT
Intent, שפותח את האפליקציה של המערכת לבחירת קבצים. כדי להציג רק את הסוגים של
קבצים שהאפליקציה שלך תומכת בהם, צריך לציין סוג MIME. אפשר גם לציין את ה-URI של הקובץ שבורר הקבצים אמור להציג בטעינה הראשונה שלו, באמצעות הפרמטר הנוסף של הכוונה EXTRA_INITIAL_URI
.
בקטע הקוד הבא מוסבר איך ליצור ולהפעיל את הכוונה לפתוח מסמך PDF:
// Request code for selecting a PDF document. const val PICK_PDF_FILE = 2 fun openFile(pickerInitialUri: Uri) { val intent = 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) }
// Request code for selecting a PDF document. private static final int PICK_PDF_FILE = 2; private void openFile(Uri pickerInitialUri) { Intent intent = new Intent(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); }
הגבלות גישה
ב-Android 11 (רמת API 30) ואילך, אי אפשר להשתמש בפעולת הכוונה ACTION_OPEN_DOCUMENT
כדי לבקש מהמשתמש לבחור קבצים ספציפיים מהספריות הבאות:
- הספרייה
Android/data/
וכל ספריות המשנה. - הספרייה
Android/obb/
וכל ספריות המשנה.
מתן גישה לתוכן של ספרייה
אפליקציות לניהול קבצים וליצירת מדיה מנהלות בדרך כלל קבוצות של קבצים
בהיררכיית הספריות. כדי לספק את היכולת הזו באפליקציה, צריך להשתמש
ACTION_OPEN_DOCUMENT_TREE
פעולת Intent, שמאפשרת למשתמש להעניק גישה לספרייה שלמה
עץ, למעט כמה חריגים שמתחילים ב-Android 11 (רמת API 30). לאחר מכן, לאפליקציה תהיה גישה לכל קובץ בספרייה שנבחרה ובכל אחת מספריות המשנה שלה.
בזמן השימוש ב-ACTION_OPEN_DOCUMENT_TREE
, האפליקציה מקבלת גישה רק אל
קבצים בספרייה שהמשתמש בוחר. אין לך גישה לפריטים אחרים
'אפליקציות' קבצים שנמצאים מחוץ לספרייה שנבחרה על ידי המשתמש. הגישה הזו, שבה המשתמש קובע מה הוא רוצה לשתף עם האפליקציה, מאפשרת למשתמשים לבחור בדיוק איזה תוכן הם רוצים לשתף.
אפשר גם לציין את ה-URI של הספרייה שצריך לבחור בה
מוצג בטעינה הראשונה באמצעות
EXTRA_INITIAL_URI
כוונה נוספת.
בקטע הקוד הבא מוצג איך יוצרים את ה-intent לפתיח ספרייה ומפעילים אותו:
fun openDirectory(pickerInitialUri: Uri) { // Choose a directory using the system's file picker. val intent = 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 ) }
public void openDirectory(Uri uriToLoad) { // Choose a directory using the system's file picker. Intent intent = new Intent(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 ); }
הגבלות גישה
ב-Android 11 (רמת API 30) ואילך, אי אפשר להשתמש בפעולת הכוונה ACTION_OPEN_DOCUMENT_TREE
כדי לבקש גישה לספריות הבאות:
- ספריית הבסיס של נפח האחסון הפנימי.
- תיקיית השורש של כל נפח אחסון של כרטיס SD שיצרן המכשיר נחשב מהימן, גם אם הכרטיס עבר אמולציה וגם אם ניתנת להסרה. נפח אמין הוא נפח שאפליקציה יכולה לגשת אליו בהצלחה רוב הזמן.
- הספרייה
Download
.
בנוסף, ב-Android מגרסה 11 (API ברמה 30) ואילך, אי אפשר להשתמש
פעולת Intent אחת (ACTION_OPEN_DOCUMENT_TREE
) כדי לבקש מהמשתמש לבחור
קבצים נפרדים מהספריות הבאות:
- הספרייה
Android/data/
וכל ספריות המשנה. - הספרייה
Android/obb/
וכל ספריות המשנה.
ביצוע פעולות במיקום שנבחר
אחרי שהמשתמש בוחר קובץ או ספרייה באמצעות בורר הקבצים של המערכת, אפשר לאחזר את ה-URI של הפריט שנבחר באמצעות הקוד הבא ב-onActivityResult()
:
override fun onActivityResult( 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. } } }
@Override public void onActivityResult(int requestCode, int resultCode, Intent resultData) { if (requestCode ==your-request-code && resultCode == Activity.RESULT_OK) { // The result data contains a URI for the document or directory that // the user selected. Uri uri = null; if (resultData != null) { uri = resultData.getData(); // Perform operations on the document using its URI. } } }
קבלת הפניה ל-URI של הפריט שנבחר מאפשרת לאפליקציה לבצע מספר פעולות פעולות על הפריט. לדוגמה, תוכלו לגשת למטא-נתונים של הפריט, לערוך את הפריט במקום ולמחוק אותו.
בקטעים הבאים מוסבר איך לבצע פעולות על הקבצים שהמשתמש בחר.
קביעת הפעולות שנתמכות על ידי ספק
ספקי תוכן שונים מאפשרים לבצע פעולות שונות
מסמכים — כמו העתקת המסמך או צפייה בתמונה הממוזערת של מסמך. כדי לבדוק אילו פעולות ספק מסוים תומך בהן, בודקים את הערך של Document.COLUMN_FLAGS
.
לאחר מכן, ממשק המשתמש של האפליקציה יכול להציג רק את האפשרויות שהספק תומך בהן.
הרשאות מתמידות
כשהאפליקציה פותחת קובץ לקריאה או לכתיבה, המערכת מעניקה לאפליקציה הרשאת URI לקובץ הזה, שתוקפה נמשך עד שהמכשיר של המשתמש מופעל מחדש. עם זאת, נניח שהאפליקציה שלכם היא אפליקציית עריכת תמונות, ואתם רוצים שהמשתמשים יוכלו לגשת ל-5 התמונות האחרונות שערכו ישירות מהאפליקציה. אם המכשיר של המשתמש הופעל מחדש, תצטרכו לשלוח אותו חזרה לבורר המערכת כדי למצוא את הקבצים.
כדי לשמור על הגישה לקבצים אחרי הפעלות מחדש של המכשיר וליצור חוויית משתמש טובה יותר, האפליקציה יכולה 'לקבל' את הרשאת ה-URI הקבועה שהמערכת מציעה, כפי שמוצג בקטע הקוד הבא:
val contentResolver = applicationContext.contentResolver val takeFlags: Int = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION // Check for the freshest data. contentResolver.takePersistableUriPermission(uri, takeFlags)
final int takeFlags = intent.getFlags() & (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); // Check for the freshest data. getContentResolver().takePersistableUriPermission(uri, takeFlags);
בדיקת מטא-נתונים של מסמכים
כשיש לכם את ה-URI של מסמך, אתם מקבלים גישה למטא-נתונים שלו. הזה קטע הקוד מאחזר את המטא-נתונים של מסמך שצוין על ידי ה-URI, ומתעד אותו ביומן:
val contentResolver = applicationContext.contentResolver fun dumpImageMetaData(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. val cursor: 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. val displayName: String = it.getString(it.getColumnIndex(OpenableColumns.DISPLAY_NAME)) Log.i(TAG, "Display Name: $displayName") val sizeIndex: 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. val size: 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") } } }
public void dumpImageMetaData(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. Cursor cursor = 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. String displayName = cursor.getString( cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)); Log.i(TAG, "Display Name: " + displayName); int sizeIndex = 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. String size = 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(); } }
פתיחת מסמך
הפניה ל-URI של מסמך מאפשרת לפתוח מסמך להמשך בעיבוד. בקטע הזה מוצגות דוגמאות לפתיחת קובץ bitmap וסטרימינג של קלט.
מפת סיביות (bitmap)
קטע הקוד הבא מראה איך לפתוח קובץ Bitmap
לפי ה-URI שלו:
val contentResolver = applicationContext.contentResolver @Throws(IOException::class) private fun getBitmapFromUri(uri: Uri): Bitmap { val parcelFileDescriptor: ParcelFileDescriptor = contentResolver.openFileDescriptor(uri, "r") val fileDescriptor: FileDescriptor = parcelFileDescriptor.fileDescriptor val image: Bitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor) parcelFileDescriptor.close() return image }
private Bitmap getBitmapFromUri(Uri uri) throws IOException { ParcelFileDescriptor parcelFileDescriptor = getContentResolver().openFileDescriptor(uri, "r"); FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor(); Bitmap image = BitmapFactory.decodeFileDescriptor(fileDescriptor); parcelFileDescriptor.close(); return image; }
אחרי שפותחים את קובץ ה-bitmap, אפשר להציג אותו ב-ImageView
.
מקור קלט
קטע הקוד הבא מראה איך לפתוח אובייקט קלטStream בהינתן URI. בקטע הקוד הזה, השורות של הקובץ נקראות למחרוזת:
val contentResolver = applicationContext.contentResolver @Throws(IOException::class) private fun readTextFromUri(uri: Uri): String { val stringBuilder = StringBuilder() contentResolver.openInputStream(uri)?.use { inputStream -> BufferedReader(InputStreamReader(inputStream)).use { reader -> var line: String? = reader.readLine() while (line != null) { stringBuilder.append(line) line = reader.readLine() } } } return stringBuilder.toString() }
private String readTextFromUri(Uri uri) throws IOException { StringBuilder stringBuilder = new StringBuilder(); try (InputStream inputStream = getContentResolver().openInputStream(uri); BufferedReader reader = new BufferedReader( new InputStreamReader(Objects.requireNonNull(inputStream)))) { String line; while ((line = reader.readLine()) != null) { stringBuilder.append(line); } } return stringBuilder.toString(); }
עריכת מסמך
אפשר להשתמש ב-Storage Access Framework כדי לערוך מסמך טקסט במקום.
קטע הקוד הבא מחליף את התוכן של המסמך שמיוצג לפי ה-URI הנתון:
val contentResolver = applicationContext.contentResolver private fun alterDocument(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() } }
private void alterDocument(Uri uri) { try { ParcelFileDescriptor pfd = getActivity().getContentResolver(). openFileDescriptor(uri, "w"); FileOutputStream fileOutputStream = new FileOutputStream(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 (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } }
מחיקת מסמך
אם יש לכם את ה-URI של מסמך, וDocument.COLUMN_FLAGS
של המסמך מכיל את SUPPORTS_DELETE
, תוכלו למחוק את המסמך. לדוגמה:
DocumentsContract.deleteDocument(applicationContext.contentResolver, uri)
DocumentsContract.deleteDocument(applicationContext.contentResolver, uri);
אחזור URI מקביל של מדיה
getMediaUri()
ה-method method מספקת URI של מאגר מדיה שזהה למסמכים הנתונים
ה-URI של הספק. שני מזהי ה-URI מתייחסים לאותו פריט בסיסי. באמצעות ה-URI של מאגר המדיה, קל יותר לגשת לקובצי מדיה מנפח אחסון משותף.
השיטה getMediaUri()
תומכת ב-URI מסוג ExternalStorageProvider
. במצב מופעל
Android 12 (רמת API 31) ואילך, השיטה תומכת גם
MediaDocumentsProvider
מזהי URI.
פתיחת קובץ וירטואלי
בגרסה Android 7.0 (רמת API 25) ואילך, האפליקציה יכולה להשתמש בקבצים וירטואליים
ש-Storage Access Framework מאפשר. למרות שקבצים וירטואליים
אין ייצוג בינארי, האפליקציה שלך יכולה לפתוח את התוכן שלהם על ידי אילוץ
לקובץ מסוג אחר או על ידי הצגת הקבצים האלו באמצעות
ACTION_VIEW
Intent
פעולה.
כדי לפתוח קבצים וירטואליים, אפליקציית הלקוח צריכה לכלול לוגיקה מיוחדת לטיפול אותם. אם רוצים לקבל ייצוג בבייט של הקובץ – לתצוגה מקדימה של הקובץ, לדוגמה, צריך לבקש סוג MIME חלופי מהמסמכים ספק.
לאחר שהמשתמש מבצע בחירה, השתמש ב-URI בנתוני התוצאות כדי לקבוע האם הקובץ וירטואלי, כפי שמוצג בקטע הקוד הבא:
private fun isVirtualFile(uri: Uri): Boolean { if (!DocumentsContract.isDocumentUri(this, uri)) { return false } val cursor: Cursor? = contentResolver.query( uri, arrayOf(DocumentsContract.Document.COLUMN_FLAGS), null, null, null ) val flags: Int = cursor?.use { if (cursor.moveToFirst()) { cursor.getInt(0) } else { 0 } } ?: 0 return flags and DocumentsContract.Document.FLAG_VIRTUAL_DOCUMENT != 0 }
private boolean isVirtualFile(Uri uri) { if (!DocumentsContract.isDocumentUri(this, uri)) { return false; } Cursor cursor = getContentResolver().query( uri, new String[] { DocumentsContract.Document.COLUMN_FLAGS }, null, null, null); int flags = 0; if (cursor.moveToFirst()) { flags = cursor.getInt(0); } cursor.close(); return (flags & DocumentsContract.Document.FLAG_VIRTUAL_DOCUMENT) != 0; }
אחרי שתאמתו שהמסמך הוא קובץ וירטואלי, תוכלו לאלץ את הקובץ להפוך לסוג MIME חלופי, כמו "image/png"
. קטע הקוד הבא מראה איך לבדוק אם אפשר לייצג קובץ וירטואלי כתמונה, ואם כן, מקבלים ממנו מקור קלט:
@Throws(IOException::class) private fun getInputStreamForVirtualFile( uri: Uri, mimeTypeFilter: String): InputStream { val openableMimeTypes: Array<String>? = contentResolver.getStreamTypes(uri, mimeTypeFilter) return if (openableMimeTypes?.isNotEmpty() == true) { contentResolver .openTypedAssetFileDescriptor(uri, openableMimeTypes[0], null) .createInputStream() } else { throw FileNotFoundException() } }
private InputStream getInputStreamForVirtualFile(Uri uri, String mimeTypeFilter) throws IOException { ContentResolver resolver = getContentResolver(); String[] openableMimeTypes = resolver.getStreamTypes(uri, mimeTypeFilter); if (openableMimeTypes == null || openableMimeTypes.length < 1) { throw new FileNotFoundException(); } return resolver .openTypedAssetFileDescriptor(uri, openableMimeTypes[0], null) .createInputStream(); }
מקורות מידע נוספים
לקבלת מידע נוסף על אופן האחסון של מסמכים וקבצים אחרים וגישה אליהם, כדאי לעיין במקורות המידע הבאים.
דוגמיות
- ActionOpenDocument, שזמינות ב-GitHub.
- ActionOpenDocumentTree, שזמין ב-GitHub.