2015-10-15 92 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