碎碎念
本來每次安卓版本升級都是非常期待的事情,但是開發者就吃苦了!!!
尤其是從Q開始,應用采用沙盒模式,即各種公共文件的訪問都會受到限制。。。
所以適配Q成了當務之急,然鵝網上關於適配的資料少之又少(可能是我太菜了)
主要出現的問題:
根據圖片的絕對路徑無法正常加載圖片,同時使用File.delete刪除也是失效
直到我看到oppo開發者平台的開發指南:Android Q版本應用兼容性適配指導,才解決了這個問題!
特此記錄一下。
權限申請(都是權限惹的禍)
安卓6.0以上動態申請權限,這里就寫簡單一點:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
動態申請:
private void checkPermission() { int readExternalStoragePermissionResult = checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE); if(readExternalStoragePermissionResult != PackageManager.PERMISSION_GRANTED) { requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},1); } }
遍歷圖片
這里仍然使用ContentProvider來進行圖片的獲取。
首先我們要確定我們需要的內容,大概是圖片路徑、圖片顯示名稱、圖片ID、圖片創建時間。
創建相應的Bean類:
public class PhotoBean { private String path; private String name; private int ID; private long createDate; public int getID() { return ID; } public void setID(int ID) { this.ID = ID; } public String getPath() { return path; } public void setPath(String path) { this.path = path; } public String getName() { return name; } public void setName(String name) { this.name = name; } public long getCreateDate() { return createDate; } public void setCreateDate(long createDate) { this.createDate = createDate; } }
遍歷圖片:
private List<PhotoBean> mPics = new ArrayList<>(); private void initData(){ mPics.clear(); ContentResolver contentResolver = getContentResolver(); Uri uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; Cursor query = contentResolver.query(uri,new String[]{ MediaStore.Images.Media.DATA, MediaStore.Images.Media.DISPLAY_NAME, MediaStore.Images.Media.DATE_ADDED, MediaStore.Images.Media._ID},null,null,null,null); while(query.moveToNext()) { PhotoBean photoItem = new PhotoBean(); photoItem.setPath(query.getString(0)); //這里的下標跟上面的query第一個參數對應,時間是第2個,所以下標為1 photoItem.setCreateDate(query.getLong(1)); photoItem.setName(query.getString(2)); photoItem.setID(query.getInt(query.getColumnIndex(MediaStore.MediaColumns._ID))); mPics.add(photoItem); } query.close(); }
這樣我們就取到了相冊所有圖片的信息,主要是查到這個ID
此時我們如果直接使用path來創建Bitmap去加載或者File、第三方框架均不能正確加載圖片。
下面講一下如何使用Uri來加載圖片
獲取Uri並加載圖片
我們可以在PhotoBean中增加這個方法
public Uri getUri(){ Uri baseUri = Uri.parse("content://media/external/images/media"); return Uri.withAppendedPath(baseUri, "" + ID); }
如果沒有獲取ID,只有photpath也是可以的,但影響效率:需要根據path再去查一遍
public static Uri getImageContentUri(Context context, String path) { Cursor cursor = context.getContentResolver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, new String[] { MediaStore.Images.Media._ID }, MediaStore.Images.Media.DATA + "=? ", new String[] { path }, null); if (cursor != null && cursor.moveToFirst()) { int id = cursor.getInt(cursor.getColumnIndex(MediaStore.MediaColumns._ID)); Uri baseUri = Uri.parse("content://media/external/images/media"); return Uri.withAppendedPath(baseUri, "" + id); } else { if (new File(path).exists()) { ContentValues values = new ContentValues(); values.put(MediaStore.Images.Media.DATA, path); return context.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); } else { return null; } } }
使用第三方控件如Glide加載
拿到Uri以后就可以直接使用
Glide.with(context).load(photoBeanList.get(position).getUri()).into(imageView);
這樣就沒有問題了。
選擇圖片並拷貝到私有目錄下進行加載
那么,既然安卓Q針對應用私有數據不受任何限制,那么我們可以提前把用戶選擇的圖片拷貝一份到自己的私有目錄下,那么直接進行讀取刪除操作就不會受到限制了。
選擇一張圖片:
Intent intent = new Intent(Intent.ACTION_PICK); intent.setType("image/*"); startActivityForResult(intent, 2);
在onActivityResult中接收
if (data != null) { Uri originalUri = data.getData(); // 獲得圖片的uri String path= ImageHelper.getPrivatePath(SettingActivity.this,originalUri); if(path!=null&&!path.equals("")){ //TODO } }
getPrivatePath的操作就是將文件拷貝一份到私有目錄下並返回絕對路徑
/** * 根據Uri直接獲取圖片 * @param context 上下文 * @param uri 圖片的uri * */ public static String getPrivatePath(Context context,Uri uri){ try { Bitmap bitmap = MediaStore.Images.Media.getBitmap(context.getContentResolver(), uri); File file=compressImage(context,bitmap); return file.getAbsolutePath(); } catch (IOException e) { e.printStackTrace(); } return ""; }
compress是精簡的意思,不知道我怎么想的就寫成了這個,自己注意一下
/** * 把bitmap寫入app私有目錄下 * @param context 上下文 * @param bitmap 這個bitmap不能為null * @return File * 適配到4.4 * */ private static File compressImage(Context context, Bitmap bitmap) { String filename; ByteArrayOutputStream baos = new ByteArrayOutputStream(); bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);//質量壓縮方法,這里100表示不壓縮,把壓縮后的數據存放到baos中 if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N){ SimpleDateFormat format = new SimpleDateFormat("yyyyMMddHHmmss"); Date date = new Date(System.currentTimeMillis()); //圖片名 filename = format.format(date); }else { Date date=new Date(); filename=date.getYear()+date.getMonth()+date.getDate()+date.getHours()+date.getMinutes()+date.getSeconds()+""; } final File[] dirs = context.getExternalFilesDirs("Documents"); File primaryDir = null; if (dirs != null && dirs.length > 0) { primaryDir = dirs[0]; } File file = new File(primaryDir.getAbsolutePath(), filename + ".png"); try { FileOutputStream fos = new FileOutputStream(file); try { fos.write(baos.toByteArray()); fos.flush(); fos.close(); } catch (IOException e) { e.printStackTrace(); } } catch (FileNotFoundException e) { e.printStackTrace(); } // recycleBitmap(bitmap); return file; }
后面如果正常使用的話就是直接用path加載即可。
/** * 根據私有路徑加載 * @param context 上下文 * @param path 這個路徑一定是私有路徑,即應用自己的目錄下(data/包名) * @return Drawable 用來設置背景什么的 * */ public static Drawable getByPrivatePath(Context context,String path){ Bitmap bitmap = BitmapFactory.decodeFile(path); Drawable drawable = new BitmapDrawable(context.getResources(), bitmap); return drawable; }
圖片刪除操作
以前我們在刪除的時候,需要開發者自己添加一個確認刪除的功能,現在谷歌已經幫我們完成了。
File.delete也就失效了,相對來說比較安全吧。
同樣的,我們需要使用Uri來進行操作。
@TargetApi(29) public void deleteUri(Uri imageUri) { ContentResolver resolver = getContentResolver(); OutputStream os = null; try { if (imageUri != null) { resolver.delete(imageUri,null,null); } } catch (RecoverableSecurityException e1) { Log.d(TAG,"get RecoverableSecurityException"); try { this.startIntentSenderForResult( e1.getUserAction().getActionIntent().getIntentSender(), 100, null, 0, 0, 0); } catch (IntentSender.SendIntentException e2) { Log.d(TAG,"startIntentSender fail"); } } }
RecoverableSecurityException在谷歌文檔中是這樣解釋的:This exception is only appropriate where there is a concrete action the user can take to recover and make forward progress, such as confirming or entering authentication credentials, or granting access.即對於圖片的修改、刪除操作都需要用戶的允許,即也是一種權限,故需要拋出該異常並去申請獲得該權限。
如圖所示:
總結
圖片在加載的過程中,有一些圖片能用uri查到,但是通過uri獲取圖片拋出文件不存在異常,故完善一下代碼。
這里提供的解決思路是將選擇的圖片拷貝一份到私有目錄,這樣無論是讀取還是修改圖片都不會受到影響。
public class ImageHelper { /** * 通過絕對路徑獲取圖片的私有存儲路徑 * 將圖片復制到私有目錄下,下次加載、刪除啥的就沒有影響了 * 但是注意刪除的僅是app私有的數據,並不是真正刪除相冊的圖片 * 存在部分圖片能查到uri但是無法正常加載,故需要判斷一下 * 如果返回路徑為空,則跳過該圖片,並提示用戶手動在系統相冊中將圖片添加至相冊再重試 * @param context 上下文 * @param path 圖片絕對路徑(直接獲取到的) * @return String 返回一個復制到私有路徑下相同的圖片路徑 * */ public static String coverFromBitmap(Context context, String path){ Bitmap bitmap=SuperSuitWay(context,path); if(bitmap==null){ return ""; } return compressImage(context,bitmap).getAbsolutePath(); } /** * 把bitmap寫入app私有目錄下 * @param context 上下文 * @param bitmap 這個bitmap不能為null * @return File * */ private static File compressImage(Context context,Bitmap bitmap) { String filename; ByteArrayOutputStream baos = new ByteArrayOutputStream(); bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);//質量壓縮方法,這里100表示不壓縮,把壓縮后的數據存放到baos中 SimpleDateFormat format = new SimpleDateFormat("yyyyMMddHHmmss"); Date date = new Date(System.currentTimeMillis()); //圖片名 filename = format.format(date); final File[] dirs = context.getExternalFilesDirs("Documents"); File primaryDir = null; if (dirs != null && dirs.length > 0) { primaryDir = dirs[0]; } File file = new File(primaryDir.getAbsolutePath(), filename + ".png"); try { FileOutputStream fos = new FileOutputStream(file); try { fos.write(baos.toByteArray()); fos.flush(); fos.close(); } catch (IOException e) { e.printStackTrace(); } } catch (FileNotFoundException e) { e.printStackTrace(); } // recycleBitmap(bitmap); return file; } /** * 通過絕對路徑獲取bitmap * 適配安卓Q使用絕對路徑無法正確加載的問題 * @param context 上下文 * @param path 絕對路徑 * @return Bitmap * */ private static Bitmap SuperSuitWay(Context context,String path){ Uri external = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; Cursor cursor = context.getContentResolver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, new String[] { MediaStore.Images.Media._ID }, MediaStore.Images.Media.DATA + "=? ", new String[] { path }, null); Uri imageUri = null; if (cursor != null && cursor.moveToFirst()) { imageUri = ContentUris.withAppendedId(external, cursor.getLong(0)); cursor.close(); } ParcelFileDescriptor pfd = null; if (imageUri != null) { try { pfd = context.getContentResolver().openFileDescriptor(imageUri, "r"); if (pfd != null) { Bitmap bitmap = BitmapFactory.decodeFileDescriptor(pfd.getFileDescriptor()); return bitmap; } } catch (IOException e) { e.printStackTrace(); } finally { try { if (pfd != null) { pfd.close(); } } catch (IOException e) { e.printStackTrace(); } } } return null; } }
---------------------------------------------------------------------------------------------------------------------
啊,自己好菜_(¦3」∠)_
覺得文章好的歡迎點贊哦~
這里推薦一篇文章,也是最近我找到的,拜讀一下:點我跳轉