Создать простой File Chooser в Android
View more Tutorials:
В операционнной системе Windows вы уже знакомы с File Chooser, он помогает вам выбрать файлы имеющиеся на вашем компьютере. Ваш вопрос в том, имеется ли что-то подобное в Android?

К сожалению File Chooser не имеется в Android, но вам не нужно волноваться, в данной статье я покажу вам как создать FileChooserFragment симулирующий File Chooser, и вы можете переиспользовать его в ваших проектах.

Просмотр примера:

Примечание: Вам нужно пару файлов для тестирования данного примера на Android Emulator. OK, на окне Device File Explorer скопируйте несколько файлов "sdcard/Download", как в изображении ниже:

На Android Studio создайте новый project:
- File > New > New Project > Empty Activity
- Name: FileChooserExample
- Package name: org.o7planning.filechooserexample
- Language: Java
Добавьте код ниже в AndroidManifest.xml чтобы сказать Android "Разрешить данному приложению получить доступ во внешнее хранилище устройства":
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="org.o7planning.filechooserexample"> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
Примечание: Android 6.0+ (API Level 23+) требует более высокого уровня безопасности, поэтому ваше приложение должно запросить разрешение пользователя для доступа к внешнему хранилищу устройства (Смотрите обработку в кодовом примере).
FileChooserFragment
Далее мы создаем FileChooserFragment, вы можете использовать данный Fragment в разных местах вашего проекта.

Нажмите на правую кнопку мыши на project и выберите:
- File > New > Fragment > Fragment (Blank)



Откройте файл fragment_file_chooser.xml чтобы сделать дизайн интерфейса для FileChooserFragment.

Настроить ID, Text для компонентов на интерфейсе:

