【Android】安卓Q適配指南-相冊


碎碎念

本來每次安卓版本升級都是非常期待的事情,但是開發者就吃苦了!!!

尤其是從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」∠)_

覺得文章好的歡迎點贊哦~

這里推薦一篇文章,也是最近我找到的,拜讀一下:點我跳轉

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM