1. 程式人生 > >呼叫系統相機、相簿、剪裁圖片並上傳(常用於上傳頭像,相容Android7.0)

呼叫系統相機、相簿、剪裁圖片並上傳(常用於上傳頭像,相容Android7.0)

轉載請註明出處文章地址
本文轉自Hansion的部落格

由於在Android 7.0 採用了StrictMode API政策禁,其中有一條限制就是對目錄訪問的限制。

這項變更意味著我們無法通過File API訪問手機儲存上的資料,也就是說,給其他應用傳遞 file:// URI 型別的Uri,可能會導致接受者無法訪問該路徑,並且會會觸發 FileUriExposedException異常。

StrictMode API政策禁中的應用間共享檔案就是對上述限制的應對方法,它指明瞭我們在在應用間共享檔案可以傳送 content:// URI型別的Uri,並授予 URI 臨時訪問許可權,即使用FileProvider

接下來,我們使用FileProvider實現呼叫系統相機、相簿、剪裁圖片的功能相容Android 7.0

第一步:FileProvider相關準備工作

  • 在AndroidManifest.xml中增加provider節點,如下:
        <provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="com.hansion.chosehead"
            android:grantUriPermissions
="true" android:exported="false">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/filepaths" /> </provider>

其中:
android:authorities 表示授權列表,填寫你的應用包名,當有多個授權時,用分號隔開
android:exported

表示該內容提供器(ContentProvider)是否能被第三方程式元件使用,必須為false,否則會報異常:ava.lang.RuntimeException: Unable to get provider android.support.v4.content.FileProvider: java.lang.SecurityException: Provider must not be exported
android:grantUriPermissions=”true” 表示授予 URI 臨時訪問許可權
android:resource 屬性指向我們自及建立的xml檔案的路徑,檔名隨便起

  • 接下來,我們需要在資源(res)目錄下建立一個xml目錄,並建立一個以上面名字為檔名的xml檔案,內容如下:
<?xml version="1.0" encoding="utf-8"?>
<paths>
    <external-path path="" name="image" />
</paths>

其中:
external-path 代表根目錄為: Environment.getExternalStorageDirectory() ,也可以寫其他的,如:
files-path 代表根目錄為:Context.getFilesDir()
cache-path 代表根目錄為:getCacheDir()
path屬性的值代表路徑後層級名稱,為空則代表就是根目錄,假如為“pictures”,就代表對應根目錄下的pictures目錄

第二步:使用FileProvider

  • 在這之前,我們需要在AndroidManifest.xml中增加必要的讀寫許可權:
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

1. 通過相機獲取圖片

在通過Intent跳轉系統相機前,我們需要對版本進行判斷,如果在Android7.0以上,使用FileProvider獲取Uri,程式碼如下:

    /**
     * 從相機獲取圖片
     */
    private void getPicFromCamera() {
        //用於儲存呼叫相機拍照後所生成的檔案
        tempFile = new File(Environment.getExternalStorageDirectory().getPath(), System.currentTimeMillis() + ".jpg");
        //跳轉到呼叫系統相機
        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        //判斷版本
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {   //如果在Android7.0以上,使用FileProvider獲取Uri
            intent.setFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
            Uri contentUri = FileProvider.getUriForFile(MainActivity.this, "com.hansion.chosehead", tempFile);
            intent.putExtra(MediaStore.EXTRA_OUTPUT, contentUri);
        } else {    //否則使用Uri.fromFile(file)方法獲取Uri
            intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(tempFile));
        }
        startActivityForResult(intent, CAMERA_REQUEST_CODE);
    }

如果你好奇通過FileProvider獲取的Uri是什麼樣的,可以打印出來看一看,例如:

content://com.hansion.chosehead/image/1509356493642.jpg

其中:
com.hansion.chosehead 是我的包名
image 是上文中xml檔案中的name屬性的值
1509356493642.jpg 是我建立的圖片的名字
也就是說,content://com.hansion.chosehead/image/ 代表的就是根目錄

