Android 6.0 Marshmallow. Не удается записать на SD-карту
У меня есть приложение, которое использует внешнее хранилище для хранения фотографий. При необходимости в своем манифесте запрашиваются следующие разрешения:
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
и для получения требуемого каталога
используется следующее:
File sdDir = Environment
.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
SimpleDateFormat dateFormat = new SimpleDateFormat("MM-dd", Locale.US);
String date = dateFormat.format(new Date());
storageDir = new File(sdDir, getResources().getString(
R.string.storagedir)
+ "-" + date);
// Create directory, error handling
if (!storageDir.exists() && !storageDir.mkdirs()) {
... fails here
Приложение отлично работает на Android от 5.1 до 2.3; он уже больше года работает в Google Play.
После обновления одного из моих тестовых телефонов (Android One) до 6 он теперь возвращает ошибку при попытке создать требуемый каталог "/sdcard/Pictures/myapp-yy-mm".
SD-карта настроена как "переносное хранилище". Я отформатировал SD-карту. Я заменил его. Я перезагрузился. Все безрезультатно.
Кроме того, встроенная функция скриншотов Android (с помощью Power + Lower volume) терпит неудачу "из-за ограниченного пространства для хранения или не допускается приложением или вашей организацией".
Любые идеи?
Ответы
Ответ 1
Я столкнулся с той же проблемой. В Android есть два типа разрешений:
- Опасный (доступ к контактам, запись на внешнее хранилище...)
- Normal
Нормальные разрешения автоматически утверждаются Android, а опасные разрешения должны быть одобрены пользователями Android.
Вот стратегия получения опасных разрешений в Android 6.0
- Проверьте, есть ли у вас разрешение.
- Если вашему приложению уже предоставлено разрешение, действуйте нормально и выполняйте нормально.
- Если ваше приложение еще не имеет разрешения, попросите пользователя одобрить
- Прослушать утверждение пользователя в onRequestPermissionsResult
Вот мой случай: мне нужно написать во внешнее хранилище.
Во-первых, я проверяю, есть ли у меня разрешение:
...
private static final int REQUEST_WRITE_STORAGE = 112;
...
boolean hasPermission = (ContextCompat.checkSelfPermission(activity,
Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED);
if (!hasPermission) {
ActivityCompat.requestPermissions(parentActivity,
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
REQUEST_WRITE_STORAGE);
}
Затем проверьте утверждение пользователя:
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode)
{
case REQUEST_WRITE_STORAGE: {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED)
{
//reload my activity with permission granted or use the features what required the permission
} else
{
Toast.makeText(parentActivity, "The app was not allowed to write to your storage. Hence, it cannot function properly. Please consider granting it this permission", Toast.LENGTH_LONG).show();
}
}
}
}
Здесь вы можете узнать больше о новой модели разрешения: https://developer.android.com/training/permissions/requesting.html
Ответ 2
Сначала я дам вам Опасный список разрешений в Android M и более поздней версии
![введите описание изображения здесь]()
Затем дайте вам пример того, как запрашивать разрешение в версии Android M и позже.
Я запрашиваю у пользователя разрешение WRITE_EXTERNAL_STORAGE.
Сначала добавьте разрешение в файл манифеста Android.
Шаг 1 Объявить код запроса
private static String TAG = "PermissionDemo";
private static final int REQUEST_WRITE_STORAGE = 112;
Шаг 2 Добавьте этот код, если хотите, чтобы пользователь запрашивал разрешение
//ask for the permission in android M
int permission = ContextCompat.checkSelfPermission(this,
Manifest.permission.WRITE_EXTERNAL_STORAGE);
if (permission != PackageManager.PERMISSION_GRANTED) {
Log.i(TAG, "Permission to record denied");
if (ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage("Permission to access the SD-CARD is required for this app to Download PDF.")
.setTitle("Permission required");
builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
Log.i(TAG, "Clicked");
makeRequest();
}
});
AlertDialog dialog = builder.create();
dialog.show();
} else {
makeRequest();
}
}
protected void makeRequest() {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
REQUEST_WRITE_STORAGE);
}
Шаг 3 Добавить метод переопределения для запроса
@Override
public void onRequestPermissionsResult(int requestCode,
String permissions[], int[] grantResults) {
switch (requestCode) {
case REQUEST_WRITE_STORAGE: {
if (grantResults.length == 0
|| grantResults[0] !=
PackageManager.PERMISSION_GRANTED) {
Log.i(TAG, "Permission has been denied by user");
} else {
Log.i(TAG, "Permission has been granted by user");
}
return;
}
}
}
Примечание. Не забудьте добавить разрешение в файл манифеста
ЛУЧШИЙ ПРИМЕР НИЖЕ С МНОЖЕСТВЕННЫМ РАЗРЕШЕНИЕМ ПЛЮС ПОКРЫВАЕТ ВСЕ СКЭНАРИО
Я добавил комментарии, чтобы вы могли легко понять.
import android.Manifest;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.provider.Settings;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import com.production.hometech.busycoder.R;
import java.util.ArrayList;
public class PermissionInActivity extends AppCompatActivity implements View.OnClickListener {
private static final int REQUEST_PERMISSION_SETTING = 99;
private Button bt_camera;
private static final String[] PARAMS_TAKE_PHOTO = {
Manifest.permission.CAMERA,
Manifest.permission.WRITE_EXTERNAL_STORAGE
};
private static final int RESULT_PARAMS_TAKE_PHOTO = 11;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_permission_in);
bt_camera = (Button) findViewById(R.id.bt_camera);
bt_camera.setOnClickListener(this);
}
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.bt_camera:
takePhoto();
break;
}
}
/**
* shouldShowRequestPermissionRationale() = This will return true if the user had previously declined to grant you permission
* NOTE : that ActivityCompat also has a backwards-compatible implementation of
* shouldShowRequestPermissionRationale(), so you can avoid your own API level
* checks.
* <p>
* shouldShowRequestPermissionRationale() = returns false if the user declined the permission and checked the checkbox to ask you to stop pestering the
* user.
* <p>
* requestPermissions() = request for the permisssiion
*/
private void takePhoto() {
if (canTakePhoto()) {
Toast.makeText(this, "You can take PHOTO", Toast.LENGTH_SHORT).show();
} else if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CAMERA) || ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
Toast.makeText(this, "You should give permission", Toast.LENGTH_SHORT).show();
ActivityCompat.requestPermissions(this, netPermisssion(PARAMS_TAKE_PHOTO), RESULT_PARAMS_TAKE_PHOTO);
} else {
ActivityCompat.requestPermissions(this, netPermisssion(PARAMS_TAKE_PHOTO), RESULT_PARAMS_TAKE_PHOTO);
}
}
// This method return permission denied String[] so we can request again
private String[] netPermisssion(String[] wantedPermissions) {
ArrayList<String> result = new ArrayList<>();
for (String permission : wantedPermissions) {
if (!hasPermission(permission)) {
result.add(permission);
}
}
return (result.toArray(new String[result.size()]));
}
private boolean canTakePhoto() {
return (hasPermission(Manifest.permission.CAMERA) && hasPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE));
}
/**
* checkSelfPermission() = you can check if you have been granted a runtime permission or not
* ex = ContextCompat.checkSelfPermission(this,permissionString)== PackageManager.PERMISSION_GRANTED
* <p>
* ContextCompat offers a backwards-compatible implementation of checkSelfPermission(), ActivityCompat offers a backwards-compatible
* implementation of requestPermissions() that you can use.
*
* @param permissionString
* @return
*/
private boolean hasPermission(String permissionString) {
return (ContextCompat.checkSelfPermission(this, permissionString) == PackageManager.PERMISSION_GRANTED);
}
/**
* requestPermissions() action goes to onRequestPermissionsResult() whether user can GARNT or DENIED those permisssions
*
* @param requestCode
* @param permissions
* @param grantResults
*/
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == RESULT_PARAMS_TAKE_PHOTO) {
if (canTakePhoto()) {
Toast.makeText(this, "You can take picture", Toast.LENGTH_SHORT).show();
} else if (!(ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CAMERA) || ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE))) {
final AlertDialog.Builder settingDialog = new AlertDialog.Builder(PermissionInActivity.this);
settingDialog.setTitle("Permissioin");
settingDialog.setMessage("Now you need to enable permisssion from the setting because without permission this app won't run properly \n\n goto -> setting -> appInfo");
settingDialog.setCancelable(false);
settingDialog.setPositiveButton("Setting", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
dialogInterface.cancel();
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
Uri uri = Uri.fromParts("package", getPackageName(), null);
intent.setData(uri);
startActivityForResult(intent, REQUEST_PERMISSION_SETTING);
Toast.makeText(getBaseContext(), "Go to Permissions to Grant all permission ENABLE", Toast.LENGTH_LONG).show();
}
});
settingDialog.show();
Toast.makeText(this, "You need to grant permission from setting", Toast.LENGTH_SHORT).show();
}
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_PERMISSION_SETTING) {
if (canTakePhoto()) {
Toast.makeText(this, "You can take PHOTO", Toast.LENGTH_SHORT).show();
}
}
}
}
Специальный случай изменения конфигурации
Возможно, что пользователь будет вращать устройство или иным образом инициировать изменение конфигурации, в то время как наш диалог диалогов находится на переднем плане. Поскольку наша деятельность по-прежнему виден за этим диалогом, мы уничтожаемся и воссозданы... но мы не хотим снова поднимать диалоговое окно с правами.
Вот почему у нас есть логическое имя с именем isInPermission, которое отслеживает, есть или нет
мы находимся в середине запроса разрешений. Мы держим это значение в
onSaveInstanceState()
:
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean(STATE_IN_PERMISSION, isInPermission);
}
Мы восстанавливаем его в onCreate()
. Если мы не обладаем всеми требуемыми разрешениями, но isInPermission истинно, мы пропускаем запрос на разрешения, поскольку мы находимся в
посреди этого уже.
Ответ 3
Android изменил, как разрешения работают с Android 6.0, что является причиной ваших ошибок. Вы должны действительно запросить и проверить, было ли разрешение предоставлено пользователем для использования. Таким образом, разрешения в файле манифеста будут работать только для api ниже 21.
Проверьте эту ссылку для фрагмента того, как запрашиваются разрешения в api23 http://android-developers.blogspot.nl/2015/09/google-play-services-81-and-android-60.html?m=1
Код: -
If (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.READ_EXTERNAL_STORAGE) !=
PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, STORAGE_PERMISSION_RC);
return;
}`
` @Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == STORAGE_PERMISSION_RC) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
//permission granted start reading
} else {
Toast.makeText(this, "No permission to read external storage.", Toast.LENGTH_SHORT).show();
}
}
}
}
Ответ 4
Возможно, вы не можете использовать класс manifest из сгенерированного кода в своем проекте. Таким образом, вы можете использовать класс manifest из android sdk "android.Manifest.permission.WRITE_EXTERNAL_STORAGE". Но в версии Marsmallow есть 2 разрешения, которые должны предоставить: WRITE и READ EXTERNAL STORAGE в категории хранения. Посмотрите мою программу, моя программа запросит разрешение, пока пользователь не выберет "да" и не сделает что-то после предоставления разрешений.
if (Build.VERSION.SDK_INT >= 23) {
if (ContextCompat.checkSelfPermission(LoginActivity.this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(LoginActivity.this, android.Manifest.permission.READ_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(LoginActivity.this,
new String[]{android.Manifest.permission.WRITE_EXTERNAL_STORAGE, android.Manifest.permission.READ_EXTERNAL_STORAGE},
1);
} else {
//do something
}
} else {
//do something
}
Ответ 5
Right. Поэтому я, наконец, дошел до сути проблемы: это было неудачное обновление OTA на месте.
Мои подозрения усилились после того, как мой Garmin Fenix 2 не смог подключиться через bluetooth и после поиска в Google "Проблемы с обновлением Marshmallow". Во всяком случае, "Factory reset" исправил проблему.
Удивительно, но reset не вернул телефон оригинальному Kitkat; вместо этого процесс очистки очистил загруженный пакет обновления OTA 6.0 и работал с ним, в результате (я думаю) в "более чистом" обновлении.
Конечно, это означало, что телефон потерял все приложения, которые я установил. Но недавно установленные приложения, в том числе мои, работают без каких-либо изменений (т.е. Есть обратная совместимость). Уф!
Ответ 6
Документация для Android на Manifest.permission.Manifest.permission.WRITE_EXTERNAL_STORAGE:
Начиная с уровня API 19, это разрешение не требуется для чтения/записи файлов в ваших каталогах приложений, возвращаемых getExternalFilesDir (String) и getExternalCacheDir().
Я думаю, что это означает, что вам не нужно кодировать реализацию во время выполнения разрешения WRITE_EXTERNAL_STORAGE, если приложение не пишет в каталог, не относящийся к вашему приложению.
Вы можете определить максимальную версию sdk в манифесте для разрешения, например:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="19" />
Также не забудьте изменить целевой SDK в build.graddle, а не манифест, параметры gradle всегда будут перезаписывать параметры манифеста.
android {
compileSdkVersion 23
buildToolsVersion '23.0.1'
defaultConfig {
minSdkVersion 17
targetSdkVersion 22
}