fragment_file_chooser.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:dividerPadding="5sp" tools:context=".FileChooserFragment"> <EditText android:id="@+id/editText_path" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:ems="10" android:inputType="textUri" /> <Button android:id="@+id/button_browse" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="0" android:text="Browse" /> </LinearLayout>
FileChooserFragment.java
package org.o7planning.filechooserexample; import android.Manifest; import android.app.Activity; import android.content.Intent; import android.content.pm.PackageManager; import android.net.Uri; import android.os.Bundle; import androidx.core.app.ActivityCompat; import androidx.fragment.app.Fragment; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.Button; import android.widget.EditText; import android.widget.Toast; public class FileChooserFragment extends Fragment { private static final int MY_REQUEST_CODE_PERMISSION = 1000; private static final int MY_RESULT_CODE_FILECHOOSER = 2000; private Button buttonBrowse; private EditText editTextPath; private static final String LOG_TAG = "AndroidExample"; @Override public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.fragment_file_chooser, container, false); this.editTextPath = (EditText) rootView.findViewById(R.id.editText_path); this.buttonBrowse = (Button) rootView.findViewById(R.id.button_browse); this.buttonBrowse.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { askPermissionAndBrowseFile(); } }); return rootView; } private void askPermissionAndBrowseFile() { // With Android Level >= 23, you have to ask the user // for permission to access External Storage. if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { // Level 23 // Check if we have Call permission int permisson = ActivityCompat.checkSelfPermission(this.getContext(), Manifest.permission.READ_EXTERNAL_STORAGE); if (permisson != PackageManager.PERMISSION_GRANTED) { // If don't have permission so prompt the user. this.requestPermissions( new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, MY_REQUEST_CODE_PERMISSION ); return; } } this.doBrowseFile(); } private void doBrowseFile() { Intent chooseFileIntent = new Intent(Intent.ACTION_GET_CONTENT); chooseFileIntent.setType("*/*"); // Only return URIs that can be opened with ContentResolver chooseFileIntent.addCategory(Intent.CATEGORY_OPENABLE); chooseFileIntent = Intent.createChooser(chooseFileIntent, "Choose a file"); startActivityForResult(chooseFileIntent, MY_RESULT_CODE_FILECHOOSER); } // When you have the request results @Override public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); // switch (requestCode) { case MY_REQUEST_CODE_PERMISSION: { // Note: If request is cancelled, the result arrays are empty. // Permissions granted (CALL_PHONE). if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { Log.i( LOG_TAG,"Permission granted!"); Toast.makeText(this.getContext(), "Permission granted!", Toast.LENGTH_SHORT).show(); this.doBrowseFile(); } // Cancelled or denied. else { Log.i(LOG_TAG,"Permission denied!"); Toast.makeText(this.getContext(), "Permission denied!", Toast.LENGTH_SHORT).show(); } break; } } } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { switch (requestCode) { case MY_RESULT_CODE_FILECHOOSER: if (resultCode == Activity.RESULT_OK ) { if(data != null) { Uri fileUri = data.getData(); Log.i(LOG_TAG, "Uri: " + fileUri); String filePath = null; try { filePath = FileUtils.getPath(this.getContext(),fileUri); } catch (Exception e) { Log.e(LOG_TAG,"Error: " + e); Toast.makeText(this.getContext(), "Error: " + e, Toast.LENGTH_SHORT).show(); } this.editTextPath.setText(filePath); } } break; } super.onActivityResult(requestCode, resultCode, data); } public String getPath() { return this.editTextPath.getText().toString(); } }
После получения FileChooserFragment вы можете использовать его везде в проекте.


Настроить ID, Text для компонентов на интерфейсе:

activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingLeft="5sp" android:paddingTop="5sp" android:paddingRight="5sp" android:paddingBottom="5sp" tools:context=".MainActivity" > <fragment android:id="@+id/fragment_fileChooser" android:name="org.o7planning.filechooserexample.FileChooserFragment" android:layout_width="0dp" android:layout_height="45dp" android:layout_marginStart="16dp" android:layout_marginLeft="16dp" android:layout_marginTop="16dp" android:layout_marginEnd="16dp" android:layout_marginRight="16dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <Button android:id="@+id/button_showInfo" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="16dp" android:text="Show Info" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/fragment_fileChooser" /> </androidx.constraintlayout.widget.ConstraintLayout>
MainActivity.java
package org.o7planning.filechooserexample; import androidx.appcompat.app.AppCompatActivity; import androidx.fragment.app.FragmentManager; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.Toast; public class MainActivity extends AppCompatActivity { private FileChooserFragment fragment; private Button buttonShowInfo; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); FragmentManager fragmentManager = this.getSupportFragmentManager(); this.fragment = (FileChooserFragment) fragmentManager.findFragmentById(R.id.fragment_fileChooser); this.buttonShowInfo = this.findViewById(R.id.button_showInfo); this.buttonShowInfo.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { showInfo(); } }); } private void showInfo() { String path = this.fragment.getPath(); Toast.makeText(this, "Path: " + path, Toast.LENGTH_LONG).show(); } }
FileUtils.java
package org.o7planning.filechooserexample; import android.annotation.SuppressLint; import android.content.ContentResolver; import android.content.ContentUris; import android.content.Context; import android.database.Cursor; import android.net.Uri; import android.os.Build; import android.os.Environment; import android.provider.DocumentsContract; import android.provider.MediaStore; import android.provider.OpenableColumns; import android.text.TextUtils; import android.util.Log; import java.io.File; import java.io.FileOutputStream; import java.io.InputStream; public class FileUtils { private static final String LOG_TAG = "FileUtils"; private static Uri contentUri = null; /** * Get a file path from a Uri. This will get the the path for Storage Access * Framework Documents, as well as the _data field for the MediaStore and * other file-based ContentProviders.<br> * <br> * Callers should check whether the path is local before assuming it * represents a local file. * * @param context The context. * @param uri The Uri to query. */ @SuppressLint("NewApi") public static String getPath(final Context context, final Uri uri) { // Check here to KITKAT or new version final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; String selection = null; String[] selectionArgs = null; // DocumentProvider if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) { // ExternalStorageProvider if (isExternalStorageDocument(uri)) { final String docId = DocumentsContract.getDocumentId(uri); final String[] split = docId.split(":"); final String type = split[0]; String fullPath = getPathFromExtSD(split); if (fullPath != "") { return fullPath; } else { return null; } } // DownloadsProvider else if (isDownloadsDocument(uri)) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { final String id; Cursor cursor = null; try { cursor = context.getContentResolver().query(uri, new String[]{MediaStore.MediaColumns.DISPLAY_NAME}, null, null, null); if (cursor != null && cursor.moveToFirst()) { String fileName = cursor.getString(0); String path = Environment.getExternalStorageDirectory().toString() + "/Download/" + fileName; if (!TextUtils.isEmpty(path)) { return path; } } } finally { if (cursor != null) { cursor.close(); } } id = DocumentsContract.getDocumentId(uri); if (!TextUtils.isEmpty(id)) { if (id.startsWith("raw:")) { return id.replaceFirst("raw:", ""); } String[] contentUriPrefixesToTry = new String[] { "content://downloads/public_downloads", "content://downloads/my_downloads" }; for (String contentUriPrefix : contentUriPrefixesToTry) { try { final Uri contentUri = ContentUris.withAppendedId(Uri.parse(contentUriPrefix), Long.valueOf(id)); // final Uri contentUri = ContentUris.withAppendedId( // Uri.parse("content://downloads/public_downloads"), Long.valueOf(id)); return getDataColumn(context, contentUri, null, null); } catch (NumberFormatException e) { // In Android 8 and Android P the id is not a number return uri.getPath().replaceFirst("^/document/raw:", "").replaceFirst("^raw:", ""); } } } } else { final String id = DocumentsContract.getDocumentId(uri); final boolean isOreo = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O; if (id.startsWith("raw:")) { return id.replaceFirst("raw:", ""); } try { contentUri = ContentUris.withAppendedId( Uri.parse("content://downloads/public_downloads"), Long.valueOf(id)); } catch (NumberFormatException e) { e.printStackTrace(); } if (contentUri != null) { return getDataColumn(context, contentUri, null, null); } } } // MediaProvider else if (isMediaDocument(uri)) { final String docId = DocumentsContract.getDocumentId(uri); final String[] split = docId.split(":"); final String type = split[0]; Uri contentUri = null; if ("image".equals(type)) { contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; } else if ("video".equals(type)) { contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; } else if ("audio".equals(type)) { contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; } selection = "_id=?"; selectionArgs = new String[]{split[1]}; return getDataColumn(context, contentUri, selection, selectionArgs); } else if (isGoogleDriveUri(uri)) { return getDriveFilePath(uri, context); } } // MediaStore (and general) else if ("content".equalsIgnoreCase(uri.getScheme())) { if (isGooglePhotosUri(uri)) { return uri.getLastPathSegment(); } if (isGoogleDriveUri(uri)) { return getDriveFilePath(uri, context); } if( Build.VERSION.SDK_INT == Build.VERSION_CODES.N) { // return getFilePathFromURI(context,uri); return getMediaFilePathForN(uri, context); // return getRealPathFromURI(context,uri); } else { return getDataColumn(context, uri, null, null); } } // File else if ("file".equalsIgnoreCase(uri.getScheme())) { return uri.getPath(); } return null; } /** * Check if a file exists on device * * @param filePath The absolute file path */ private static boolean fileExists(String filePath) { File file = new File(filePath); return file.exists(); } /** * Get full file path from external storage * * @param pathData The storage type and the relative path */ private static String getPathFromExtSD(String[] pathData) { final String type = pathData[0]; final String relativePath = "/" + pathData[1]; String fullPath = ""; // on my Sony devices (4.4.4 & 5.1.1), `type` is a dynamic string // something like "71F8-2C0A", some kind of unique id per storage // don't know any API that can get the root path of that storage based on its id. // // so no "primary" type, but let the check here for other devices if ("primary".equalsIgnoreCase(type)) { fullPath = Environment.getExternalStorageDirectory() + relativePath; if (fileExists(fullPath)) { return fullPath; } } // Environment.isExternalStorageRemovable() is `true` for external and internal storage // so we cannot relay on it. // // instead, for each possible path, check if file exists // we'll start with secondary storage as this could be our (physically) removable sd card fullPath = System.getenv("SECONDARY_STORAGE") + relativePath; if (fileExists(fullPath)) { return fullPath; } fullPath = System.getenv("EXTERNAL_STORAGE") + relativePath; if (fileExists(fullPath)) { return fullPath; } return fullPath; } private static String getDriveFilePath(Uri uri, Context context) { Uri returnUri = uri; ContentResolver contentResolver = context.getContentResolver(); Cursor returnCursor = contentResolver.query(returnUri, null, null, null, null); // Get the column indexes of the data in the Cursor, // move to the first row in the Cursor, get the data, // and display it. int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME); int sizeIndex = returnCursor.getColumnIndex(OpenableColumns.SIZE); returnCursor.moveToFirst(); String name = (returnCursor.getString(nameIndex)); String size = (Long.toString(returnCursor.getLong(sizeIndex))); File file = new File(context.getCacheDir(), name); try { InputStream inputStream = context.getContentResolver().openInputStream(uri); FileOutputStream outputStream = new FileOutputStream(file); int read = 0; int maxBufferSize = 1 * 1024 * 1024; int bytesAvailable = inputStream.available(); // int bufferSize = 1024; int bufferSize = Math.min(bytesAvailable, maxBufferSize); final byte[] buffers = new byte[bufferSize]; while ((read = inputStream.read(buffers)) != -1) { outputStream.write(buffers, 0, read); } Log.e("File Size", "Size " + file.length()); inputStream.close(); outputStream.close(); Log.e("File Path", "Path " + file.getPath()); Log.e("File Size", "Size " + file.length()); } catch (Exception e) { Log.e(LOG_TAG, e.getMessage()); } return file.getPath(); } private static String getMediaFilePathForN(Uri uri, Context context) { Uri returnUri = uri; Cursor returnCursor = context.getContentResolver().query(returnUri, null, null, null, null); // Get the column indexes of the data in the Cursor, // move to the first row in the Cursor, get the data, // and display it. int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME); int sizeIndex = returnCursor.getColumnIndex(OpenableColumns.SIZE); returnCursor.moveToFirst(); String name = (returnCursor.getString(nameIndex)); String size = (Long.toString(returnCursor.getLong(sizeIndex))); File file = new File(context.getFilesDir(), name); try { InputStream inputStream = context.getContentResolver().openInputStream(uri); FileOutputStream outputStream = new FileOutputStream(file); int read = 0; int maxBufferSize = 1 * 1024 * 1024; int bytesAvailable = inputStream.available(); //int bufferSize = 1024; int bufferSize = Math.min(bytesAvailable, maxBufferSize); final byte[] buffers = new byte[bufferSize]; while ((read = inputStream.read(buffers)) != -1) { outputStream.write(buffers, 0, read); } Log.e("File Size", "Size " + file.length()); inputStream.close(); outputStream.close(); Log.e("File Path", "Path " + file.getPath()); Log.e("File Size", "Size " + file.length()); } catch (Exception e) { Log.e(LOG_TAG, e.getMessage()); } return file.getPath(); } private static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) { Cursor cursor = null; final String column = "_data"; final String[] projection = {column}; try { cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null); if (cursor != null && cursor.moveToFirst()) { final int index = cursor.getColumnIndexOrThrow(column); return cursor.getString(index); } } finally { if (cursor != null) { cursor.close(); } } return null; } /** * @param uri - The Uri to check. * @return - Whether the Uri authority is ExternalStorageProvider. */ private static boolean isExternalStorageDocument(Uri uri) { return "com.android.externalstorage.documents".equals(uri.getAuthority()); } /** * @param uri - The Uri to check. * @return - Whether the Uri authority is DownloadsProvider. */ private static boolean isDownloadsDocument(Uri uri) { return "com.android.providers.downloads.documents".equals(uri.getAuthority()); } /** * @param uri - The Uri to check. * @return - Whether the Uri authority is MediaProvider. */ private static boolean isMediaDocument(Uri uri) { return "com.android.providers.media.documents".equals(uri.getAuthority()); } /** * @param uri - The Uri to check. * @return - Whether the Uri authority is Google Photos. */ private static boolean isGooglePhotosUri(Uri uri) { return "com.google.android.apps.photos.content".equals(uri.getAuthority()); } /** * @param uri The Uri to check. * @return Whether the Uri authority is Google Drive. */ private static boolean isGoogleDriveUri(Uri uri) { return "com.google.android.apps.docs.storage".equals(uri.getAuthority()) // || "com.google.android.apps.docs.storage.legacy".equals(uri.getAuthority()); } }