藉助系統自帶圖片裁剪集成圖片選擇以及7.0適配

1、前文

  以前使用的圖片裁剪功能一直是使用第三方的,也沒時間去思考本身寫一個的想法。後來無心間發現android本身原本就有裁剪功能,因此本身動手去集成了一把,而且把本身的權限封裝以及7.0的適配都加進去php

2、注意的幾個點

  其實也沒有什麼好說的,基本沒有難度,只是有幾個須要注意的點
1.一個是7.0的文件安全機制,7.0以後android對於文件的安全增長了保護,在部分地方使用Uri會產生FileUriExposedException文件暴露異常。
2.其次,就是對於權限的封裝,只有拿到了權限才能進行操做,作好權限適配,這些下面會一併講到。
java

3、權限封裝

  權限封裝最好封裝到一個方法裏面,獨立處理權限,這樣也能夠比較輕鬆的集成在BaseActivity裏面,在用的地方直接調,能夠達到權限隨用隨取得效果。先看看關鍵代碼android

public void needPermission(AppPermissionListener mAppPermissionListener, List<String> permissions) {
        if (null != permissions && permissions.size() > 0) {
            this.appPermissionListener = mAppPermissionListener;
            this.allPermission = permissions;
            // 容許的權限
            allowPermission.clear();
            // 被拒絕的權限
            deniedPermission.clear();
            // 不在詢問的權限
            neverAskPermission.clear();
            // 開始遍歷拿到的權限
            Observable.fromIterable(allPermission)
                    .subscribe(new Consumer<String>() {
                        @Override
                        public void accept(String s) throws Exception {
                            // 判斷是否有WRITE_SETTINGS特殊權限
                            if (s.equals(Manifest.permission.WRITE_SETTINGS)) {
                                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
	                                // WRITE_SETTINGS須要用該方式判斷
                                    if (!Settings.System.canWrite(mActivity))
                                        allowPermission.add(s);
                                    else
                                        haveWriteSetting = true;
                                }
                            } else if (ActivityCompat.checkSelfPermission(mActivity, s)         
                            == PackageManager.PERMISSION_GRANTED) {
                                allowPermission.add(s);
                            } else {
                                deniedPermission.add(s);
                            }
                        }
                    });
            if (haveWriteSetting) {
	            // 請求特殊權限
                requestWriteSettings();
                return;
            }
            if (!deniedPermission.isEmpty()) {
                String[] tempArray = new String[deniedPermission.size()];
                deniedPermission.toArray(tempArray);
                requestPermissions(tempArray);
                if (null != mAppPermissionListener) {
                    mAppPermissionListener.onHaveDenied(deniedPermission);
                }
                return;
            }
            if (!neverAskPermission.isEmpty()) {
                showNeverAskDialog();
                if (null != mAppPermissionListener) {
                    mAppPermissionListener.onNeverAsk(neverAskPermission);
                }
                return;
            }
            // 權限經過,回調onAllGranted方法
            if (null != mAppPermissionListener) {
                mAppPermissionListener.onAllGranted();
            }
        }
    }
複製代碼

android有兩個特殊權限,一個是WRITE_SETTINGS,另一個是SYSTEM_ALERT_WINDOW,這裏只對WRITE_SETTINGS作了處理,我在想有沒有更優雅的方法把SYSTEM_ALERT_WINDOW加進去,因此後面會完善。   在這裏處理了會觸發拒絕權限的彈框。可是當用戶點擊了再也不詢問權限,就不會再彈框。android原生給咱們提供了shouldShowRequestPermissionRationale()方法來判斷是否再也不彈框。可是 這個方法會在部分手機上失效,好比我手中的魅族pro6手機,可能因爲ROM問題,在調用該方法的時候就是失效的。那麼,咱們能夠用另一種方法來達到相同的目的。
  咱們能夠直接調用requestPermissions方法,在onRequestPermissionsResult的權限回調方法中進行檢查,若是拒絕權限列表中還有這個權限的話,就能夠進行彈框引導用戶去設置中手動打開。不但替代了shouldShowRequestPermissionRationale方法,並且在用戶一通拒絕以後還能給予提醒。下面是onRequestPermissionsResult方法的代碼
git

// 權限返回結果
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        if (requestCode == PERMISSION_REQUEST_CODE) {
            for (int i = 0; i < permissions.length; i++) {
                String per = permissions[i];
                if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {
                    if (deniedPermission.contains(per)) {
                        deniedPermission.remove(per);
                    } else if (neverAskPermission.contains(per)) {
                        neverAskPermission.remove(per);
                    }
                    if (!allowPermission.contains(per))
                        allowPermission.add(per);
                } else {
                    if (deniedPermission.contains(per)) {
                        deniedPermission.remove(per);
                        neverAskPermission.add(per);
                    }
                }
            }
            if (!deniedPermission.isEmpty()) {
                neverAskPermission.addAll(new ArrayList<>(deniedPermission));
                deniedPermission.clear();
            }
            if (!neverAskPermission.isEmpty()) {
                showNeverAskDialog();
                if (null != appPermissionListener) {
                    appPermissionListener.onNeverAsk(neverAskPermission);
                }
                return;
            }
            if (null != appPermissionListener) {
                appPermissionListener.onAllGranted();
            }
        }
    }
