Android 6.0棉花糖。 无法写入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; 它已经在谷歌播放了一年多了。

在将我的一个testing手机(Android One)升级到6之后,现在在尝试创build必需的目录“/ sdcard / Pictures / myapp-yy-mm”时会返回一个错误。

SD卡被configuration为“便携式存储”。 我已经格式化了SD卡。 我已经取代了它。 我重新启动了。 一切都无济于事。

另外,由于存储空间有限,或者应用程序或您的组织不允许使用内置的android截图function(通过Power + Lower音量)失败。

有任何想法吗?

我面临同样的问题。 Android中有两种types的权限:

  • 危险(访问联系人,写入外部存储…)
  • 正常

普通权限由Android自动批准,而危险权限需要由Android用户批准。

这是在Android 6.0中获取危险权限的策略

  1. 检查你是否有权限
  2. 如果您的应用程序已被授予权限,请继续并正常执行。
  3. 如果您的应用还没有权限,请要求用户批准
  4. 在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

首先,我会给你在Android M和更高版本的危险的权限列表

在这里输入图像说明 在这里输入图像说明

然后举例说明如何在Android M更高版本中请求权限。

我要求用户WRITE_EXTERNAL_STORAGE权限。

首先在你的android menifest文件中添加权限

步骤1声明requestcode

  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(); } } } } 

configuration更改的特殊情况

当我们的权限对话框处于前台时,用户可能会旋转设备或者触发configuration更改。 由于我们的活动在对话框后面仍然可见,所以我们被销毁并重新创build…但我们不想再次重新提高权限对话框。

这就是为什么我们有一个名为isInPermission的布尔值,它跟踪我们是否正在请求权限。 我们在onSaveInstanceState()保持这个值:

 @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putBoolean(STATE_IN_PERMISSION, isInPermission); } 

我们在onCreate()恢复它。 如果我们没有拥有所有期望的权限,但是InPermission为true,则我们跳过请求权限,因为我们已经在这么做了。

Android改变了权限与Android 6.0的工作原理,这是你的错误。 您必须实际请求并检查权限是否被用户授予使用。 因此清单文件中的权限将只适用于21以下的api。请查看此链接,了解如何在api23中请求权限。http ://android-developers.blogspot.nl/2015/09/google-play-services-81-和机器人-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(); } } } } 

也许你不能在你的项目中使用生成的代码清单类。 所以,你可以使用android sdk“android.Manifest.permission.WRITE_EXTERNAL_STORAGE”的manifest类。 但在Marsmallow版本中有2个权限必须授予在存储类别中的WRITE和READ EXTERNAL STORAGE。 看到我的程序,我的程序将要求权限,直到用户select是,并授予权限后做一些事情。

  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 } 

对。 所以我终于到了问题的底部:这是一个糟糕的就地OTA升级。

我的Garmin Fenix 2无法通过蓝牙连接,并在search“棉花糖升级问题”之后,我的怀疑激化了。 无论如何,“出厂重置”解决了这个问题。

令人惊讶的是,重置没有把手机还给原来的Kitkat; 相反,擦除进程拿起OTA下载6.0升级包,并运行它,导致(我猜)在一个“清洁”升级。

当然,这意味着手机丢失了我安装的所有应用程序。 但是,新安装的应用程序,包括我的,没有任何改变(即有向后兼容性)。 呼!

关于Manifest.permission.Manifest.permission.WRITE_EXTERNAL_STORAGE的Android文档声明:

从API级别19开始,读取/写入由getExternalFilesDir(String)和getExternalCacheDir()返回的特定于应用程序的目录中的文件不需要此权限。


认为这意味着您不必为WRITE_EXTERNAL_STORAGE权限的运行时实现编写代码,除非应用程序正在写入不是特定于您的应用程序的目录。

您可以按照以下权限在清单中定义最大sdk版本:

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

另外请确保更改build.graddle中的目标SDK而不是清单,gradle设置将始终覆盖清单设置。

 android { compileSdkVersion 23 buildToolsVersion '23.0.1' defaultConfig { minSdkVersion 17 targetSdkVersion 22 }