2015-10-15 94 views
49

我有一个应用程序使用外部存储来存储照片。根据需要,在其清单,下面的权限要求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;它已在谷歌播放了一年多。

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

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

此外,由于存储空间有限或应用程序或您的组织不允许,内置的android截图功能(通过Power + Lower volume)失败。

任何想法?

+0

你可以发布你的Logcat吗? –

+1

是你的'targetSdkVersion' 23吗?还是早期版本? – ianhanniballake

+1

logcat中没有什么异常,大概是因为“错误”被应用程序困住了。 RudyF

回答

7

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

问题是,该应用没有针对api 23.它只是在6.0上运行。不应该有向后兼容?另外,使用SD卡的内置应用程序(如Camera(2.7.008))也会因“无SD卡可用”而失败。如果我通过“设置 - >应用程序”查看“应用程序权限”,它会显示个人权限的新粒度(例如相机,存储等),所有权限均已正确授予。 – RudyF

+0

另一件事:当配置为“便携式存储”时,SD卡图标在通知区域保持可见。为什么? – RudyF

+0

安装了流行的相机应用程序,打开相机(100万次下载)...它也无法保存照片。我开始怀疑棉花糖是否需要SD卡中的特定规格(如果是这样,为什么它接受我试过的那些?)。 – RudyF

49

我面临同样的问题。有两种类型的权限在安卓

  • 危险(访问联系人,编写到外部存储......)
  • 正常

普通权限由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

+6

没错。我知道如果我的应用定位到6.0,那么会有不同的安全协议。但让我们退一步再看看问题。我所做的只是让我的Android One手机升级到6.0(就像我之前从4.4到5.0,然后是5.1)。而且,繁荣,写入SD的应用程序停止工作? [请注意,这些应用未针对API 23] – RudyF

+1

您是否尝试将compileSdkVersion和targetSdkVersion设置为低于23?我已经完成了,我的应用在Android 6.0上运行良好 – codingpuss

+1

我正在将Eclipse的sdk升级到API23(Marshmallow),之后我计划将新的权限协议合并到我的应用中。但是,这并不能解决我手机的问题:在升级到6.0后,一些已安装的应用程序停止工作。现在这款手机似乎受到迟迟开发商的支配(像我一样)。坦率地说,这只是没有意义......在6.0中只需要有一些“向后兼容性调整”。 [就目前而言,差不多1%的用户已经升级到6.0 ...我必须快速移动:)] – RudyF

3

对。所以我终于明白了这个问题的底部:这是一个拙劣的就地OTA升级。

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

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

当然,这意味着手机会丢失我安装的所有应用程序。但是,新安装的应用程序(包括我的)在没有任何更改的情况下工作(即存在向后兼容性)。呼!

+0

因此,通过更新我的应用程序以部署API23并试图实现6的权限处理,我更加困惑。由于我已经从6位用户那里获得了几百次下载(我目前的目标是api21),我想知道是否应该打扰,如果他们真的使用我的应用程序很好吗? 这是一个真正的任务,试图解决23中所有的废弃问题,包括库代码,恐怕我会实施一些从SO上的各种示例入侵并引起用户问题,可能导致不良评论。 但是我觉得我现在太过分了,现在回到<23但是。 –

+0

我的项目的构建目标是20,并且事情看起来很顺利,因为(a)该应用在我目前升级到6.0的Android One手机上工作正常,(b)几百个6用户没有实际投诉。因此,我看不出任何迫切的理由来修改我的代码。当我这样做时,我可能会(a)首先摆弄我最不受欢迎的应用程序,并且(b)通过少量周。祝一切顺利。 – RudyF

3

Android Documentation on Manifest.permission.Manifest.permission.WRITE_EXTERNAL_STORAGE states:

API级别19开始,此权限是由getExternalFilesDir(串)和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 
} 
+1

+1帮助了我很多,但它不应该读取:'<使用权限android:name =“android.permission.WRITE_EXTERNAL_STORAGE”android:maxSdkVersion =“18”/>'(而不是19),即你正在如果使用getExternalFilesDir(String)和getExternalCacheDir(),则不再需要API级别19中允许的“旧方式”权限。 – kalabalik

6

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

  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 
      } 
19

首先,我会给你风险的权限列表中版本的Android M版及更高版本

enter image description hereenter image description here

然后给你举例如何在版本的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; 
     } 
    } 
} 

注:不要忘了在menifest文件中添加权限

BEST下面的例子具有多项权限PLUS覆盖所有情景

我添加评论,以便您可以轻松理解。

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为true,则我们跳过请求权限,因为我们已经在 的中间。

+1

如何在非活动中执行此操作? – Prabs

+0

这可能有助于http://stackoverflow.com/questions/38480671/android-m-onrequestpermissionsresult-in-non-activity –

+0

这是一个令人头痛的@Arpit。我已将课程更改为活动,并删除了'setcontentview',因为我需要在同一课程中使用异步任务。感谢您的搜索 – Prabs