2.通過相簿獲取圖片

    /**
     * 從相簿獲取圖片
     */
    private void getPicFromAlbm() {
        Intent photoPickerIntent = new Intent(Intent.ACTION_PICK);
        photoPickerIntent.setType("image/*");
        startActivityForResult(photoPickerIntent, ALBUM_REQUEST_CODE);
    }

3.剪裁圖片

    /**
     * 裁剪圖片
     */
    private void cropPhoto(Uri uri) {
        Intent intent = new Intent("com.android.camera.action.CROP");
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
        intent.setDataAndType(uri, "image/*");
        intent.putExtra("crop", "true");
        intent.putExtra("aspectX", 1);
        intent.putExtra("aspectY", 1);

        intent.putExtra("outputX", 300);
        intent.putExtra("outputY", 300);
        intent.putExtra("return-data", true);

        startActivityForResult(intent, CROP_REQUEST_CODE);
    }

上文中,你會發現,我們只對Uri進行了特殊處理。沒錯,這就是核心變化

第三步:接收圖片資訊

  • 我們在onActivityResult方法中獲得返回的圖片資訊,在這裡我們會先呼叫剪裁去剪裁圖片,然後對剪裁返回的圖片進行設定、儲存、上傳等操作
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
        switch (requestCode) {
            case CAMERA_REQUEST_CODE:   //呼叫相機後返回
                if (resultCode == RESULT_OK) {
                    //用相機返回的照片去呼叫剪裁也需要對Uri進行處理
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                        Uri contentUri = FileProvider.getUriForFile(MainActivity.this, "com.hansion.chosehead", tempFile);
                        cropPhoto(contentUri);
                    } else {
                        cropPhoto(Uri.fromFile(tempFile));
                    }
                }
                break;
            case ALBUM_REQUEST_CODE:    //呼叫相簿後返回
                if (resultCode == RESULT_OK) {
                    Uri uri = intent.getData();
                    cropPhoto(uri);
                }
                break;
            case CROP_REQUEST_CODE:     //呼叫剪裁後返回
                Bundle bundle = intent.getExtras();
                if (bundle != null) {
                    //在這裡獲得了剪裁後的Bitmap物件,可以用於上傳
                    Bitmap image = bundle.getParcelable("data");
                    //設定到ImageView上
                    mHeader_iv.setImageBitmap(image);
                    //也可以進行一些儲存、壓縮等操作後上傳
//                    String path = saveImage("crop", image);
                }
                break;
        }
    }
  • 儲存Bitmap到本地的方法:
    public String saveImage(String name, Bitmap bmp) {
        File appDir = new File(Environment.getExternalStorageDirectory().getPath());
        if (!appDir.exists()) {
            appDir.mkdir();
        }
        String fileName = name + ".jpg";
        File file = new File(appDir, fileName);
        try {
            FileOutputStream fos = new FileOutputStream(file);
            bmp.compress(Bitmap.CompressFormat.PNG, 100, fos);
            fos.flush();
            fos.close();
            return file.getAbsolutePath();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

至此,對Android7.0的相容就結束了
總結一下,在呼叫相機和剪裁時,傳入的Uri需要使用FileProvider來獲取

完整程式碼:

  • MainActivity.java
public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private ImageView mHeader_iv;

    //相簿請求碼
    private static final int ALBUM_REQUEST_CODE = 1;
    //相機請求碼
    private static final int CAMERA_REQUEST_CODE = 2;
    //剪裁請求碼
    private static final int CROP_REQUEST_CODE = 3;

    //呼叫照相機返回圖片檔案
    private File tempFile;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initView();
    }

    private void initView() {
        mHeader_iv = (ImageView) findViewById(R.id.mHeader_iv);
        Button mGoCamera_btn = (Button) findViewById(R.id.mGoCamera_btn);
        Button mGoAlbm_btn = (Button) findViewById(R.id.mGoAlbm_btn);
        mGoCamera_btn.setOnClickListener(this);
        mGoAlbm_btn.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.mGoCamera_btn:
                getPicFromCamera();
                break;
            case R.id.mGoAlbm_btn:
                getPicFromAlbm();
                break;
            default:
                break;
        }
    }


    /**
     * 從相機獲取圖片
     */
    private void getPicFromCamera() {
        //用於儲存呼叫相機拍照後所生成的檔案
        tempFile = new File(Environment.getExternalStorageDirectory().getPath(), System.currentTimeMillis() + ".jpg");
        //跳轉到呼叫系統相機
        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        //判斷版本
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {   //如果在Android7.0以上,使用FileProvider獲取Uri
            intent.setFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
            Uri contentUri = FileProvider.getUriForFile(MainActivity.this, "com.hansion.chosehead", tempFile);
            intent.putExtra(MediaStore.EXTRA_OUTPUT, contentUri);
            Log.e("dasd", contentUri.toString());
        } else {    //否則使用Uri.fromFile(file)方法獲取Uri
            intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(tempFile));
        }
        startActivityForResult(intent, CAMERA_REQUEST_CODE);
    }

    /**
     * 從相簿獲取圖片
     */
    private void getPicFromAlbm() {
        Intent photoPickerIntent = new Intent(Intent.ACTION_PICK);
        photoPickerIntent.setType("image/*");
        startActivityForResult(photoPickerIntent, ALBUM_REQUEST_CODE);
    }


    /**
     * 裁剪圖片
     */
    private void cropPhoto(Uri uri) {
        Intent intent = new Intent("com.android.camera.action.CROP");
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
        intent.setDataAndType(uri, "image/*");
        intent.putExtra("crop", "true");
        intent.putExtra("aspectX", 1);
        intent.putExtra("aspectY", 1);

        intent.putExtra("outputX", 300);
        intent.putExtra("outputY", 300);
        intent.putExtra("return-data", true);

        startActivityForResult(intent, CROP_REQUEST_CODE);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
        switch (requestCode) {
            case CAMERA_REQUEST_CODE:   //呼叫相機後返回
                if (resultCode == RESULT_OK) {
                    //用相機返回的照片去呼叫剪裁也需要對Uri進行處理
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                        Uri contentUri = FileProvider.getUriForFile(MainActivity.this, "com.hansion.chosehead", tempFile);
                        cropPhoto(contentUri);
                    } else {
                        cropPhoto(Uri.fromFile(tempFile));
                    }
                }
                break;
            case ALBUM_REQUEST_CODE:    //呼叫相簿後返回
                if (resultCode == RESULT_OK) {
                    Uri uri = intent.getData();
                    cropPhoto(uri);
                }
                break;
            case CROP_REQUEST_CODE:     //呼叫剪裁後返回
                Bundle bundle = intent.getExtras();
                if (bundle != null) {
                    //在這裡獲得了剪裁後的Bitmap物件,可以用於上傳
                    Bitmap image = bundle.getParcelable("data");
                    //設定到ImageView上
                    mHeader_iv.setImageBitmap(image);
                    //也可以進行一些儲存、壓縮等操作後上傳
//                    String path = saveImage("crop", image);
                }
                break;
        }
    }

    public String saveImage(String name, Bitmap bmp) {
        File appDir = new File(Environment.getExternalStorageDirectory().getPath());
        if (!appDir.exists()) {
            appDir.mkdir();
        }
        String fileName = name + ".jpg";
        File file = new File(appDir, fileName);
        try {
            FileOutputStream fos = new FileOutputStream(file);
            bmp.compress(Bitmap.CompressFormat.PNG, 100, fos);
            fos.flush();
            fos.close();
            return file.getAbsolutePath();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}
  • activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:padding="5dp"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:layout_marginTop="30dp"
        android:text="選擇頭像"
        android:textSize="18sp" />

    <ImageView
        android:id="@+id/mHeader_iv"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_gravity="center_horizontal"
        android:layout_marginTop="50dp"
        android:src="@mipmap/ic_launcher" />

    <android.support.v4.widget.Space
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"/>

    <Button
        android:id="@+id/mGoCamera_btn"
        android:text="拍照選擇"
        android:layout_marginBottom="5dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
    <Button
        android:id="@+id/mGoAlbm_btn"
        android:text="本地相簿選擇"
        android:layout_marginBottom="10dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />


</LinearLayout>

本文還有多處可優化的地方,比如Android 6.0以上動態許可權的獲取、儲存圖片前對SD卡是否可用的判斷、容錯措施等等。這些都是不屬於本文的主要內容,本文就不再多說了。
其實還有一些偏門的方法,不過本人是不提倡的,比如下文自己處理StrictMode嚴苛模式、或者將targetSdkVersion改為24以下等方法,這些都是違背開發思想的方法,最好不要使用。

        //建議在application 的onCreate()的方法中呼叫
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
            StrictMode.setVmPolicy(builder.build());
        }