複製代碼

4、7.0文件安全機制

  很多安卓應用開發的程序員,一直都不多有機會接觸到安卓四大組件之一的Content Provider,可是因爲android7.0文件安全機制的限制,使咱們不得不去接觸這個組件。我我的以爲這也是一件好事情。不用再只侷限於部分組件的開發。
  首先咱們須要在res文件下簡歷一個xml文件夾,而後再創建一個xml文件,這個xml文件名字本身能夠隨便命名,這裏我命名爲file_path。以下圖所示程序員

xml文件位置

  file_path裏面的內容有事有講究的。裏面有一個root-path 節點,雖然說android會提示element roo-path is not allowed here。可是我目前尚未找到在不用這個節點的時候,可以正常運行的手段。若是有哪位大神知道,能夠指出。github

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <files-path name="my_images" path="path/" />
    <cache-path name="my_cache" path="path/" />
    <external-files-path name="my_file" path="path/" />
    <external-path name="myApp_file" path="path/" />
    <external-cache-path name="myApp_cache" path="path/" />
    <root-path name="myApp" path="" />
</paths>
複製代碼

而後,咱們須要創建一個本身的provider。這樣在問題出現的時候有利於查找問題。最後,咱們須要在Manifest裏面聲名這個Provdier。以下所示安全

<provider android:name="android.support.v4.content.FileProvider" android:authorities="${applicationId}.common.MyImageFileProvider" android:exported="false" android:grantUriPermissions="true">
            <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_path" />
        </provider>
複製代碼

這裏使用了${applicationId}來代替包名很方便,可是在java代碼中仍是須要寫徹底。 接下來講使用。使用起來就會變得很簡單,一句代碼就能夠了app

FileProvider.getUriForFile(context, DEFAULT_AUTHORITIES, imageFile)
複製代碼

這個方法會返回Uri對象,在須要的地方進行使用。ide

5、如何調用相機、圖庫以及裁剪工具

相機和圖庫的調用,網上其實已經在不少地方寫爛了,我這裏直接貼出個人方法工具

/** * 打開圖庫 */
    private void openGallery() {
        initImageFile();
        Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
        intent.setType("image/*");
        ((Activity) context).startActivityForResult(intent, REQUEST_PICK);
    }

    /** * 開啓攝像頭 */
    private void doPhoto() {
        initImageFile();
        Uri uri = FileProvider.getUriForFile(context, DEFAULT_AUTHORITIES, imageFile);
        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
        intent.putExtra(MediaStore.EXTRA_SCREEN_ORIENTATION, 
	        ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
        ((Activity) context).startActivityForResult(intent, REQUEST_PICK);
    }
複製代碼

接下來,在打開系統自帶裁剪工具的時候會有個坑,也是android 7.0的文件安全機制的問題。不過這個和以前的文件安全機制不同。使用FileProvider會提示"沒法加載該圖片"。經過找到該圖片路徑能夠了解到,android7.0的文件安全機制實際上是但願能作成相似IOS沙盒機制的效果,每一個app只能訪問本身的沙盒,可是從系統相冊拿到的圖片卻不屬於當前app自己,因此咱們這時候就須要一個臨時受權。intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
調用系統裁剪功能的代碼以下

private void beginCropDirect(Uri uri) {
        Intent intent = new Intent("com.android.camera.action.CROP");
        //添加這一句表示對目標應用臨時受權該Uri所表明的文件
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        //能夠選擇圖片類型,若是是*代表全部類型的圖片
        intent.setDataAndType(uri, "image/*");
        // 下面這個crop = true是設置在開啓的Intent中設置顯示的VIEW可裁剪
        intent.putExtra("crop", "true");
        // aspectX aspectY 是寬高的比例,這裏設置的是正方形(長寬比爲1:1)
        intent.putExtra("aspectX", 1);
        intent.putExtra("aspectY", 1);
        // outputX outputY 是裁剪圖片寬高
        intent.putExtra("outputX", 500);
        intent.putExtra("outputY", 500);
        intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
        //是否將數據保留在Bitmap中返回,true返回bitmap,false返回uri
        intent.putExtra("return-data", false);
        ((Activity) context).startActivityForResult(intent, REQUEST_CROP);
    }
複製代碼

本文代碼:https://github.com/Kongdy/ImageCropBySystem 我的github地址:https://github.com/Kongdy 我的掘金主頁:https://juejin.im/user/595a64def265da6c2153545b csdn主頁:http://blog.csdn.net/u014303003

相關文章
相關標籤/搜索