Как я могу позволить пользователям получить доступ к внутреннему каталогу хранилища моего приложения?

Мое приложение хранит файлы во внутреннем каталоге хранилища (/Android/data/com.mycompany.myapp/files, как было возвращено getFilesDir()), и я хотел бы разрешить пользователям обращаться к этим файлам непосредственно из управления файлами приложение на своем мобильном устройстве или приложение Android File Transfer для рабочего стола.

Руководство разработчика для опций хранилища говорит:

По умолчанию файлы, сохраненные во внутреннем хранилище, являются приватными для вашего приложения, а другие приложения не могут получить к ним доступ (а также не могут).

"По умолчанию" означает, что я могу изменить разрешения по умолчанию, чтобы позволить пользователям получать доступ к этим файлам, но я не могу найти документацию о том, как это сделать. В настоящее время каталог com.mycompany.myapp скрыт, когда я просматриваю каталог /Android/data с помощью приложения для управления файлами.

В настоящее время я сохраняю данные файла следующим образом:

String fileName = "myfile.jpg";
String filePath = getFilesDir() + File.separator + fileName;
FileOutputStream fileStream = new FileOutputStream(filePath);
fileData.writeTo(fileStream); // fileData is a ByteArrayOutputStream
fileStream.close();

Я попробовал настроить разрешения для отдельных файлов на чтение во всем мире, но это не делало каталог видимым:

FileOutputStream fileStream = this.app.openFileOutput(fileName, Context.MODE_WORLD_READABLE);

Я также проверил документацию для файла AndroidManifest и ничего там не видел. Кто-нибудь знает, как я могу это сделать?

Ответ 1

Я более подробно рассмотрел результат getFilesDir() vs getExternalFilesDir() и обнаружил, что getFilesDir() возвращает /data/data/[packagename]/files, а getExternalFilesDir() возвращает /Android/data/[packagename]/files. Я думал, что файлы приложений, которые я просматривал в /Android/data, были внутренними каталогами хранилища, но теперь я вижу, что это фактически внешние каталоги хранения.

Если внутренние папки хранения никогда не доступны для обычных пользователей, я хочу, чтобы в документации говорилось, что вместо того, чтобы говорить, что они недоступны "по умолчанию". По крайней мере, для меня, говоря "по умолчанию", подразумевается, что поведение по умолчанию может быть изменено.

В любом случае, я протестировал и подтвердил, что если я удалю свое приложение, файлы, сохраненные в getExternalFilesDir(), будут удалены автоматически. Таким образом, это соответствует моей потребности в местоположении хранилища, которое явно связано с приложением (использует имя конкретного приложения и удаляется с помощью приложения), но доступно пользователям для случайного ручного управления файлами.

Вот сравнение, которое может быть полезно для кого-то другого, читающего это:

  • getFilesDir() - создает каталог, специфичный для приложения; скрытые от пользователей; удален с помощью приложения
  • getExternalFilesDir() - создает каталог, специфичный для приложения; доступный для пользователей; удален с помощью приложения
  • getExternalStoragePublicDirectory() - использует общий каталог (например, Music); доступный для пользователей; остается, когда приложение удаляется.

Ответ 2

Я думаю, что вы запутались в документации разработчиков, я не могу вас обвинить, это не лучшая документация, которую я когда-либо читал. В любом случае, Android предоставляет два способа сохранения файлов в файловой системе:

  • Использование внутреннего хранилища
  • Использование внешнего хранилища

Внутреннее хранилище ВСЕГДА доступно и доступно ТОЛЬКО вашему приложению, никакое другое приложение в системе не может получить доступ к файлам, сохраненным в этом разделе.

Теперь я думаю, что вам нужно Внешнее хранилище, потому что оно "доступно для чтения" и доступно любому пользователю и любым приложениям. Недостатком является то, что он может быть недоступен, так как пользователь может установить его на USB-устройство, которое может быть удалено пользователем в любое время.

Вы не должны использовать ни MODE_WORLD_WRITEABLE, ни MODE_WORLD_READABLE, как указано в документации, потому что это очень опасно. Кроме того, эти константы устарели, поскольку уровень API 17

Рекомендация

Если вам нужно сделать ваши файлы общедоступными, сохраните их в Внешнем хранилище. Вам нужно будет объявить разрешение в вашем файле манифеста, чтобы избежать сбоя приложения при каждом доступе к Внешнему хранилищу...

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

поскольку Внешнее хранилище может быть недоступно, вам нужно будет определить его состояние перед выполнением какой-либо операции, иначе ваше приложение выйдет из строя...

public enum StorageState{
  NOT_AVAILABLE, WRITEABLE, READ_ONLY
}

public StorageState getExternalStorageState() {
    StorageState result = StorageState.NOT_AVAILABLE;
    String state = Environment.getExternalStorageState();

    if (Environment.MEDIA_MOUNTED.equals(state)) {
        return StorageState.WRITEABLE;
    }
    else if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
        return StorageState.READ_ONLY;
    }

    return result;
}

Более подробная информация о документации и вещах, о которых вы должны знать. Например, вы можете предоставить владельцам своих файлов эти файлы, чтобы при удалении приложения система могла автоматически удалять эти файлы. Для получения дополнительной информации см. Документацию на сайте разработчиков Android

Ответ 3

добавить это, чтобы манифест

затем добавьте это в свою деятельность:

private static final String RequieredPermission = Manifest.permission.READ_EXTERNAL_STORAGE;

затем вызовите HandlePermission() где вам требуется проверить разрешение, обычно в методе Oncreate действия. Эти функции тогда необходимы:

@Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        Log.d("amir", "onRequestPermissionsResult: called");
        switch (requestCode) {
            case REQUEST_PERMISSION_READING_STATE:
                if (grantResults.length > 0
                        && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    Toast.makeText(MainActivity.this, "Permission Granted!", Toast.LENGTH_SHORT).show();
                } else {
                    Toast.makeText(MainActivity.this, "Permission Denied!", Toast.LENGTH_SHORT).show();
                }
        }

    }

    private void HandlePermission() {
        int permissionCheck = ContextCompat.checkSelfPermission(
                this, RequieredPermission);
        if (permissionCheck != PackageManager.PERMISSION_GRANTED) {
            if (ActivityCompat.shouldShowRequestPermissionRationale(this,
                    RequieredPermission)) {
                showExplanationaboutPermission("Permission Needed", "Rationale", RequieredPermission, REQUEST_PERMISSION_READING_STATE);
            } else {
                requestPermission(RequieredPermission, REQUEST_PERMISSION_READING_STATE);
            }
        } else {
            Toast.makeText(MainActivity.this, "Permission (already) Granted!", Toast.LENGTH_SHORT).show();
        }
    }

    private void showExplanationaboutPermission(String title,
                                                String message,
                                                final String permission,
                                                final int permissionRequestCode) {
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle(title)
                .setMessage(message)
                .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                        requestPermission(permission, permissionRequestCode);
                    }
                });
        builder.create().show();
    }

    private void requestPermission(String permissionName, int permissionRequestCode) {
        ActivityCompat.requestPermissions(this,
                new String[]{permissionName}, permissionRequestCode);
    }