原始碼下載

相關推薦

呼叫系統相機相簿剪裁圖片常用頭像相容Android7.0

轉載請註明出處文章地址 本文轉自Hansion的部落格 由於在Android 7.0 採用了StrictMode API政策禁,其中有一條限制就是對目錄訪問的限制。 這項變更意味著我們無法通過File API訪問手機儲存上的資料,也就是說,給其他應用傳

呼叫系統相機相簿並且裁剪成圓形圖片(解決6.07.08.0版本問題)

之前寫過一篇部落格,那篇部落格對7.0手機裁剪圖片的問題沒有進行解決,現在對之前的那篇部落格進行補充,解決了Android6.0,7.0,8.0版本問題,不僅可以呼叫相簿,相機,還可以將圖片儲存到本地,並且裁剪成圓形圖片 必要的許可權: <uses-permissi

iOS圖片處理呼叫系統相機相簿獲取圖片相機新增自定義覆蓋物

[摘要:起首,拍照或從相簿挑選照片須要應用 UIImagePickerController,應用時須要增加兩個協定 #import UIKit/UIKit.h @interface ViewContr

Android呼叫系統相機相簿

拍照和相簿的功能在實際開發中是最常見的功能,這裡記錄下。 準備工作 許可權 1234 <!-- 往SDCard寫入資料許可權 --> <uses-permission android:name="android.permission.WRIT

android呼叫系統相機相簿頭像

話說昨天的冰碴下得真心大,騎車回來的路上臉被打的生疼啊!清明小長假第一天,借這個時間把前兩天想記錄的一點內容補充上吧。這篇文章主要記錄呼叫系統相機或者從系統相簿中選取照片然後上傳頭像,這是一個很平常的需求,網上的例子也很多,但是,(注意:前方高能預警!!!)我遇到了一個坑,選

android之 h5呼叫系統相機相簿顯示

先上html介面的程式碼,放在assets裡面就可以了,我也不太會html,所以隨便寫了點 <!doctype html> <html lang="en"> <head> <meta charset="UT

【iOS】一個完整的簡單的呼叫系統相機相簿設定頭像

1.Xcode8,iOS10的許可權設定(不設定會崩潰): 找到專案的info.plist檔案,右鍵open As,以Source Code的形式開啟,將以下程式碼新增進去: 相機許可權設定: <key>NSCameraUsageDescription</

android 開啟攝像頭以及選取相簿照片剪裁圖片使用比例壓縮大小

        之前搜尋了各種網上的開啟攝像頭拍照和選取相簿的方式,發現大部分都不太相容、適合。因此在這裡總結一下切實可用的方法(參考第一行程式碼)         拍照使用的是FileProvider,這

Android 從相簿或者拍照設定頭像相容Android6.0後許可權問題

                                    平時開發中我們會需要設定使用者頭像  從手機相簿或者拍照設定,在Android6.0以前還不用考慮許可權問題,到了6.0後還需要考慮許可權問題,這裡我們就來給大家一起處理下。有些可能不完善大家自己完善

Android呼叫系統相機相簿裁剪圖片壓縮適配7.0

作者:八怪不姓醜 連結:http://www.jianshu.com/p/e11a34e2ea4f 著作權歸作者所有,本文經作者授權推送。 一、前言 最近在開發中遇到了一個比較棘手的問題 由於在之前使用的版本-targetSdkVersion小於24也就是小於7.

呼叫系統相機相簿選擇圖片

相信很多學習android的朋友,都和我一樣連一個簡單的獲取手機圖片的無從知曉,於是藉助於百度尋求幫助,於是乎有感寫,這篇部落格不僅僅是鞏固自身的基礎知識,希望能幫助到其他人。 Uri mPhotoUri; private static fin

SimplePhotoPicker:呼叫系統相機相簿的輕量框架

SimplePhotoPicker 一個進入相簿,呼叫相機的可高度定製化的框架,持續維護中… 效果圖 那些你所擔心但是已經幫你解決的問題 1.Android6.0**隱私許可權**請求問題—>已判斷並處理 2.Android7.0 Fi

Android呼叫系統相機自定義相機處理大圖片

Android呼叫系統相機和自定義相機例項 本博文主要是介紹了android上使用相機進行拍照並顯示的兩種方式,並且由於涉及到要把拍到 的照片顯示出來,該例子也會涉及到Android載入大圖片時候的處理(避免OOM),還有簡要提一下有些人SurfaceView出現黑屏的原因。 An

Android7.0呼叫系統相機拍照訪問相簿問題。

關於Android7.0呼叫系統相機拍照、訪問相簿的一些問題: 在Android6.0中Google提出了動態申請許可權的Api,呼叫相機拍照,訪問SDcard等操作都需要先申請對應的許可權如下: <uses-permission android:name="and

Android完美呼叫系統相機相簿以及裁剪功能

在 Android應用中,很多時候我們需要實現上傳圖片,或者直接呼叫手機上的拍照功能拍照處理然後直接顯示並上傳功能,下面將講述呼叫相機拍照處理圖片然後顯示和呼叫手機相簿中的圖片處理然後顯示的功能,要想實現上傳功能,一般都是上傳到資料庫中,將imageView中的

Android呼叫系統相機相簿功能適配6.0許可權獲取以及7.0以後獲取URI(相容多版本)

  Android中呼叫系統相機來拍攝照片的程式碼,如下:1、首先設定Uri獲取判斷以及相機請求Codepublicfinalint TYPE_TAKE_PHOTO = 1;//Uri獲取型別判斷publicfinalint CODE_TAKE_PHOTO = 1;//相機R

菜鳥筆記2Android6.0版本關於呼叫系統相機的使用和圖片儲存

這兩天寫了一下相機的使用,看了網上很多文章,不是很全的,在我自己弄出來之後,做了一個整理歸納。我用的是Android6.0真機測試,華為手機。 1、Android6.0以上,在呼叫相機許可權時,必須要動態註冊。 驗證許可權+重寫許可權 //相機許可權標記,camera

Android7.0呼叫系統相機拍照訪問相簿問題到伺服器

//model層 package zhaochuang.bawei.com.myapplication.model; import java.util.Map; import okhttp3.RequestBody; import zhaochuang.bawei.com

h5呼叫相機相簿攝像頭拍照錄影壓縮預覽

1、使用h5中的input標籤  <!--相機--> <input id="fileBtn" type="file" onchange="upload('#fileBtn');" accept="image/*" capture='camera' styl

呼叫系統相機拍照獲取圖片或者從相簿呼叫本地圖片顯示問題

            需求是這樣的:開啟系統或者第三方相機app拍照,然後使用所拍照片進行處理,或者直接呼叫系統或者第三方相簿app,選擇圖片返回使用。需求很簡單,過程很蛋疼,網上有很多方法,不過有很多不好用的地方,最後使用的這種方式還不錯,記錄一下,還是那句話,我很懶,